Coverage Report

Created: 2024-10-16 07:58

/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
}