ADR-083 — B1 pinned QEMU: inject-nmi → taken el0: serror (Verified opt-in)
- Status: Accepted (implementation + evidence). Sponsor B1 (ADR-081/082) met via pinned QEMU build (not upstream merge). Dated proof: QMP
inject-nmisucceeds and guest serial showsel0: serrorunder EXPECT. Stock distro QEMU path unchanged (park Verified). Umbrella “EL0 isolated” stays non-claim. - Date: 2026-09-19
- Follow-on (2026-09-26): ADR-090 (sponsor D1/D5) made
qemu-nmi-pinunconditional (repo-variable gate removed, job renamed “QEMU NMI pin smoke (taken SError)”). This reverses §6’s “do not force the compile on every PR”. It is part of the umbrella’s taken-SError evidence class together with B2-P. - Tracks #125. CloudAgent HELD — agent-box proof; amd64 workstation preferred for heavy build but offline this session (
<machine-id>intent). Do not self-merge (ADR-002).
Context
ADR-082 found no upstream/packaged QEMU pin: virt through master lacks TYPE_NMI→async SError. DSO (2026-09-19) preferred a pinned custom QEMU over waiting upstream. Draft sketch lived under research/qemu-nmi/.
Decision
- Pin QEMU v10.0.0 (
7c949c53e936aa3a658d84ab53bae5cadaa5d59c) + reviewed local patchresearch/qemu-nmi/0001-hw-arm-virt-TYPE_NMI-raise-SError.patch. - Patch semantics (honest): add TCG
EXCP_SERROR/CPU_INTERRUPT_SERROR; virt implementsTYPE_NMIsoinject-nmiraises that line. Masked byPSTATE.A. Not FEAT_NMI / FIQ / BRK. - Install prefix:
tools/qemu-nmi/via./scripts/build-qemu-nmi.sh(bin/share gitignored). - Opt-in smoke:
CTOS_QEMU=…/qemu-system-aarch64+CTOS_REQUIRE_TAKEN_SERROR=1fail-closed requires bareel0: serror. Default (unset) still requiresel0: serror-park. - Host inject timing:
qemu-serial-inject.pyissues QMPstop→inject-nmi→contonel0: serror-armso TCG cannot race past the A-clear window. - CI/Docker: default stays distro QEMU. Dockerfile
ARG CTOS_BUILD_QEMU_NMI=0opt-in. GHA job snippet in docs/dev/qemu-nmi-pin-ci.md (OAuth push lacksworkflowscope this session — paste when available). Do not force multi-minute QEMU compile on every PR. - Still non-claims: umbrella EL0 isolated; never yank
_start; park remains Verified on stock QEMU.
Evidence (2026-09-19 CEST, agent-box)
| Probe | Result |
|---|---|
CTOS_QEMU=tools/qemu-nmi/bin/… ./scripts/qemu-nmi-probe.py | inject-nmi ok on virt + cortex-a76 / a57 / GICv3 / virt-on / secure / max |
Stock qemu-system-aarch64 10.0.13 probe | still machine does not provide NMIs |
CTOS_QEMU=… CTOS_REQUIRE_TAKEN_SERROR=1 ./scripts/qemu-smoke.sh | el0: serror present; qemu-smoke: ok |
| Stock inject (no pin) | inject-nmi error + el0: serror-park |
Machine tag: (agent-box); (amd64 workstation) intent only (CloudAgent HELD / arm64 workstation offline).
Honesty
Say: “B1 met with a pinned QEMU build; taken el0: serror Verified under opt-in CTOS_QEMU + CTOS_REQUIRE_TAKEN_SERROR=1; stock QEMU still parks.” Do not say taken is Verified on distro QEMU, or that FEAT_NMI is the path, or “EL0 isolated.”
Consequences
- Docs: this ADR; ADR-082/081/053/045 cross-links; honesty ledger; SUMMARY; threat-model bump;
research/qemu-nmi/+tools/qemu-nmi/PIN.txt. - Smoke/scripts:
CTOS_QEMUhonored; taken gate opt-in; stock park preserved. - Dockerfile:
CTOS_BUILD_QEMU_NMIARG. GHA: see docs/dev/qemu-nmi-pin-ci.md (workflow file unchanged this PR).