Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 asynchronouslyafter the façade returns, up until completion. The memory must remain valid until then. The Mapping type (unique RAII, Drop=munmap) does not guarantee this: nothing prevents its Drop (hence munmap) 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, munmap at 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. MmapRegion makes one allocation (the inner Arc). This is allowed and documented: (1) it is opt-in — the common Mapping path stays zero-cost; (2) the cost is negligible and amortized against the mmap syscall 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:

  1. it validates the range/futex word against the region’s bounds (Principle 4);
  2. it clones an MmapRegionLiveness (strong counter +1);
  3. 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). MmapRegion does 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 existing ProtectionFlags/MapFlags).
  • air-sys-syscall::mem: MmapRegion, MmapRegionLiveness (RAII calling munmap → lives in the wrappers crate, like Mapping).
  • Mapping stays unchanged (no modification of its representation nor its Drop) → existing mem coverage intact.

5. Tests

  • Safety (Miri) — the central point: park an op (simulated madvise/futex) that holds an MmapRegionLiveness, drop the user MmapRegionno munmap as long as the guard lives; free the slot → munmap at 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/range out of bounds or unaligned → EINVAL before submission.
  • Integration (once consumed by io_uring): madvise and futex_wait woken by futex_wake on an MmapRegion shared between two threads.
  • from_mapping: the consumed Mapping no 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

  1. MmapRegion distinct from Mapping: Mapping stays unique/zero-cost and untouched (coverage preserved); MmapRegion is the shareable opt-in for async io_uring.
  2. Reference-counted → neither UAF nor leak: refines the “leak rather than UAF” envisaged (ADR-028/2a §6.2); munmap at the last drop.
  3. MmapRegionLiveness guard parked in the S1 slot: the memory safety of madvise/futex is by construction, with no exposed unsafe.
  4. Corrected futex signature (&MmapRegion + offset, not &AtomicU32) — reconciles 2c with the asynchronous ownership model.
  5. 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).