Coverage Report

Created: 2026-03-26 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/region-3.0.2/src/protect.rs
Line
Count
Source
1
use crate::{os, util, Protection, QueryIter, Region, Result};
2
3
/// Changes the memory protection of one or more pages.
4
///
5
/// The address range may overlap one or more pages, and if so, all pages
6
/// spanning the range will be modified. The previous protection flags are not
7
/// preserved (if you desire to preserve the protection flags, use
8
/// [`protect_with_handle`]).
9
///
10
/// # Parameters
11
///
12
/// - The range is `[address, address + size)`
13
/// - The address is rounded down to the closest page boundary.
14
/// - The size may not be zero.
15
/// - The size is rounded up to the closest page boundary, relative to the
16
///   address.
17
///
18
/// # Errors
19
///
20
/// - If an interaction with the underlying operating system fails, an error
21
/// will be returned.
22
/// - If size is zero,
23
/// [`Error::InvalidParameter`](crate::Error::InvalidParameter) will be
24
/// returned.
25
///
26
/// # Safety
27
///
28
/// This function can violate memory safety in a myriad of ways. Read-only memory
29
/// can become writable, the executable properties of code segments can be
30
/// removed, etc.
31
///
32
/// # Examples
33
///
34
/// - Make an array of x86 assembly instructions executable.
35
///
36
/// ```
37
/// # fn main() -> region::Result<()> {
38
/// # if cfg!(any(target_arch = "x86", target_arch = "x86_64"))
39
/// #   && !cfg!(any(target_os = "openbsd", target_os = "netbsd")) {
40
/// use region::Protection;
41
/// let ret5 = [0xB8, 0x05, 0x00, 0x00, 0x00, 0xC3u8];
42
///
43
/// let x: extern "C" fn() -> i32 = unsafe {
44
///   region::protect(ret5.as_ptr(), ret5.len(), region::Protection::READ_WRITE_EXECUTE)?;
45
///   std::mem::transmute(ret5.as_ptr())
46
/// };
47
///
48
/// assert_eq!(x(), 5);
49
/// # }
50
/// # Ok(())
51
/// # }
52
/// ```
53
#[inline]
54
5.34k
pub unsafe fn protect<T>(address: *const T, size: usize, protection: Protection) -> Result<()> {
55
5.34k
  let (address, size) = util::round_to_page_boundaries(address, size)?;
56
5.34k
  os::protect(address.cast(), size, protection)
57
5.34k
}
region::protect::protect::<u8>
Line
Count
Source
54
1.19k
pub unsafe fn protect<T>(address: *const T, size: usize, protection: Protection) -> Result<()> {
55
1.19k
  let (address, size) = util::round_to_page_boundaries(address, size)?;
56
1.19k
  os::protect(address.cast(), size, protection)
57
1.19k
}
region::protect::protect::<u8>
Line
Count
Source
54
681
pub unsafe fn protect<T>(address: *const T, size: usize, protection: Protection) -> Result<()> {
55
681
  let (address, size) = util::round_to_page_boundaries(address, size)?;
56
681
  os::protect(address.cast(), size, protection)
57
681
}
Unexecuted instantiation: region::protect::protect::<_>
region::protect::protect::<u8>
Line
Count
Source
54
2.17k
pub unsafe fn protect<T>(address: *const T, size: usize, protection: Protection) -> Result<()> {
55
2.17k
  let (address, size) = util::round_to_page_boundaries(address, size)?;
56
2.17k
  os::protect(address.cast(), size, protection)
57
2.17k
}
region::protect::protect::<u8>
Line
Count
Source
54
1.29k
pub unsafe fn protect<T>(address: *const T, size: usize, protection: Protection) -> Result<()> {
55
1.29k
  let (address, size) = util::round_to_page_boundaries(address, size)?;
56
1.29k
  os::protect(address.cast(), size, protection)
57
1.29k
}
58
59
/// Temporarily changes the memory protection of one or more pages.
60
///
61
/// The address range may overlap one or more pages, and if so, all pages within
62
/// the range will be modified. The protection flag for each page will be reset
63
/// once the handle is dropped. To conditionally prevent a reset, use
64
/// [`std::mem::forget`].
65
///
66
/// This function uses [`query_range`](crate::query_range) internally and is
67
/// therefore less performant than [`protect`]. Use this function only if you
68
/// need to reapply the memory protection flags of one or more regions after
69
/// operations.
70
///
71
/// # Guard
72
///
73
/// Remember not to conflate the *black hole* syntax with the ignored, but
74
/// unused, variable syntax. Otherwise the [`ProtectGuard`] instantly resets the
75
/// protection flags of all pages.
76
///
77
/// ```ignore
78
/// let _ = protect_with_handle(...);      // Pages are instantly reset
79
/// let _guard = protect_with_handle(...); // Pages are reset once `_guard` is dropped.
80
/// ```
81
///
82
/// # Parameters
83
///
84
/// - The range is `[address, address + size)`
85
/// - The address is rounded down to the closest page boundary.
86
/// - The size may not be zero.
87
/// - The size is rounded up to the closest page boundary, relative to the
88
///   address.
89
///
90
/// # Errors
91
///
92
/// - If an interaction with the underlying operating system fails, an error
93
/// will be returned.
94
/// - If size is zero,
95
/// [`Error::InvalidParameter`](crate::Error::InvalidParameter) will be
96
/// returned.
97
///
98
/// # Safety
99
///
100
/// See [protect].
101
#[allow(clippy::missing_inline_in_public_items)]
102
0
pub unsafe fn protect_with_handle<T>(
103
0
  address: *const T,
104
0
  size: usize,
105
0
  protection: Protection,
106
0
) -> Result<ProtectGuard> {
107
0
  let (address, size) = util::round_to_page_boundaries(address, size)?;
108
109
  // Preserve the current regions' flags
110
0
  let mut regions = QueryIter::new(address, size)?.collect::<Result<Vec<_>>>()?;
111
112
  // Apply the desired protection flags
113
0
  protect(address, size, protection)?;
114
115
0
  if let Some(region) = regions.first_mut() {
116
0
    // Offset the lower region to the smallest page boundary
117
0
    region.base = address.cast();
118
0
    region.size -= address as usize - region.as_range().start;
119
0
  }
120
121
0
  if let Some(region) = regions.last_mut() {
122
0
    // Truncate the upper region to the smallest page boundary
123
0
    let protect_end = address as usize + size;
124
0
    region.size -= region.as_range().end - protect_end;
125
0
  }
126
127
0
  Ok(ProtectGuard::new(regions))
128
0
}
129
130
/// A RAII implementation of a scoped protection guard.
131
///
132
/// When this structure is dropped (falls out of scope), the memory regions'
133
/// protection will be reset.
134
#[must_use]
135
pub struct ProtectGuard {
136
  regions: Vec<Region>,
137
}
138
139
impl ProtectGuard {
140
  #[inline(always)]
141
0
  fn new(regions: Vec<Region>) -> Self {
142
0
    Self { regions }
143
0
  }
144
}
145
146
impl Drop for ProtectGuard {
147
  #[inline]
148
0
  fn drop(&mut self) {
149
0
    let result = self
150
0
      .regions
151
0
      .iter()
152
0
      .try_for_each(|region| unsafe { protect(region.base, region.size, region.protection) });
153
0
    debug_assert!(result.is_ok(), "restoring region protection: {:?}", result);
154
0
  }
155
}
156
157
unsafe impl Send for ProtectGuard {}
158
unsafe impl Sync for ProtectGuard {}
159
160
#[cfg(test)]
161
mod tests {
162
  use super::*;
163
  use crate::tests::util::alloc_pages;
164
  use crate::{page, query, query_range};
165
166
  #[test]
167
  fn protect_null_fails() {
168
    assert!(unsafe { protect(std::ptr::null::<()>(), 0, Protection::NONE) }.is_err());
169
  }
170
171
  #[test]
172
  #[cfg(not(any(
173
    target_os = "openbsd",
174
    target_os = "netbsd",
175
    all(target_vendor = "apple", target_arch = "aarch64")
176
  )))]
