unsafe-rust-attestation · git:20260615.ee0cdb5 · 2026-06-15 · sha256 227c947c33884eb2

unsafe-rust-attestation git:20260615.ee0cdb5A

Immutable. This exact content is served forever at /api/v1/blob/227c947c33884eb2.

---
name: unsafe-rust-attestation
description: How sparq attests memory safety — the forbid(unsafe_code) posture across most crates, the concentrated sparq-core unsafe surface (mmap / dict-spill / SIMD, threat-model boundary B5), the nightly Miri lane, cargo-geiger, and the per-site unsafe-justification register (gap GX-5) — and how to document plus verify every unsafe block for the memory-safety attestation framework. Use when working the memsafety certification worktree, enumerating or justifying an unsafe site, wiring the cargo-geiger ratchet, reasoning about what Miri can and cannot reach, or attesting the Miri/fuzz/oracle coverage matrix. Grounded in sparq's real research/threat-model.md + crates/sparq-core + .github/workflows/miri.yml.
---

# Unsafe-Rust attestation (sparq)

sparq's headline safety claim is memory safety. This skill is how to *attest* it
honestly for the `memsafety` certification worktree — the `forbid(unsafe_code)`
posture, the concentrated `sparq-core` unsafe surface (threat-model boundary
**B5**), and the verification estate (Miri + fuzz + the deterministic oracle).

> NON-CANONICAL timing. No measured numbers belong in this file.

## The posture: forbid where you can, concentrate + justify where you can't

- **`#![forbid(unsafe_code)]`** is declared in the large majority of workspace
  crates (sparq-serve, sparq-reason, sparq-zk, sparq-nlq, sparq-shacl, and most
  others — `grep -rl "forbid(unsafe_code)" crates/`). `forbid` (not `deny`) means
  the lint can't even be locally `#[allow]`-ed away — a hard compile error if any
  `unsafe` is introduced. **This is the strongest attestable claim and it's free.**
- **`sparq-core` carries the *vast majority* of the `unsafe`, and all of the B5
  (untrusted-input) risk.** A few other crates have small, non-B5 unsafe surfaces
  (e.g. `sparq-vectors` mmap/raw-slice casts, `sparq-cli`'s loader mmap in
  `main.rs`, `sparq-zk-compose`'s `libc::flock` FFI in `verifier.rs`, one
  `sparq-bench` block) — these do **not** carry `forbid(unsafe_code)`, so the
  audit surface is "the crates without `forbid` " not "one crate". Verify the
  live set with `grep -rL "forbid(unsafe_code)" $(git ls-files 'crates/*/src/lib.rs')`.
  Attestation stays tractable because the *dangerous* (hostile-input → unsafe)
  risk is still concentrated in `sparq-core`'s B5 boundary, enumerated below.

When scoring, the honest statement is: *"N of M crates forbid unsafe; the residual
unsafe is concentrated in sparq-core's B5 boundary, enumerated and verified
below."* Verify the live count with grep before quoting it — don't hard-code it.

## The B5 unsafe surface (`research/threat-model.md` §B5)

