aligned_vmem/api/commit_range.rs
1use crate::error::VmemError;
2#[cfg(all(feature = "fault-injection", not(aligned_vmem_mock)))]
3use crate::fault_injection;
4#[cfg(aligned_vmem_mock)]
5use crate::mock;
6#[cfg(not(aligned_vmem_mock))]
7use crate::os::commit_range_impl;
8use crate::page_size::{page_size_or_poison, PAGE_SIZE_QUERY_FAILED};
9
10/// Commit pages `[base + start, base + end)` within an existing reservation.
11///
12/// This is the incremental-commit building block: after a
13/// [`reserve_aligned_lazy`](crate::api::reserve_aligned_lazy) call that left some pages reserved-but-uncommitted,
14/// `commit_range` commits exactly the requested sub-range so it becomes
15/// writable. On Windows this issues `VirtualAlloc(MEM_COMMIT)`; on Unix and
16/// under miri the pages are already accessible, so this is a no-op that always
17/// returns `true`.
18///
19/// `start` and `end` must be multiples of the runtime page size ([`page_size()`](crate::page_size))
20/// with `start <= end`. A well-formed no-op (an empty PAGE-ALIGNED range,
21/// `start == end`) returns `true`; any other contract violation (misaligned,
22/// or `start > end`) returns `false` (task #712: an earlier version of this
23/// function clamped a
24/// contract violation to the WRITE-PERMITTING `true` sentinel, which already
25/// caused a real crash — see
26/// <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md>
27/// item 6 for the incident this class of bug produces on Windows).
28///
29/// Returns `true` if the range is now committed, `false` if the OS refused
30/// (commit-charge exhaustion / true OOM) OR the offsets violated the contract
31/// above. On `false` the caller MUST NOT write into the range. Never panics.
32/// For the cause use [`try_commit_range`].
33///
34/// # Difference from [`recommit`](crate::api::recommit)
35///
36/// [`recommit`](crate::api::recommit) re-commits pages that were PREVIOUSLY committed and then
37/// decommitted via [`decommit`](crate::api::decommit). `commit_range` commits pages that were NEVER
38/// committed (reserved via the lazy path). The underlying Windows syscall is
39/// the same; the semantic intent differs.
40///
41/// # Safety
42///
43/// `base` must be the [`as_ptr`](crate::Reservation::as_ptr) of a live reservation,
44/// and `[base+start, base+end)` must fall within that reservation's usable span
45/// (i.e. `end <= len`). The range must be currently reserved but not yet
46/// committed (or already committed — recommitting is harmless on Windows).
47///
48/// **Concurrent calls are safe** (task #776, F14): multiple threads may call
49/// `commit_range` concurrently on ranges within the SAME reservation, whether
50/// the ranges overlap or not — `VirtualAlloc(MEM_COMMIT)` (Windows) is itself
51/// thread-safe and idempotent, and the Unix/miri backends are no-ops (the
52/// entire span is already committed eagerly on those platforms). This does
53/// NOT relax the range/liveness contract above; it only states that issuing
54/// several legal calls from different threads at once is not itself a new
55/// hazard. (Scalability caveat, not a safety one: with the `fault-injection`
56/// feature compiled in, the pre-syscall hook's `FAULT_STATE` mutex serializes
57/// concurrent callers — see the hook comment in [`try_commit_range`].)
58#[must_use]
59#[cfg(feature = "lazy-commit")]
60#[cfg_attr(docsrs, doc(cfg(feature = "lazy-commit")))]
61pub unsafe fn commit_range(base: *mut u8, start: usize, end: usize) -> bool {
62 // SAFETY: forwarded from the caller's contract.
63 unsafe { try_commit_range(base, start, end).is_ok() }
64}
65
66/// Fallible [`commit_range`]: `Ok(())` on success (or was a well-formed no-op),
67/// `Err(VmemError::invalid_argument())` if the offsets violated the contract
68/// (misaligned, or `start > end`), `Err(VmemError)` carrying the OS cause on
69/// genuine commit failure.
70///
71/// # Safety
72///
73/// Same as [`commit_range`].
74#[cfg(feature = "lazy-commit")]
75#[cfg_attr(docsrs, doc(cfg(feature = "lazy-commit")))]
76pub unsafe fn try_commit_range(base: *mut u8, start: usize, end: usize) -> Result<(), VmemError> {
77 let ps = page_size_or_poison();
78 // Failed OS page-size query: fail closed with the OS-side no-code error
79 // (NOT `invalid_argument` — the caller's arguments are not at fault).
80 // See `page_size`'s "If the one-time OS query fails" paragraph.
81 if ps == PAGE_SIZE_QUERY_FAILED {
82 return Err(VmemError::os_refusal_unknown_code());
83 }
84 if start > end || !start.is_multiple_of(ps) || !end.is_multiple_of(ps) {
85 return Err(VmemError::invalid_argument());
86 }
87 if start == end {
88 return Ok(());
89 }
90 #[cfg(aligned_vmem_mock)]
91 {
92 mock::record(mock::Call::CommitRange {
93 base: base.addr(),
94 start,
95 end,
96 });
97 mock::take_commit_fault().map_or(Ok(()), Err)
98 }
99 #[cfg(not(aligned_vmem_mock))]
100 {
101 // Real-path fault injection (feature `fault-injection`, DISTINCT from
102 // `mock`): consult the armed hooks immediately before the real
103 // syscall. Cost when the feature is compiled in (task #1068/F4
104 // corrected this comment, which previously claimed "two relaxed
105 // loads"): even with NOTHING armed, every real commit pays one atomic
106 // read-modify-write (`FAIL_NEXT::fetch_update`) plus an unconditional
107 // uncontended `FAULT_STATE` mutex acquire — the `target` check happens
108 // under the lock — serializing concurrent committers process-wide for
109 // as long as the feature is on (task #1021/R4-8 traded the old
110 // two-atomics fast path for arm/fire atomicity). Acceptable because
111 // the feature is test-only by design; when it is off, this block is
112 // compiled out entirely and the production path is unchanged.
113 #[cfg(feature = "fault-injection")]
114 if fault_injection::should_fail_commit() {
115 // task #713: this is a SIMULATED failure — no real syscall ran,
116 // so `VmemError::last_os_error()` would read whatever `errno`/
117 // `GetLastError` happens to be lying around from unrelated prior
118 // code, not a cause tied to this call at all.
119 // `os_refusal_unknown_code()` reports the no-code state without
120 // manufacturing a misleading one. Task #1141: this comment used
121 // to say the constructor "states plainly that the OS refused" —
122 // which contradicts the line four above it. The OS did NOT
123 // refuse; no syscall ran at all. This simulated fault is one of
124 // the sentinel's two TEST-ONLY sources (task #1173/L2,
125 // re-verified task #1194) — deliberately NOT one of the four
126 // PRODUCTION causes its own doc enumerates (see
127 // `VmemError::os_refusal_unknown_code`'s doc) — and it is
128 // deliberately NOT named after an OS refusal either way.
129 return Err(VmemError::os_refusal_unknown_code());
130 }
131 // SAFETY: forwarded from the caller's contract.
132 unsafe { commit_range_impl(base, start, end) }
133 }
134}