/rust/registry/src/index.crates.io-6f17d22bba15001f/region-3.0.2/src/query.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::{os, util, Error, Region, Result}; |
2 | | |
3 | | /// An iterator over the [`Region`]s that encompass an address range. |
4 | | /// |
5 | | /// This `struct` is created by [`query_range`]. See its documentation for more. |
6 | | pub struct QueryIter { |
7 | | iterator: Option<os::QueryIter>, |
8 | | origin: *const (), |
9 | | } |
10 | | |
11 | | impl QueryIter { |
12 | 0 | pub(crate) fn new<T>(origin: *const T, size: usize) -> Result<Self> { |
13 | 0 | let origin = origin.cast(); |
14 | 0 |
|
15 | 0 | os::QueryIter::new(origin, size).map(|iterator| Self { |
16 | 0 | iterator: Some(iterator), |
17 | 0 | origin, |
18 | 0 | }) |
19 | 0 | } |
20 | | } |
21 | | |
22 | | impl Iterator for QueryIter { |
23 | | type Item = Result<Region>; |
24 | | |
25 | | /// Advances the iterator and returns the next region. |
26 | | /// |
27 | | /// If the iterator has been exhausted (i.e. all [`Region`]s have been |
28 | | /// queried), or if an error is encountered during iteration, all further |
29 | | /// invocations will return [`None`] (in the case of an error, the error will |
30 | | /// be the last item that is yielded before the iterator is fused). |
31 | | #[allow(clippy::missing_inline_in_public_items)] |
32 | 0 | fn next(&mut self) -> Option<Self::Item> { |
33 | 0 | let regions = self.iterator.as_mut()?; |
34 | | |
35 | 0 | while let Some(result) = regions.next() { |
36 | 0 | match result { |
37 | 0 | Ok(region) => { |
38 | 0 | let range = region.as_range(); |
39 | 0 |
|
40 | 0 | // Skip the region if it precedes the queried range |
41 | 0 | if range.end <= self.origin as usize { |
42 | 0 | continue; |
43 | 0 | } |
44 | 0 |
|
45 | 0 | // Stop iteration if the region is past the queried range |
46 | 0 | if range.start >= regions.upper_bound() { |
47 | 0 | break; |
48 | 0 | } |
49 | 0 |
|
50 | 0 | return Some(Ok(region)); |
51 | | } |
52 | 0 | Err(error) => { |
53 | 0 | self.iterator.take(); |
54 | 0 | return Some(Err(error)); |
55 | | } |
56 | | } |
57 | | } |
58 | | |
59 | 0 | self.iterator.take(); |
60 | 0 | None |
61 | 0 | } |
62 | | } |
63 | | |
64 | | impl std::iter::FusedIterator for QueryIter {} |
65 | | |
66 | | unsafe impl Send for QueryIter {} |
67 | | unsafe impl Sync for QueryIter {} |
68 | | |
69 | | /// Queries the OS with an address, returning the region it resides within. |
70 | | /// |
71 | | /// If the queried address does not reside within any mapped region, or if it's |
72 | | /// outside the process' address space, the function will error with |
73 | | /// [`Error::UnmappedRegion`]. |
74 | | /// |
75 | | /// # Parameters |
76 | | /// |
77 | | /// - The enclosing region can be of multiple page sizes. |
78 | | /// - The address is rounded down to the closest page boundary. |
79 | | /// |
80 | | /// # Errors |
81 | | /// |
82 | | /// - If an interaction with the underlying operating system fails, an error |
83 | | /// will be returned. |
84 | | /// |
85 | | /// # Examples |
86 | | /// |
87 | | /// ``` |
88 | | /// # fn main() -> region::Result<()> { |
89 | | /// use region::Protection; |
90 | | /// |
91 | | /// let data = [0; 100]; |
92 | | /// let region = region::query(data.as_ptr())?; |
93 | | /// |
94 | | /// assert_eq!(region.protection(), Protection::READ_WRITE); |
95 | | /// # Ok(()) |
96 | | /// # } |
97 | | /// ``` |
98 | | #[inline] |
99 | 0 | pub fn query<T>(address: *const T) -> Result<Region> { |
100 | | // For UNIX systems, the address must be aligned to the closest page boundary |
101 | 0 | let (address, size) = util::round_to_page_boundaries(address, 1)?; |
102 | | |
103 | 0 | QueryIter::new(address, size)? |
104 | 0 | .next() |
105 | 0 | .ok_or(Error::UnmappedRegion)? |
106 | 0 | } |
107 | | |
108 | | /// Queries the OS for mapped regions that overlap with the specified range. |
109 | | /// |
110 | | /// The implementation clamps any input that exceeds the boundaries of a |
111 | | /// process' address space. Therefore it's safe to, e.g., pass in |
112 | | /// [`std::ptr::null`] and [`usize::max_value`] to iterate the mapped memory |
113 | | /// pages of an entire process. |
114 | | /// |
115 | | /// If an error is encountered during iteration, the error will be the last item |
116 | | /// that is yielded. Thereafter the iterator becomes fused. |
117 | | /// |
118 | | /// A 2-byte range straddling a page boundary, will return both pages (or one |
119 | | /// region, if the pages share the same properties). |
120 | | /// |
121 | | /// This function only returns mapped regions. If required, unmapped regions can |
122 | | /// be manually identified by inspecting the potential gaps between two |
123 | | /// neighboring regions. |
124 | | /// |
125 | | /// # Parameters |
126 | | /// |
127 | | /// - The range is `[address, address + size)` |
128 | | /// - The address is rounded down to the closest page boundary. |
129 | | /// - The size may not be zero. |
130 | | /// - The size is rounded up to the closest page boundary, relative to the |
131 | | /// address. |
132 | | /// |
133 | | /// # Errors |
134 | | /// |
135 | | /// - If an interaction with the underlying operating system fails, an error |
136 | | /// will be returned. |
137 | | /// - If size is zero, [`Error::InvalidParameter`] will be returned. |
138 | | /// |
139 | | /// # Examples |
140 | | /// |
141 | | /// ``` |
142 | | /// # use region::Result; |
143 | | /// # fn main() -> Result<()> { |
144 | | /// let data = [0; 100]; |
145 | | /// let region = region::query_range(data.as_ptr(), data.len())? |
146 | | /// .collect::<Result<Vec<_>>>()?; |
147 | | /// |
148 | | /// assert_eq!(region.len(), 1); |
149 | | /// assert_eq!(region[0].protection(), region::Protection::READ_WRITE); |
150 | | /// # Ok(()) |
151 | | /// # } |
152 | | /// ``` |
153 | | #[inline] |
154 | 0 | pub fn query_range<T>(address: *const T, size: usize) -> Result<QueryIter> { |
155 | 0 | let (address, size) = util::round_to_page_boundaries(address, size)?; |
156 | 0 | QueryIter::new(address, size) |
157 | 0 | } |
158 | | |
159 | | #[cfg(test)] |
160 | | mod tests { |
161 | | use super::*; |
162 | | use crate::tests::util::alloc_pages; |
163 | | use crate::{page, Protection}; |
164 | | |
165 | | const TEXT_SEGMENT_PROT: Protection = if cfg!(target_os = "openbsd") { |
166 | | Protection::EXECUTE |
167 | | } else { |
168 | | Protection::READ_EXECUTE |
169 | | }; |
170 | | |
171 | | #[test] |
172 | | fn query_returns_unmapped_for_oob_address() { |
173 | | let (min, max) = (std::ptr::null::<()>(), usize::max_value() as *const ()); |
174 | | assert!(matches!(query(min), Err(Error::UnmappedRegion))); |
175 | | assert!(matches!(query(max), Err(Error::UnmappedRegion))); |
176 | | } |
177 | | |
178 | | #[test] |
179 | | fn query_returns_correct_descriptor_for_text_segment() -> Result<()> { |
180 | | let region = query(query_returns_correct_descriptor_for_text_segment as *const ())?; |
181 | | |
182 | | assert_eq!(region.protection(), TEXT_SEGMENT_PROT); |
183 | | assert_eq!(region.is_shared(), cfg!(windows)); |
184 | | assert!(!region.is_guarded()); |
185 | | Ok(()) |
186 | | } |
187 | | |
188 | | #[test] |
189 | | fn query_returns_one_region_for_multiple_page_allocation() -> Result<()> { |
190 | | let alloc = crate::alloc(page::size() + 1, Protection::READ_EXECUTE)?; |
191 | | let region = query(alloc.as_ptr::<()>())?; |
192 | | |
193 | | assert_eq!(region.protection(), Protection::READ_EXECUTE); |
194 | | assert_eq!(region.as_ptr::<()>(), alloc.as_ptr()); |
195 | | assert_eq!(region.len(), alloc.len()); |
196 | | assert!(!region.is_guarded()); |
197 | | Ok(()) |
198 | | } |
199 | | |
200 | | #[test] |
201 | | #[cfg(not(target_os = "android"))] // TODO: Determine why this fails on Android in QEMU |
202 | | fn query_is_not_off_by_one() -> Result<()> { |
203 | | let pages = [Protection::READ, Protection::READ_EXECUTE, Protection::READ]; |
204 | | let map = alloc_pages(&pages); |
205 | | |
206 | | let page_mid = unsafe { map.as_ptr().add(page::size()) }; |
207 | | let region = query(page_mid)?; |
208 | | |
209 | | assert_eq!(region.protection(), Protection::READ_EXECUTE); |
210 | | assert_eq!(region.len(), page::size()); |
211 | | |
212 | | let region = query(unsafe { page_mid.offset(-1) })?; |
213 | | |
214 | | assert_eq!(region.protection(), Protection::READ); |
215 | | assert_eq!(region.len(), page::size()); |
216 | | Ok(()) |
217 | | } |
218 | | |
219 | | #[test] |
220 | | fn query_range_does_not_return_unmapped_regions() -> Result<()> { |
221 | | let regions = query_range(std::ptr::null::<()>(), 1)?.collect::<Result<Vec<_>>>()?; |
222 | | assert!(regions.is_empty()); |
223 | | Ok(()) |
224 | | } |
225 | | |
226 | | #[test] |
227 | | fn query_range_returns_both_regions_for_straddling_range() -> Result<()> { |
228 | | let pages = [Protection::READ_EXECUTE, Protection::READ_WRITE]; |
229 | | let map = alloc_pages(&pages); |
230 | | |
231 | | // Query an area that overlaps both pages |
232 | | let address = unsafe { map.as_ptr().offset(page::size() as isize - 1) }; |
233 | | let regions = query_range(address, 2)?.collect::<Result<Vec<_>>>()?; |
234 | | |
235 | | assert_eq!(regions.len(), pages.len()); |
236 | | for (page, region) in pages.iter().zip(regions.iter()) { |
237 | | assert_eq!(*page, region.protection); |
238 | | } |
239 | | Ok(()) |
240 | | } |
241 | | |
242 | | #[test] |
243 | | fn query_range_has_inclusive_lower_and_exclusive_upper_bound() -> Result<()> { |
244 | | let pages = [Protection::READ, Protection::READ_WRITE, Protection::READ]; |
245 | | let map = alloc_pages(&pages); |
246 | | |
247 | | let regions = query_range(map.as_ptr(), page::size())?.collect::<Result<Vec<_>>>()?; |
248 | | assert_eq!(regions.len(), 1); |
249 | | assert_eq!(regions[0].protection(), Protection::READ); |
250 | | |
251 | | let regions = query_range(map.as_ptr(), page::size() + 1)?.collect::<Result<Vec<_>>>()?; |
252 | | assert_eq!(regions.len(), 2); |
253 | | assert_eq!(regions[0].protection(), Protection::READ); |
254 | | assert_eq!(regions[1].protection(), Protection::READ_WRITE); |
255 | | Ok(()) |
256 | | } |
257 | | |
258 | | #[test] |
259 | | fn query_range_can_iterate_over_entire_process() -> Result<()> { |
260 | | let regions = |
261 | | query_range(std::ptr::null::<()>(), usize::max_value())?.collect::<Result<Vec<_>>>()?; |
262 | | |
263 | | // This test is a bit rough around the edges |
264 | | assert!(regions |
265 | | .iter() |
266 | | .any(|region| region.protection() == Protection::READ)); |
267 | | assert!(regions |
268 | | .iter() |
269 | | .any(|region| region.protection() == Protection::READ_WRITE)); |
270 | | assert!(regions |
271 | | .iter() |
272 | | .any(|region| region.protection() == TEXT_SEGMENT_PROT)); |
273 | | assert!(regions.len() > 5); |
274 | | Ok(()) |
275 | | } |
276 | | |
277 | | #[test] |
278 | | fn query_range_iterator_is_fused_after_exhaustion() -> Result<()> { |
279 | | let pages = [Protection::READ, Protection::READ_WRITE]; |
280 | | let map = alloc_pages(&pages); |
281 | | let mut iter = query_range(map.as_ptr(), page::size() + 1)?; |
282 | | |
283 | | assert_eq!( |
284 | | iter.next().transpose()?.map(|r| r.protection()), |
285 | | Some(Protection::READ) |
286 | | ); |
287 | | assert_eq!( |
288 | | iter.next().transpose()?.map(|r| r.protection()), |
289 | | Some(Protection::READ_WRITE) |
290 | | ); |
291 | | assert_eq!(iter.next().transpose()?, None); |
292 | | assert_eq!(iter.next().transpose()?, None); |
293 | | Ok(()) |
294 | | } |
295 | | } |