Phase 8 C (1/n): QEMU harness now preserves NVRAM across runs

Scoping Phase C (the MINT word) surfaced a real blocker: "mint one
Zuse, ever" needs the cert to survive reboots, but the qemu target
copied a fresh, pristine OVMF_VARS.fd on every invocation -- a
UEFI-NVRAM-based cert would never persist under this project's own
normal test workflow. Digging further, aarch64 had no persistent NVRAM
store at all (single -bios arg, no split VARS pflash like amd64/riscv64).

Fixed rather than switching storage substrates: amd64/riscv64 now only
copy the VARS template if the destination doesn't already exist, so
`clean` (which deletes the whole build tree) is the bleach step and a
bare `make qemu` preserves NVRAM -- matching the existing "always clean
before qemu" acceptance convention exactly. aarch64 restructured to
split CODE(ro)/VARS(rw) pflash drives matching the other two, with a
graceful fallback to the old -bios mode on hosts without split firmware.

Also resolves two design questions before any cert code: Zuse doesn't
need Milestone 6's CA (that's the capsule-signing chain, a separate
trust domain -- Zuse is a self-sovereign instance-local root of trust),
and flags that this session's own earlier vm_zuse_cert_install() storage
(16 bytes) is too small for a real Ed25519 keypair.

Verified: all three architectures boot clean to ok> with the new pflash
arrangement, Stadium conservation intact, no panics or guest errors.
Infrastructure-only -- no cert code yet. Documented in FABRIC-3.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
This commit is contained in:
Robert Allan James
2026-08-26 15:18:15 -04:00
co-authored by Claude Sonnet 5
parent 53e6c5709f
commit f223a31cec
7 changed files with 27186 additions and 4 deletions
+43
View File
@@ -432,6 +432,49 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
keypair needs to chain to the Milestone 6 offline root CA or is a self-sovereign
instance-local root of trust.
**Phase C scoping, 2026-08-26.** Three findings before any code:
1. **Resolved, not a real conflict: Zuse doesn't need Milestone 6's CA.** That CA chain is
specifically for *capsule/code signing* (root → snakeoil intermediate → per-capsule
Ed25519 signatures verified at capsule-load time) — a different trust domain from *user
identity*. The vision's own framing ("we mint one and only one Zuse user and blow a fuse
... the only way around is a new system") already implies Zuse's authority comes from
being the unique first-boot mint on *this instance*, not from an external chain.
**Decided: Zuse is a self-sovereign, instance-local root of trust**, keypair generated
on-device from real entropy (Phase A+B). Regular users, minted later via `MINT`, get
certs signed by *Zuse's* key, not the Milestone 6 CA — two independent PKI domains.
2. **A real gap in this session's own earlier work:** `vm_zuse_cert_install(vm, lo, hi)`
(the very first change this session made, before Phase A existed) only holds two
`uint64_t` (16 bytes) — sized against the old placeholder `ZUSE-CERT-LO`/`HI` FORTH-cell
design, not against what a real Ed25519 keypair needs (32-byte pubkey alone, well over
100 bytes for a full cert). Needs expanding before Phase C can store anything real.
3. **A genuine blocker, found by asking where the cert would actually live:** "mint once,
ever" requires surviving reboots, but `Makefile.starkernel`'s `qemu` target copied a
fresh, pristine `OVMF_VARS.fd` on *every* invocation (not just after `clean`) — so a
UEFI-NVRAM-based cert (the real-hardware-compatible option, and this codebase already has
a live `SetVariable`/`GetVariable` precedent via `SF_VAR_REBOOT_TRIES`/`SF_VAR_BOOT_ARGS`)
would never actually persist under this project's own normal test workflow. Digging
further: aarch64's `qemu` recipe had no persistent NVRAM store *at all* — a single
combined `-bios $AAVMF_CODE` argument, no separate writable VARS pflash drive like
amd64/riscv64 have. **Decided (on request): fix the harness rather than switch
substrates.** amd64/riscv64: the VARS-template copy is now conditional on the destination
not already existing, so `clean` (which deletes the whole `build/$(ARCH)/kernel` tree,
`OVMF_VARS.fd`/`RISCV_VARS.fd` included) is the bleach step, and a bare `make qemu` now
preserves NVRAM across runs — exactly matching the existing "always pass `clean` before
`qemu`" acceptance convention, no new bleach script needed. aarch64: restructured to
split CODE(ro)/VARS(rw) pflash drives matching the other two (host has
`/usr/share/AAVMF/AAVMF_VARS.fd` alongside the existing `AAVMF_CODE.fd`), with a
graceful fallback to the old single-`-bios` mode (and a console note) on a host that only
has non-split firmware packaged, so this doesn't regress environments without one.
**Verified live:** all three architectures still boot clean to `ok>` with the new
pflash arrangement, Stadium conservation intact, no panics or guest errors — this is
infrastructure-only (no cert code yet), so a plain boot-regression check is the right
verification tier.
**Still open:** the actual cert struct (expanding past the 16-byte placeholder), the
first-boot mint-vs-already-minted boot sequence using `SetVariable`/`GetVariable`, and the
`MINT` word itself.
### From FABRIC-2.md §X, Milestone 5 — Console/VM key-match binding
- [ ] Settle the still-open question: reuse `ACL-PIN`/`acl_allow` directly, or build a
+23 -3
View File
@@ -737,7 +737,9 @@ ifeq ($(ARCH),amd64)
-o $(QEMU_ISO) $(BUILD_DIR)/iso 2>&1
@if [ ! -f $(OVMF_CODE) ]; then echo "Error: $(OVMF_CODE) not found"; exit 1; fi
@if [ ! -f $(OVMF_VARS_RO) ]; then echo "Error: $(OVMF_VARS_RO) not found"; exit 1; fi
@cp $(OVMF_VARS_RO) $(BUILD_DIR)/OVMF_VARS.fd
@if [ ! -f $(BUILD_DIR)/OVMF_VARS.fd ]; then \
cp $(OVMF_VARS_RO) $(BUILD_DIR)/OVMF_VARS.fd; \
fi
@mkdir -p $(QEMU_LOG_DIR) $(DOE_RUNS_DIR) $(DOE_LATEST_DIR)
@TS=$$(date +%Y%m%d-%H%M%S); \
SESSION=$(QEMU_LOG_DIR)/$$TS; \
@@ -798,14 +800,26 @@ else ifeq ($(ARCH),aarch64)
@mkdir -p $(QEMU_LOG_DIR) $(DOE_RUNS_DIR) $(DOE_LATEST_DIR)
@if [ -f /usr/share/AAVMF/AAVMF_CODE.fd ]; then \
AAVMF_CODE=/usr/share/AAVMF/AAVMF_CODE.fd; \
AAVMF_VARS_RO=/usr/share/AAVMF/AAVMF_VARS.fd; \
elif [ -f /usr/share/qemu-efi-aarch64/QEMU_EFI.fd ]; then \
AAVMF_CODE=/usr/share/qemu-efi-aarch64/QEMU_EFI.fd; \
AAVMF_VARS_RO=/usr/share/qemu-efi-aarch64/vars-template-pflash.raw; \
elif [ -f /usr/share/qemu/edk2-aarch64-code.fd ]; then \
AAVMF_CODE=/usr/share/qemu/edk2-aarch64-code.fd; \
AAVMF_VARS_RO=/usr/share/qemu/edk2-aarch64-vars.fd; \
else \
echo "Error: aarch64 UEFI firmware not found. Install qemu-efi-aarch64."; \
exit 1; \
fi; \
if [ -f "$$AAVMF_VARS_RO" ]; then \
if [ ! -f $(BUILD_DIR)/AAVMF_VARS.fd ]; then \
cp $$AAVMF_VARS_RO $(BUILD_DIR)/AAVMF_VARS.fd; \
fi; \
AAVMF_VARS=$(BUILD_DIR)/AAVMF_VARS.fd; \
else \
echo " (no split VARS template found for this firmware -- NVRAM will not persist across runs)"; \
AAVMF_VARS=""; \
fi; \
TS=$$(date +%Y%m%d-%H%M%S); \
SESSION=$(QEMU_LOG_DIR)/$$TS; \
mkdir -p $$SESSION/aarch64; \
@@ -824,7 +838,11 @@ else ifeq ($(ARCH),aarch64)
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-bios $$AAVMF_CODE \
$$(if [ -n "$$AAVMF_VARS" ]; then \
echo "-drive if=pflash,format=raw,readonly=on,file=$$AAVMF_CODE -drive if=pflash,format=raw,file=$$AAVMF_VARS"; \
else \
echo "-bios $$AAVMF_CODE"; \
fi) \
-drive if=virtio,format=raw,file=$(QEMU_ISO),media=cdrom \
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
-device virtio-blk-pci,drive=artdisk \
@@ -865,7 +883,9 @@ else ifeq ($(ARCH),riscv64)
fi; \
EDK2_RISCV_VARS=""; \
if [ -f /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd ]; then \
cp /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd $(BUILD_DIR)/RISCV_VARS.fd; \
if [ ! -f $(BUILD_DIR)/RISCV_VARS.fd ]; then \
cp /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd $(BUILD_DIR)/RISCV_VARS.fd; \
fi; \
EDK2_RISCV_VARS=$(BUILD_DIR)/RISCV_VARS.fd; \
fi; \
DISK=$(BUILD_DIR)/starkernel_rv64.img; \
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-26T18:43:12Z -->
<!-- Generated by mkcapsule --manifest 2026-08-26T19:15:03Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff