Phase 8: zuse cert storage moved out of the dictionary (fuse-blow install)

Found that a pinned CONSTANT is not actually tamper-proof: ACL-PIN only
blocks redefinition, not a >BODY-then-store on the word's existing data
field. Moves the Zuse cert value into C-only VM struct fields
(zuse_cert_lo/hi + zuse_cert_installed fuse bit) with a one-time
vm_zuse_cert_install() and read-only ZUSE-CERT-LO@/HI@/INSTALLED? FORTH
accessors, closing the tamper path structurally instead of by convention.
Deletes the now-insecure ZUSE-CERT-LO/HI CONSTANT words from zuse.4th.

vm_zuse_cert_install() has no caller yet -- the real mint flow (Milestone
6 CA, the MINT word) is still open; this is storage + accessors only, not
a stand-in mint. Documented in FABRIC-3.md. Verified: hosted build clean,
mkcapsule --lint clean (31/31), clean boot to ok> on amd64/aarch64/riscv64
with Stadium conservation intact and no panics.

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 13:58:22 -04:00
co-authored by Claude Sonnet 5
parent 4dd1321ea4
commit 6f5605d479
14 changed files with 27217 additions and 10 deletions
+30
View File
@@ -316,6 +316,36 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
**Still open, not attempted:** the mint-then-pin boot-sequence fix itself, the `MINT` word,
and the Zuse recovery path.
**Correction, supersedes the "reuse `acl_pinned`" resolution above (2026-08-26): a pinned
`CONSTANT` is not actually tamper-proof.** `ACL-PIN`/`acl_pinned` only guards against
*redefinition* — `vm_create_word()`'s pin check blocks a second `: ZUSE-CERT-LO ... ;`, but
nothing stops `' ZUSE-CERT-LO >BODY !` from overwriting the same word's data field in place.
A `CONSTANT`'s value lives in its data field, so the earlier design left the cert mutable
from FORTH despite being "pinned." Found while starting the mint-then-pin boot-sequence fix
itself; fixing that gap came first since building a real mint flow on top of a tamperable
store would just re-open the hole later.
**Fixed (2026-08-26): moved cert storage out of the dictionary entirely.** New
`VM` struct fields (`include/vm.h`): `zuse_cert_lo`/`zuse_cert_hi` (the cert value) +
`zuse_cert_installed` (one-time fuse bit). New `vm_zuse_cert_install(vm, lo, hi)`
(`src/vm.c`) — C-only, no FORTH word wraps it, returns `-1` on a second call rather than
silently re-installing (a second call is a caller bug, not a runtime condition to recover
from). No FORTH store word exists or should exist for these fields, closing the `>BODY`
path structurally rather than by convention. Three new read-only C primitives
(`src/word_source/starforth_words.c`, same shape as the existing `HEARTBEAT-TICKS@`):
`ZUSE-CERT-LO@`, `ZUSE-CERT-HI@`, `ZUSE-CERT-INSTALLED?`. `capsules/zuse.4th`'s old
`ZUSE-CERT-LO`/`HI` `CONSTANT` words (and `ACL-ZUSE-BOOT`'s two now-pointless `ACL-PIN`
calls on them) deleted outright rather than left as dead/insecure scaffolding — `mkcapsule
--lint` clean (31/31) after the edit. `vm_zuse_cert_install()` has no caller yet: the real
mint flow still doesn't exist (Milestone 6 CA + the `MINT` word are both still open), and
calling it with a placeholder value would just be a stub wearing the shape of a fix — so
this stays an honest, complete slice (storage + read accessors) with the actual mint-then-
pin sequence still explicitly open, not faked. **Verified:** hosted `make` build clean,
zero warnings; clean boot to `ok>` on all three architectures (amd64/aarch64/riscv64),
Stadium conservation intact (43691/21845/65536) on all three, no panics or guest errors.
ACL is opt-in (`init.4th`'s `S" ACL.4th" EXEC` commented out by default) so the new words
weren't exercised live from the REPL this pass — compile/lint/boot verification only.
### 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
+5 -5
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-26T12:02:09Z -->
<!-- Generated by mkcapsule --manifest 2026-08-26T17:54:43Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -38,7 +38,7 @@
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` |
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` |
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` |
| `zuse.4th` | 4016, 4017, 4018 | `0x12f38ec782434a77` |
| `zuse.4th` | 4016, 4017, 4018 | `0x097456615c8d9173` |
## Block Map (sorted by LBN)
@@ -103,9 +103,9 @@
| 4006 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4007 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4015 | `ACL.4th` | `0xd781d22148ff171d` | ok |
| 4016 | `zuse.4th` | `0x12f38ec782434a77` | ok |
| 4017 | `zuse.4th` | `0x12f38ec782434a77` | ok |
| 4018 | `zuse.4th` | `0x12f38ec782434a77` | ok |
| 4016 | `zuse.4th` | `0x097456615c8d9173` | ok |
| 4017 | `zuse.4th` | `0x097456615c8d9173` | ok |
| 4018 | `zuse.4th` | `0x097456615c8d9173` | ok |
| 4050 | `lib.4th` | `0x1c77d7c9562a5b62` | ok |
| 4055 | `common:msg.4th` | `0x850a0382344ea6c4` | ok |
| 4060 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok |
+6 -5
View File
@@ -5,19 +5,20 @@ Block 4016
( Loaded by ACL.4th; must not load before ACL.4th. )
( FUTURE: Replace with thumbdrive Ed25519 PKI. )
( HUMAN-REVIEW: capsule hash = root of superuser trust. )
0 CONSTANT ZUSE-CERT-LO
0 CONSTANT ZUSE-CERT-HI
( Cert value lives in C-only VM fields (vm_zuse_cert_install),
( NOT a dictionary CONSTANT: ACL-PIN only blocks redefinition,
( not a >BODY-then-store, so a pinned CONSTANT is not actually
( tamper-proof. Read with ZUSE-CERT-LO@ / ZUSE-CERT-HI@ / )
( ZUSE-CERT-INSTALLED? -- all C primitives, all read-only. )
Block 4017
( ACL-ZUSE-BOOT ( -- ) )
( Authenticates zuse session (sets vm->zuse_session=1)
( via C primitive) and pins zuse capsule words. )
( via C primitive) and pins itself against redefinition.)
( ZUSE-AUTHENTICATE is C-only; no FORTH word grants )
( god-mode except through this boot sequence. )
: ACL-ZUSE-BOOT ( -- )
ZUSE-AUTHENTICATE
['] ZUSE-CERT-LO ACL-PIN
['] ZUSE-CERT-HI ACL-PIN
LOG-INFO" zuse: activated"
['] ACL-ZUSE-BOOT ACL-PIN ;
BIN
View File
Binary file not shown.
+10
View File
@@ -392,6 +392,11 @@ typedef struct VM
uint8_t emergency_console; /**< 1 = fault handler active; bypasses all ACL checks (C-only write) */
uint8_t zuse_session; /**< 1 = zuse authenticated at console; shows zuse)ok> prompt */
uint8_t acl_skip; /**< 1 = skip all ACL hooks (Ananke enforcement VM; prevents recursion) */
uint8_t zuse_cert_installed; /**< 1 = zuse_cert_lo/hi hold a real minted cert (one-time fuse) */
uint64_t zuse_cert_lo; /**< Zuse cert value, low half. C-only write via vm_zuse_cert_install(). */
uint64_t zuse_cert_hi; /**< Zuse cert value, high half. No FORTH word can write these fields --
* deliberately kept out of the dictionary so ACL-PIN's redefinition-only
* guarantee can't be bypassed via >BODY on a CONSTANT (see FABRIC-3.md). */
/** @} */
/** @name Dictionary Management
@@ -626,6 +631,11 @@ cell_t* vm_dictionary_get_data_field(DictEntry* entry);
void vm_compile_word(VM* vm, DictEntry* entry);
/* Zuse cert one-time install (blows the fuse). Returns 0 on success, -1 if
* already installed -- a second call is a caller bug, not a runtime error to
* recover from silently. C-only: no FORTH word wraps this. */
int vm_zuse_cert_install(VM* vm, uint64_t lo, uint64_t hi);
/* Memory management */
void* vm_allot(VM* vm, size_t bytes);
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
+25
View File
@@ -117,6 +117,31 @@ void vm_set_base(VM* vm, unsigned b)
/* vm_init and vm_cleanup moved to vm_bootstrap.c */
/* vm_tick* and heartbeat functions moved to vm_time.c */
/**
* @brief One-time write of the Zuse cert value (blows the fuse).
*
* Deliberately not backed by a dictionary CONSTANT: ACL-PIN only blocks
* redefinition (vm_create_word shadowing), not a >BODY-then-store on the
* word's data field, so a pinned CONSTANT is not actually immutable against
* FORTH-level tampering. Keeping the value in a VM struct field with no
* corresponding FORTH store word closes that path entirely -- see
* FABRIC-3.md's Milestone 4 mint-then-pin writeup for the finding.
*
* @param vm VM instance.
* @param lo Cert value, low half.
* @param hi Cert value, high half.
* @return 0 on success; -1 if already installed (fuse already blown).
*/
int vm_zuse_cert_install(VM* vm, uint64_t lo, uint64_t hi)
{
if (!vm) return -1;
if (vm->zuse_cert_installed) return -1;
vm->zuse_cert_lo = lo;
vm->zuse_cert_hi = hi;
vm->zuse_cert_installed = 1;
return 0;
}
/* ====================== Parser / number ======================= */
/**
+26
View File
@@ -793,6 +793,26 @@ static void starforth_word_zuse_authenticate(VM *vm)
vm->zuse_session = 1;
}
/* ZUSE-CERT-LO@ ( -- lo ) Read-only: no FORTH store word exists or should
* exist -- the cert is written exactly once, in C, via vm_zuse_cert_install(). */
static void starforth_word_zuse_cert_lo_fetch(VM *vm)
{
vm_push(vm, (cell_t)vm->zuse_cert_lo);
}
/* ZUSE-CERT-HI@ ( -- hi ) See ZUSE-CERT-LO@. */
static void starforth_word_zuse_cert_hi_fetch(VM *vm)
{
vm_push(vm, (cell_t)vm->zuse_cert_hi);
}
/* ZUSE-CERT-INSTALLED? ( -- flag ) -1 if the one-time cert fuse has been
* blown (vm_zuse_cert_install() has succeeded), 0 otherwise. */
static void starforth_word_zuse_cert_installed_query(VM *vm)
{
vm_push(vm, vm->zuse_cert_installed ? -1 : 0);
}
/**
* @brief Read-only accessor for the canonical heartbeat tick counter
*
@@ -826,6 +846,9 @@ void register_starforth_words(VM* vm)
register_word(vm, "RANDOM", starforth_word_random);
register_word(vm, "WAIT", starforth_word_wait);
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
register_word(vm, "ZUSE-CERT-LO@", starforth_word_zuse_cert_lo_fetch);
register_word(vm, "ZUSE-CERT-HI@", starforth_word_zuse_cert_hi_fetch);
register_word(vm, "ZUSE-CERT-INSTALLED?", starforth_word_zuse_cert_installed_query);
register_word(vm, "HEARTBEAT-TICKS@", starforth_word_heartbeat_ticks);
vm_bootstrap_root_vocabulary(vm, "STARFORTH");
@@ -844,6 +867,9 @@ void register_starforth_words(VM* vm)
register_word(vm, "RANDOM", starforth_word_random);
register_word(vm, "WAIT", starforth_word_wait);
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
register_word(vm, "ZUSE-CERT-LO@", starforth_word_zuse_cert_lo_fetch);
register_word(vm, "ZUSE-CERT-HI@", starforth_word_zuse_cert_hi_fetch);
register_word(vm, "ZUSE-CERT-INSTALLED?", starforth_word_zuse_cert_installed_query);
register_word(vm, "HEARTBEAT-TICKS@", starforth_word_heartbeat_ticks);
vocabulary_word_forth(vm);