# mgf-hrb — Privileged-Core Hardening (resolving 9.7 #6)

Resolving the last design-resolvable open question: **how is the tiny privileged core (Part 7.5) actually isolated on Linux — a logical boundary (a seccomp-confined thread inside the worker) or a hard boundary (a separate privileged helper process per worker)?** This is a threat-model decision, not a measurement one, so it can be settled now by reasoning about blast radius and the priority order.

---

## 1. What's actually being decided

Recall the worker's internal split (Part 7.5): the bulk of the worker is unprivileged (protocol handling, the lease engine, capability *verification*), and a *tiny privileged core* does the few acts that touch hardware (the kabi backend's `acquire`/`reset`/`map`, holding the elevated fd to `/dev/vfio`). The question is the *strength of the wall* between those two regions:

```
   OPTION A — logical boundary            OPTION B — hard boundary
   (one process, two zones)               (two processes)

   ┌─────────────────────────┐            ┌─────────────────────────┐
   │ worker process           │            │ worker process (unpriv)  │
   │ ┌─────────────────────┐ │            │  lease engine, cap verify│
   │ │ unpriv: lease, cap   │ │            │  protocol handling       │
   │ ├─────────────────────┤ │            └───────────┬─────────────┘
   │ │ priv: seccomp thread │ │                        │ tiny IPC
   │ │ holds /dev/vfio fd   │ │            ┌───────────▼─────────────┐
   │ └─────────────────────┘ │            │ priv helper (separate)   │
   │ shared address space     │            │ holds /dev/vfio fd       │
   └─────────────────────────┘            │ SEPARATE address space   │
                                          └─────────────────────────┘
   wall = seccomp + thread                 wall = process + kernel
   discipline; SHARED memory               isolation; NO shared memory
```

The difference that matters: in Option A the two zones **share an address space**, so a memory-corruption bug in the large unprivileged region can in principle reach the privileged zone's state (including the elevated fd). In Option B they are **separate processes**, so the only thing crossing is explicit IPC messages — a corrupted unprivileged worker cannot reach into the helper's memory at all.

---

## 2. The threat model — what each defends against

| Threat | Option A (seccomp thread) | Option B (helper process) |
|---|---|---|
| Bug in unpriv code crashes the worker | both zones die together; recover via supervision (Part 7.2) | only unpriv dies; helper survives or is restarted independently |
| Memory-corruption exploit in unpriv code (the protocol parser is the classic target) | **can reach the privileged fd** in shared memory — wall breached | **cannot reach** the helper's memory; must additionally exploit the IPC boundary |
| Compromised unpriv code tries to issue unauthorized hardware ops | seccomp limits *syscalls* but the code already holds the fd in-process | helper independently re-validates each request; holds the only fd |
| Supply-chain / logic bug grants over-broad capability | contained by capability attenuation (Part 7.6) — same either way | contained by capability attenuation **plus** process isolation |

The decisive row is the second: **the unprivileged region contains the protocol parser, which parses untrusted bytes from arbitrary clients — the single highest-risk code in the whole system.** A memory-safety exploit there is the realistic attack. Under Option A, that exploit shares an address space with the elevated `/dev/vfio` fd; under Option B, the exploit is trapped in a process that holds no hardware authority and must *also* defeat the IPC boundary and the helper's independent re-validation to do damage.

> Note Zig's memory safety reduces but does not eliminate this risk — Zig is safer than C but not provably memory-safe like Rust, and the parser handles adversarial input. We do not assume the parser is exploit-free; we architect so that its compromise is survivable. (Reliability/security doctrine: defense in depth, not faith in any one layer.)

---

## 3. The decision: hard boundary (Option B), with a precise minimality constraint

**Decision: separate privileged helper process per worker (Option B).** The privileged core is a distinct process holding the only elevated hardware handle; the unprivileged worker reaches it through a tiny, typed IPC. The rationale follows the priority order:

- **Reliability #1 / security:** the realistic attack (exploit in the untrusted-input parser) must not reach hardware authority. Process isolation is the only wall that *categorically* prevents shared-memory reach; seccomp narrows syscalls but leaves the in-process fd exposed. When the top priority is reliability and the exposed surface parses adversarial input, the categorical wall wins.
- **It composes with what's already there.** Option B *reinforces* the two existing containment mechanisms rather than duplicating them: capability attenuation (Part 7.6) bounds *what authority exists*, the unprivileged/privileged split bounds *what code can act*, and now process isolation bounds *what memory can be reached*. Three independent walls; an attacker must defeat all three to cross from "exploited a client-facing parser" to "controls arbitrary hardware." That is genuine defense in depth.
- **The cost is acceptable and bounded.** The objection to Option B is performance (an IPC hop) and complexity (another process). But — crucially — **the privileged helper is only on the *control* path** (`acquire`/`reset`/`map`-setup), never the data or use path. Once a device is acquired and the raw channel handed out (Part 3.8), the client talks to hardware *directly*; the helper is absent. So the IPC hop is paid only at claim time (cold, infrequent, Part 5.1), exactly where latency is irrelevant. Performance priority #2 is *not* taxed on any hot path — the cost lands precisely where we can afford it.