177
  fn protect_can_alter_text_segments() {
178
    #[allow(clippy::ptr_as_ptr)]
179
    let address = &mut protect_can_alter_text_segments as *mut _ as *mut u8;
180
    unsafe {
181
      protect(address, 1, Protection::READ_WRITE_EXECUTE).unwrap();
182
      *address = 0x90;
183
    }
184
  }
185
186
  #[test]
187
  fn protect_updates_both_pages_for_straddling_range() -> Result<()> {
188
    let pz = page::size();
189
190
    // Create a page boundary with different protection flags in the upper and
191
    // lower span, so the intermediate region sizes are fixed to one page.
192
    let map = alloc_pages(&[
193
      Protection::READ,
194
      Protection::READ_EXECUTE,
195
      Protection::READ_WRITE,
196
      Protection::READ,
197
    ]);
198
199
    let exec_page = unsafe { map.as_ptr().add(pz) };
200
    let exec_page_end = unsafe { exec_page.add(pz - 1) };
201
202
    // Change the protection over two page boundaries
203
    unsafe {
204
      protect(exec_page_end, 2, Protection::NONE)?;
205
    }
206
207
    // Query the two inner pages
208
    let result = query_range(exec_page, pz * 2)?.collect::<Result<Vec<_>>>()?;
209
210
    // On some OSs the pages are merged into one region
211
    assert!(matches!(result.len(), 1 | 2));
212
    assert_eq!(result.iter().map(Region::len).sum::<usize>(), pz * 2);
213
    assert_eq!(result[0].protection(), Protection::NONE);
214
    Ok(())
215
  }