B5 = **hostile on-disk index file → mmap loader → unsafe code** — an
*untrusted-input → unsafe-code* boundary, the most dangerous class in the system.
`grep -rn "unsafe" crates/sparq-core/src` enumerates the sites — concentrated
across five files (run it for the live count; don't hard-code one here):

| File | Nature of the unsafe |
|---|---|
| `lib.rs` | `Graph::open` loader: mmap of permutation + dict + numerics/temporals caches; POD↔bytes reinterprets |
| `dict.rs` | zero-copy dictionary: `MappedDict::stored` (attacker-controlled `u64` offset), `rd_str` → `from_utf8_unchecked` over the mmap'd blob |
| `store.rs` | `TripleStore::open` raw-perm reinterpret (`from_raw_parts` over mmap bytes) |
| `dictspill.rs` | `#[cfg(feature = "dict-spill")]` ingest spill: libc `sysconf`/`statvfs` FFI, `from_utf8_unchecked` on own records, parallel scatter `ptr.add().write` |
| `extsort.rs` | external-sort buffer reinterprets |

**The sharpest edges (track these — they have beads):**
- **T-MMAP-UB (sq-znld):** `rd_str` calls `from_utf8_unchecked` on the mmap'd blob
  with **no UTF-8 check** → immediate UB on a hostile/corrupt store. Fix: checked
  `from_utf8` + bounds-check every offset (`dict-offs.bin` length `== len*8`,
  every offset `< dict-terms.bin.len()`) at open time.
- **T-MMAP-DoS (sq-ed2i):** `CompressedPerm::from_mmap` header arithmetic has no
  overflow guard + unchecked per-block offsets/varints → panic / OOB-read. Fix:
  `checked_mul`/`checked_add` + bounds-check directory offsets and every varint.
- **T-MMAP-FUZZ (sq-ky2a):** the loader isn't fuzzed against corrupt/truncated
  files. Fix: a fuzz target asserting *error-not-UB* under `--features dict-spill`.

The dict-spill unsafe is **NOT** the B5 attack surface (it operates on
internally-produced spill files, not hostile index files) — say so explicitly so
the attestation doesn't overstate the threat there.

## The verification estate — what reaches each site

| Verifier | Wired in | Reaches | Does NOT reach |
|---|---|---|---|
| **Miri** (UB) | `.github/workflows/miri.yml`, nightly | pure-Rust unsafe with default features (`parallel`): the `par_iter_mut` scatter writes in `dict.rs`, POD↔bytes reinterprets, `from_utf8_unchecked` over in-memory buffers, `MaybeUninit`+`set_len` remap | the **mmap-backed** sites — Miri rejects file-backed mappings ("Miri does not support file-backed memory mappings"), so `mmap`/`dict-spill` features are **off** in this lane |
| **mmap_corruption_oracle** (deterministic) | `ci.yml` under `--features mmap,dict-spill` | the mmap B5 sites Miri can't reach | non-determinism (it's a fixed oracle, not a fuzzer) |
| **fuzz** lane (cargo-fuzz) | `fuzz.yml`, PR smoke + nightly | hostile-input panics/OOM on the loader | UB detection (panics ≠ UB) |
| **cargo-geiger** | `ci.yml` `geiger` job, **informational** | counts unsafe sites (sparq-core, via `--manifest-path`; can't run the virtual root) | nothing gates on it yet (GX-5) |

**Miri flags (load-bearing — `miri.yml`):** `-Zmiri-tree-borrows` (rayon's
`crossbeam-epoch` violates Stacked Borrows; Tree Borrows is the correct model and
still catches real UB in sparq-core), `-Zmiri-ignore-leaks` (rayon daemon threads
outlive `main`), `-Zmiri-disable-isolation` (incidental clock/temp access). It's
**nightly-only** (Miri ships only on nightly; pinned by date) with **no PR
trigger**, so it creates no check-run and the ci-summary gate never waits on it —
the same "nightly safety net, not a per-PR tax" posture as fuzz/zk-toolchain.

The intended future addition is an **ASan lane** (`-Zsanitizer=address`, non-musl
target) to reach the mmap sites dynamically — deferred (noted in the sq-fo28 PR).

## The two attestation deliverables for memsafety (GX-5)

GX-5 is the open gap: *the unsafe surface has no per-site justification register,
and cargo-geiger is informational only (no gating ratchet).* Two things close it:

### 1. The unsafe-justification register
One document enumerating every `sparq-core` unsafe site with, per site:
- **Location** (`file.rs:line`) and **operation** (e.g. `from_utf8_unchecked`).
- **Safety invariant** — the precondition the caller must uphold for soundness.
- **Why it holds** — or, for the B5 untrusted-input sites, the validation that
  *must* run first (and the bead if it doesn't yet — sq-znld, sq-ed2i).
- **Which verifier covers it** — the Miri / oracle / fuzz column from the matrix.
This is the SSDF/memsafety evidence: every `unsafe` block has a written, reviewed
justification. Pair each with a `// SAFETY:` comment in source.

### 2. The cargo-geiger ratchet
Promote the informational geiger job to a **gating unsafe-count ratchet**:
check a checked-in expected count, fail if the count *increases* without an
accompanying register update (mirror the coverage/conformance ratchet idiom). A
PR that adds `unsafe` then can't merge without justifying it.

## How to use this

1. **Cite, don't re-derive.** The posture + verification estate above is live —
   reference `miri.yml`, the threat-model §B5, the `geiger` job in `ci.yml`.
2. **Gap-fixes land test-first** (`test-driven-development` skill): a register
   entry pairs with a `// SAFETY:` comment; the geiger ratchet lands with the
   checked-in count + a failing-on-increase test.
3. **Honesty contract.** Don't claim Miri covers the mmap sites (it structurally
   can't) — attest the *split* coverage (Miri for pure-Rust UB, oracle+fuzz for
   mmap) honestly. Don't claim "no unsafe" — claim "unsafe concentrated, enumerated,
   justified, verified". The known UB gaps (sq-znld) are real until fixed — never
   paper over them in a control table.

## Local commands
```
grep -rln "forbid(unsafe_code)" crates/          # the forbid-unsafe crate set
grep -rn "unsafe" crates/sparq-core/src          # enumerate the B5 surface
cargo +nightly miri test -p sparq-core           # the UB lane (no mmap features)
cargo test -p sparq-core --features mmap,dict-spill mmap_corruption_oracle
cargo geiger --manifest-path crates/sparq-core/Cargo.toml   # unsafe report
```

<!-- [OPUS-4.8] Authored for bead sq-toze.1 (epic sq-toze, cert framework). Grounded in
research/threat-model.md §B5, crates/sparq-core/src, .github/workflows/miri.yml + ci.yml geiger.
Re-review when Fable returns. -->