### 3.1 The minimality constraint (what makes B safe rather than just isolated)

Isolation is necessary but not sufficient — a large helper is a large attack surface even if isolated. So the decision carries a hard constraint:

> **The privileged helper must be the smallest auditable unit: it performs only the specific kabi hardware operations, parses no client protocol, makes no policy decisions, and verifies no capabilities.** It receives *already-verified, already-typed* operation requests from the unprivileged worker and executes exactly those. Its entire input surface is the handful of typed kabi operations (Part 6.2: `acquire`, `release`, `reset`, `map_region`, `raw_channel`) — not bytes, not client messages.

This is the critical subtlety: **the helper does not re-implement verification — it trusts the worker's verification but holds the authority.** Wait — that seems to undercut isolation. It does not, and the reason is precise: the worker decides *whether* an operation is permitted (capability math, unprivileged, exploitable-but-powerless); the helper holds the *power* to perform it but accepts only well-typed operations on resources it manages. An exploited worker can *ask* the helper to do permitted-looking things, but (a) it can only ask for operations the typed IPC allows — no arbitrary syscalls, no fd theft, no memory reach — and (b) the blast radius is bounded to that worker's own bus subtree, because the helper holds only the attenuated authority for that subtree (Part 7.6). The worst case degrades from "arbitrary hardware control" to "misuse of the one bus this worker already legitimately controls" — a dramatically smaller blast radius, which is the whole point of defense in depth.

### 3.2 Optional further hardening (recorded, not mandated)

For the highest-criticality deployments, the helper can additionally *re-validate* a minimal invariant (e.g. "this operation names a resource within my seeded subtree") as a cheap belt-and-suspenders check — catching a worker that's not just exploited but lying. This is a deployment-time toggle, not a default, because it duplicates a check the worker already does; it's listed so the option exists when the threat model warrants paranoia.

---

## 4. How this updates the design

Folding back into the main document (honestly recorded):

1. **Part 7.5 is resolved to Option B:** the "tiny privileged core" is a *separate helper process per worker*, not an in-process seccomp thread. The prose in 7.5 that called it "a logical boundary that *can* be hardened into a process boundary" is now settled: it *is* a process boundary, by default.
2. **The constellation (Part 7.1) gains a helper process per worker:** facade → worker (unpriv) → privileged helper (per worker). The helper is the *only* process per worker holding an elevated hardware handle.
3. **The friction→kernel-feature list (Part 9.6 / kernel brief Feature 7) is reinforced, not changed:** on Linux this isolation costs a whole extra process per worker plus IPC. On the future kernel, *privilege-as-a-thread-held-capability* (Feature 7) collapses Option B back to Option A's simplicity *with* Option B's safety — a thread holds the hardware capability and no other thread in the address space can use it, so you get process-grade isolation without a second process. **This decision is therefore the strongest single argument for kernel Feature 7:** Linux forces a costly process split to get categorical isolation; the new kernel makes categorical isolation a capability-scoping detail. The pain is real *now* and the kernel erases it later.

---

## 5. Decision summary

- **Chosen:** separate privileged helper process per worker (hard boundary), holding the only elevated hardware handle, accepting only typed kabi operations, parsing no untrusted input.
- **Why:** the realistic attack is a memory-safety exploit in the client-facing parser; only process isolation categorically prevents it reaching hardware authority. It adds a *third* independent containment wall (attenuation + privilege-split + process-isolation).
- **Cost, and why it's acceptable:** an IPC hop on the *control* path only (claim-time, cold); the data/use path is direct (Part 3.8) and untouched, so performance priority #2 is not taxed where it matters.
- **Minimality constraint:** the helper is the smallest auditable unit — typed ops only, bounded to its subtree's attenuated authority, so a compromised worker degrades to "misuse its own bus," not "arbitrary hardware."
- **Kernel implication:** this is the clearest case for kernel Feature 7 (privilege as thread-capability), which makes this costly-on-Linux isolation a free capability-scoping detail on the future OS.

> The shape rhymes with every other decision in this design: the dangerous capability is held by the smallest, most-isolated, most-audited unit, and the large exposed surface holds no power. Here that principle is enforced by the strongest available Linux mechanism (process isolation) — and flagged as a place the future kernel does it better and cheaper.
