Skip to main content

aligned_vmem/api/
recommit.rs

1use crate::error::VmemError;
2#[cfg(aligned_vmem_mock)]
3use crate::mock;
4#[cfg(not(aligned_vmem_mock))]
5use crate::os::recommit_pages_impl;
6use crate::page_size::{page_size_or_poison, PAGE_SIZE_QUERY_FAILED};
7
8/// Recommit pages `[base + start, base + end)` previously passed to
9/// [`decommit`](crate::api::decommit). On Windows this re-commits physical pages
10/// (`VirtualAlloc(MEM_COMMIT)`); on Unix re-access is implicit so this is a
11/// no-op. On the Darwin family (macOS/iOS/tvOS/watchOS) specifically, whether
12/// re-access reads back zeroed pages or the pre-decommit contents is not
13/// guaranteed either way — see [`decommit`](crate::api::decommit)'s Darwin caveat for why.
14///
15/// Returns `true` if the range is now committed (or the call was a
16/// well-formed no-op — an empty PAGE-ALIGNED range, `start == end`), and
17/// `false` if the OS refused to
18/// commit the pages (commit-charge exhaustion / true OOM) OR the offsets
19/// violated the contract below. On `false` the caller MUST NOT write into
20/// `[base+start, base+end)`. Never panics. For the cause use [`try_recommit`].
21///
22/// # Safety
23///
24/// - `base` must be the [`as_ptr`](crate::Reservation::as_ptr) of a live
25///   reservation whose `[base+start, base+end)` range was previously
26///   decommitted.
27/// - **`end <= reservation.len()`** (the reservation's usable span, in
28///   bytes) — this is a MANDATORY precondition of the pointer arithmetic
29///   this function performs internally (`base.add(start)` in the Windows
30///   backend's `recommit_pages_impl`; the Unix and miri backends are no-ops
31///   but the contract is stated platform-independently), not merely a
32///   functional/behavioral preference. Before task #1229/F6 this function
33///   was the only range-taking free function whose `# Safety` lacked the
34///   bound: [`decommit`](crate::api::decommit)'s states it in full (task
35///   #1213/L2, whose wording this matches), `try_decommit` and
36///   `decommit_lazy` carry it (restated in prose / in full — the latter
37///   since task #1235, which replaced `decommit_lazy`'s earlier bare
38///   same-contract reference), and the
39///   [`commit_range`](crate::api::commit_range) pair spells it out as
40///   `end <= len`. For an `unsafe fn`, a
41///   bounds requirement that determines whether pointer arithmetic is even
42///   defined belongs inside `# Safety` itself, restated in full. Passing
43///   `end > reservation.len()` is undefined behavior (with `start <= end`
44///   the bound is what keeps the backend's `base.add(start)` offset
45///   in-bounds and the OS call's span `[base+start, base+end)` inside the
46///   reservation), distinct from — and a strictly worse violation than —
47///   the `page_size()`-multiple contract below, which merely returns
48///   `false` on violation, never UB. Callers through the safe
49///   [`Reservation::recommit`](crate::Reservation::recommit) /
50///   [`Reservation::try_recommit`](crate::Reservation::try_recommit)
51///   methods are not exposed: both bounds-check `end <= self.len()` before
52///   delegating here, so the gap reaches only callers of this free
53///   function directly.
54/// - `start`/`end` must be multiples of the runtime page size
55///   ([`page_size()`](crate::page_size)) with `start <= end` — a violation
56///   returns `false` (task #712: an earlier version of this function
57///   clamped a contract violation to the WRITE-PERMITTING `true` sentinel,
58///   which already caused a real crash — see
59///   <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md>
60///   item 6 for the incident this class of bug produces on Windows).
61#[must_use]
62pub unsafe fn recommit(base: *mut u8, start: usize, end: usize) -> bool {
63    // SAFETY: forwarded from the caller's contract.
64    unsafe { try_recommit(base, start, end).is_ok() }
65}
66
67/// Fallible [`recommit`]: `Ok(())` if the range is now committed (or was a
68/// well-formed no-op), `Err(VmemError::invalid_argument())` if the offsets
69/// violated the contract (misaligned, or `start > end`), `Err(VmemError)`
70/// carrying the OS cause on genuine commit failure.
71///
72/// # Safety
73///
74/// Same contract as [`recommit`], with the bound restated here rather than
75/// only referenced (task #1229/F6): this function is the one that actually
76/// reaches the backend — its non-mock arm calls `recommit_pages_impl`
77/// directly, and [`recommit`] forwards through here — so a caller auditing
78/// only this section must see it. `base` must be the
79/// [`as_ptr`](crate::Reservation::as_ptr) of a live reservation whose
80/// `[base+start, base+end)` range was previously decommitted, and
81/// **`end <= reservation.len()`** — passing a larger `end` is undefined
82/// behavior (the backend computes `base.add(start)` and nothing from
83/// `end`; with `start <= end` the bound is what keeps that offset
84/// in-bounds and the OS call's span `[base+start, base+end)` inside the
85/// reservation), a strictly worse violation than the
86/// `page_size()`-multiple / `start <= end` contract, which merely returns
87/// `Err(VmemError::invalid_argument())`, never UB.
88pub unsafe fn try_recommit(base: *mut u8, start: usize, end: usize) -> Result<(), VmemError> {
89    let ps = page_size_or_poison();
90    // Failed OS page-size query: fail closed with the OS-side no-code error
91    // (NOT `invalid_argument` — the caller's arguments are not at fault).
92    // See `page_size`'s "If the one-time OS query fails" paragraph.
93    if ps == PAGE_SIZE_QUERY_FAILED {
94        return Err(VmemError::os_refusal_unknown_code());
95    }
96    if start > end || !start.is_multiple_of(ps) || !end.is_multiple_of(ps) {
97        return Err(VmemError::invalid_argument());
98    }
99    if start == end {
100        return Ok(());
101    }
102    #[cfg(aligned_vmem_mock)]
103    {
104        mock::record(mock::Call::Recommit {
105            base: base.addr(),
106            start,
107            end,
108        });
109        mock::take_commit_fault().map_or(Ok(()), Err)
110    }
111    #[cfg(not(aligned_vmem_mock))]
112    // SAFETY: forwarded from the caller's contract.
113    unsafe {
114        recommit_pages_impl(base, start, end)
115    }
116}