216
217
  #[test]
218
  fn protect_has_inclusive_lower_and_exclusive_upper_bound() -> Result<()> {
219
    let map = alloc_pages(&[
220
      Protection::READ_WRITE,
221
      Protection::READ,
222
      Protection::READ_WRITE,
223
      Protection::READ,
224
    ]);
225
226
    // Alter the protection of the second page
227
    let second_page = unsafe { map.as_ptr().add(page::size()) };
228
    unsafe {
229
      let second_page_end = second_page.offset(page::size() as isize - 1);
230
      protect(second_page_end, 1, Protection::NONE)?;
231
    }
232
233
    let regions = query_range(map.as_ptr(), page::size() * 3)?.collect::<Result<Vec<_>>>()?;
234
    assert_eq!(regions.len(), 3);
235
    assert_eq!(regions[0].protection(), Protection::READ_WRITE);
236
    assert_eq!(regions[1].protection(), Protection::NONE);
237
    assert_eq!(regions[2].protection(), Protection::READ_WRITE);
238
239
    // Alter the protection of '2nd_page_start .. 2nd_page_end + 1'
240
    unsafe {
241
      protect(second_page, page::size() + 1, Protection::READ_EXECUTE)?;
242
    }
243
244
    let regions = query_range(map.as_ptr(), page::size() * 3)?.collect::<Result<Vec<_>>>()?;
245
    assert!(regions.len() >= 2);
246
    assert_eq!(regions[0].protection(), Protection::READ_WRITE);
247
    assert_eq!(regions[1].protection(), Protection::READ_EXECUTE);
248
    assert!(regions[1].len() >= page::size());
249
250
    Ok(())
251
  }
252
253
  #[test]
254
  fn protect_with_handle_resets_protection() -> Result<()> {
255
    let map = alloc_pages(&[Protection::READ]);
256
257
    unsafe {
258
      let _handle = protect_with_handle(map.as_ptr(), page::size(), Protection::READ_WRITE)?;
259
      assert_eq!(query(map.as_ptr())?.protection(), Protection::READ_WRITE);
260
    };
261
262
    assert_eq!(query(map.as_ptr())?.protection(), Protection::READ);
263
    Ok(())
264
  }
265
266
  #[test]
267
  fn protect_with_handle_only_alters_protection_of_affected_pages() -> Result<()> {
268
    let pages = [
269
      Protection::READ_WRITE,
270
      Protection::READ,
271
      Protection::READ_WRITE,
272
      Protection::READ_EXECUTE,
273
      Protection::NONE,
274
    ];
275
    let map = alloc_pages(&pages);
276
277
    let second_page = unsafe { map.as_ptr().add(page::size()) };
278
    let region_size = page::size() * 3;
279
280
    unsafe {
281
      let _handle = protect_with_handle(second_page, region_size, Protection::NONE)?;
282
      let region = query(second_page)?;
283
284
      assert_eq!(region.protection(), Protection::NONE);
285
      assert_eq!(region.as_ptr(), second_page);
286
    }
287
288
    let regions =
289
      query_range(map.as_ptr(), page::size() * pages.len())?.collect::<Result<Vec<_>>>()?;
290
291
    assert_eq!(regions.len(), 5);
292
    assert_eq!(regions[0].as_ptr(), map.as_ptr());
293
    for i in 0..pages.len() {
294
      assert_eq!(regions[i].protection(), pages[i]);
295
    }
296
297
    Ok(())
298
  }
299
}