Layer 0 spec — mem family: MmapRegion (shareable mapping + io_uring liveness handle)
Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS). Extension
of the mem family.
Position
This document specifies MmapRegion, the shareable and io_uring-compatible
mapping on which two io_uring operations so far deferred depend:
submit_madvise (Temps 2a §6.2) and submit_futex_wait/wake/waitv
(Temps 2c §6.1). It unblocks both of them.
Emergence (doc-first method). The need surfaced while specifying io_uring (madvise at 2a, confirmed by futex at 2c): an io_uring operation that references userspace memory reads it asynchronously — after the façade returns, up until completion. The memory must remain valid until then. The
Mappingtype (unique RAII,Drop=munmap) does not guarantee this: nothing prevents itsDrop(hencemunmap) while an op is in flight → use-after-unmap (the kernel reads unmapped memory). A mapping is needed whose lifetime can be shared with the S1 slot of an in-flight op.
The naming mystery, resolved
The io_uring specs name MmapRegion; the mem family has Mapping. These are
two distinct types, by design:
Mapping(existing, unchanged): unique RAII (move-only),Drop=munmap, zero-cost (no allocation, ADR-021 c.4). For any ordinary synchronous use. We do not touch it — its 100% coverage stays intact.MmapRegion(new): shareable, reference-counted mapping,munmapat the last drop. It is the only type passable to io_uring’s asynchronous memory operations (madvise, futex). Opt-in: you only pay for sharing if you need it.
1. MmapRegion
#![allow(unused)]
fn main() {
/// **Shareable** mmap mapping, io_uring-compatible: its pages stay
/// valid as long as an in-flight io_uring operation (madvise/futex) references
/// memory in it. Reference-counted — `munmap` at the **last** drop (the region
/// **and** all liveness guards of in-flight ops).
#[derive(Clone)]
pub struct MmapRegion { /* Arc<MmapRegionInner> : ptr, len, prot */ }
impl MmapRegion {
/// Shareable anonymous mapping (equivalent to `mmap_anonymous` but `MmapRegion`).
/// # Errors `Errno` (cf. `mem` family).
pub fn new_anonymous(len: usize, prot: ProtectionFlags, flags: MapFlags)
-> Result<Self, Errno>;
/// Shareable file mapping.
pub fn from_file(fd: BorrowedFd<'_>, len: usize, prot: ProtectionFlags,
flags: MapFlags, offset: u64) -> Result<Self, Errno>;
/// Promotes a unique `Mapping` into a shareable region: **transfers** the
/// responsibility for the `munmap` to the shared inner (the `Mapping` is consumed,
/// its `Drop` no longer munmaps). A single allocation (the inner). The `prot`
/// **must reflect** the one the `Mapping` was created with (a bare `Mapping`
/// does not memorize its protections) — **no `READ|WRITE` default**,
/// otherwise `bytes()`/`futex_word()` would decide accessibility on a
/// false basis (a reference that faults on use).
pub fn from_mapping(mapping: Mapping, prot: ProtectionFlags) -> Self;
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool;
/// Readable byte slice (if `prot` is readable). **Safe**, bounded access.
pub fn bytes(&self) -> &[u8];
/// Reference to a **futex word** (`AtomicU32`) at `offset` bytes. The reference
/// is **shared-mutable** (`store`/`load` via `&`): the region must therefore
/// be **writable**.
/// # Errors `EINVAL` if `prot` does not contain `WRITE`, or if `offset` is
/// out of bounds (`offset + 4 > len`) or not aligned on 4.
pub fn futex_word(&self, offset: usize) -> Result<&AtomicU32, Errno>;
/// Raw pointer + length (advanced consumers; no ownership).
pub fn as_ptr(&self) -> *const u8;
}
}
Ownership model. MmapRegion wraps an Arc<MmapRegionInner>;
MmapRegionInner::Drop calls munmap (ignoring the error, like Mapping).
Cloning an MmapRegion increments the strong counter; munmap only happens at the
last drop. No page copy — only ownership sharing.
Upfront validation (Principle 4). futex_word checks writability
(prot ⊇ WRITE, since the returned reference is mutable), bounds and alignment (a
futex is an aligned u32) before returning the reference — EINVAL otherwise. bytes()
accesses are bounded by construction (empty slice if prot lacks READ).
“No alloc in the happy path” exception (ADR-021 c.4), justified.
MmapRegionmakes one allocation (the innerArc). This is allowed and documented: (1) it is opt-in — the commonMappingpath stays zero-cost; (2) the cost is negligible and amortized against themmapsyscall that creates the region (which dominates it by several orders of magnitude); (3) lifetime sharing is intrinsically necessary to the safety of asynchronous use (cf. ADR-021 c.4, “documented necessity” clause). To be recorded in the family notes.
2. Liveness handle (consumed by io_uring’s S1 slot)
#![allow(unused)]
fn main() {
/// Liveness guard retained by the S1 slot of an in-flight io_uring operation that
/// references the region. Keeps the pages **mapped** without granting access:
/// as long as a guard exists, `munmap` cannot occur.
pub struct MmapRegionLiveness { /* clone of the inner Arc */ }
impl MmapRegion {
/// Produces a liveness guard (internal clone). Used by the io_uring façades
/// `submit_madvise`/`submit_futex_*`: the guard is **parked in the
/// slot**, and released at completion → `munmap` at the last drop.
pub(crate) fn liveness_handle(&self) -> MmapRegionLiveness;
}
}
Safety mechanics (the core). When an io_uring façade submits an op referencing the region:
- it validates the range/futex word against the region’s bounds (Principle 4);
- it clones an
MmapRegionLiveness(strong counter +1); - it parks the guard in the S1 slot next to the op.
At completion, the slot is freed → the guard drops (counter −1). munmap only
occurs when the user region AND all in-flight guards are
dropped. The kernel therefore never reads unmapped memory: neither
use-after-unmap, nor a leak (munmap at the last drop, not “leak rather than UAF”).
Refinement of ADR-028 / Temps 2a §6.2. The initial wording envisaged, for madvise, a “leak rather than UAF” scheme (do not munmap if an op is in flight → leak).
MmapRegiondoes strictly better: reference-counted, it does not leak (munmap at the last drop) and cannot UAF. This is the chosen mechanism.
3. Consumption by io_uring (corrected signatures)
madvise (Temps 2a §6.2) — already compliant
#![allow(unused)]
fn main() {
pub fn submit_madvise(&mut self, region: &MmapRegion, range: Range<usize>, advice: MadviseAdvice)
-> Result<SubmissionToken, Errno>;
}
range.end ≤ region.len() validated upfront; the slot retains
region.liveness_handle().
futex (Temps 2c §6.1) — signature CORRECTION
The 2c spec showed futex: &AtomicU32: insufficient for async (the borrow
does not survive the façade’s return). It is replaced by a reference into an
MmapRegion + offset (the slot retains the liveness guard):
#![allow(unused)]
fn main() {
pub fn submit_futex_wait(&mut self, region: &MmapRegion, offset: usize,
expected: u64, mask: u64, flags: FutexFlags)
-> Result<SubmissionToken, Errno>; // OP 51
pub fn submit_futex_wake(&mut self, region: &MmapRegion, offset: usize,
nr: u64, mask: u64, flags: FutexFlags)
-> Result<SubmissionToken, Errno>; // OP 52
pub fn submit_futex_waitv(&mut self, waiters: Vec<FutexWaiter>, flags: FutexFlags)
-> Result<SubmissionToken, Errno>; // OP 53
/// One waiter of `futex_waitv`: the region + the word's offset and the expected
/// value. The region is kept alive by the slot. **No mask**: the kernel
/// `struct futex_waitv` carries none (cf. note below).
pub struct FutexWaiter {
pub region: MmapRegion, // owned (clone) ⇒ natural liveness guard
pub offset: usize,
pub expected: u64,
}
}
offset locates the futex word within the region (writable, 4-aligned, bounded —
EINVAL otherwise). ABI note: kernel struct futex_waitv = { val, uaddr, flags, __reserved } (no per-waiter mask); FutexWaiter therefore does not expose
a mask (a silently ignored field would be a footgun, ADR-032). For
a masked wait, use submit_futex_wait (single-wait, which keeps
mask). The io-uring-2c-async.md spec and the mem family are reconciled
on these signatures.
4. Type distribution
air-sys-types::mem: no new pure type required (reuses the existingProtectionFlags/MapFlags).air-sys-syscall::mem:MmapRegion,MmapRegionLiveness(RAII callingmunmap→ lives in the wrappers crate, likeMapping).Mappingstays unchanged (no modification of its representation nor itsDrop) → existingmemcoverage intact.
5. Tests
- Safety (Miri) — the central point: park an op (simulated madvise/futex) that
holds an
MmapRegionLiveness, drop the userMmapRegion→ nomunmapas long as the guard lives; free the slot →munmapat the last drop. Verify no UAF (memory stays readable as long as a guard exists) and no leak (munmap does happen at the last drop). Exact strong counter under loom (concurrent clones/drops). - Validation:
futex_word/rangeout of bounds or unaligned →EINVALbefore submission. - Integration (once consumed by io_uring):
madviseandfutex_waitwoken byfutex_wakeon anMmapRegionshared between two threads. from_mapping: the consumedMappingno longer munmaps; the region does so at the last drop (no double munmap).- Coverage 100% lines + branches; non-triggerable resource errors →
COVERAGE-EXCEPTIONS.md(STRUCTURAL).
6. Core decisions
MmapRegiondistinct fromMapping:Mappingstays unique/zero-cost and untouched (coverage preserved);MmapRegionis the shareable opt-in for async io_uring.- Reference-counted → neither UAF nor leak: refines the “leak rather than UAF”
envisaged (ADR-028/2a §6.2);
munmapat the last drop. MmapRegionLivenessguard parked in the S1 slot: the memory safety of madvise/futex is by construction, with no exposedunsafe.- Corrected futex signature (
&MmapRegion+ offset, not&AtomicU32) — reconciles 2c with the asynchronous ownership model. - One allocation (Arc) accepted (ADR-021 c.4 “documented necessity”): opt-in,
negligible against
mmap.
7. Work to resume
This spec unblocks: MmapRegion implementation (coordinated mem PR), then
submit_madvise (Temps 2a, remainder) and submit_futex_* (Temps 2c, remainder) —
reconcile the signatures in io-uring-2a-filesystem.md and
io-uring-2c-async.md. fanotify, epoll (depending on 2c feedback): independent.
Document license: MPL 2.0
Status: Technical specification of MmapRegion (extension air-sys-syscall::mem),
target kernel 6.12 LTS. Unblocks madvise (2a) and futex (2c).