region/
query.rs

1use 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.
6pub struct QueryIter {
7  iterator: Option<os::QueryIter>,
8  origin: *const (),
9}
10
11impl QueryIter {
12  pub(crate) fn new<T>(origin: *const T, size: usize) -> Result<Self> {
13    let origin = origin.cast();
14
15    os::QueryIter::new(origin, size).map(|iterator| Self {
16      iterator: Some(iterator),
17      origin,
18    })
19  }
20}
21
22impl 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  fn next(&mut self) -> Option<Self::Item> {
33    let regions = self.iterator.as_mut()?;
34
35    while let Some(result) = regions.next() {
36      match result {
37        Ok(region) => {
38          let range = region.as_range();
39
40          // Skip the region if it precedes the queried range
41          if range.end <= self.origin as usize {
42            continue;
43          }
44
45          // Stop iteration if the region is past the queried range
46          if range.start >= regions.upper_bound() {
47            break;
48          }
49
50          return Some(Ok(region));
51        }
52        Err(error) => {
53          self.iterator.take();
54          return Some(Err(error));
55        }
56      }
57    }
58
59    self.iterator.take();
60    None
61  }
62}
63
64impl std::iter::FusedIterator for QueryIter {}
65
66unsafe impl Send for QueryIter {}
67unsafe 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]
99pub fn query<T>(address: *const T) -> Result<Region> {
100  // For UNIX systems, the address must be aligned to the closest page boundary
101  let (address, size) = util::round_to_page_boundaries(address, 1)?;
102
103  QueryIter::new(address, size)?
104    .next()
105    .ok_or(Error::UnmappedRegion)?
106}
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]
154pub fn query_range<T>(address: *const T, size: usize) -> Result<QueryIter> {
155  let (address, size) = util::round_to_page_boundaries(address, size)?;
156  QueryIter::new(address, size)
157}
158
159#[cfg(test)]
160mod 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}