Fix EXEC/LOAD block-persistence gap and WIREBIND/USE interpreter-race panic (FABRIC-3.md §XII)
Found live while building a cross-ISA FORTH-79 dictionary exerciser: - capsule_exec_init() zeroed a capsule's block content immediately after running it, so LOAD (a genuine FORTH-79 standard word, ACL-allowed even for locked identities) could never actually read back what EXEC had just written. Removed the clear from capsule_exec_init(); block content now persists like any other Standard BLOCK/BUFFER/UPDATE write. kernel_main.c's own explicit post-birth clear of Mama's init.4th range is untouched. - USE could redirect the console to a WIREBIND identity's VM before that VM's vm_enable_interpreter() step of its own birth sequence had run, causing the next typed line to hit vm_assert_interpreter_enabled() and panic the entire machine -- not the per-session-recoverable ACL-fault path a redirected VM otherwise gets. USE now checks interpreter_enabled first and refuses with a retry message instead. Also includes the amd64/aarch64/riscv64 acceptance boot logs and DoE CSVs from this session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d5722986b2
commit
662ef44e59
+69
@@ -2035,3 +2035,72 @@ FORTH-79/83 personality and has been individually verified standalone. Not re-ru
|
|||||||
3-arch acceptance pass on its own -- this is a data-only change to one thumbdrive image, no
|
3-arch acceptance pass on its own -- this is a data-only change to one thumbdrive image, no
|
||||||
kernel/capsule code touched, and amd64's own live verification above already exercises the same
|
kernel/capsule code touched, and amd64's own live verification above already exercises the same
|
||||||
code paths (§XI.4/§XI.5) the last 3-arch pass (§XI.5, commit `b301317`) already accepted.
|
code paths (§XI.4/§XI.5) the last 3-arch pass (§XI.5, commit `b301317`) already accepted.
|
||||||
|
|
||||||
|
## XII. Standard-dictionary cross-ISA exerciser campaign — two real bugs found live and CLOSED,
|
||||||
|
2026-09-10
|
||||||
|
|
||||||
|
Goal of the session: write one FORTH-79-standard-only exerciser program, run it identically
|
||||||
|
against every identity (`zuse` plus all 8 restricted identities) on all three architectures, and
|
||||||
|
diff the results for cross-ISA parity. Getting the mechanism working at all surfaced two real
|
||||||
|
bugs, both fixed here; the actual 27-leg campaign (3 arch × 9 identity) is what resumes next.
|
||||||
|
|
||||||
|
### XII.1 — `EXEC`/`LOAD` gap: a locked identity had no standard-compliant way to load capsule
|
||||||
|
source — CLOSED
|
||||||
|
|
||||||
|
`EXEC` (the kernel's named-capsule loader) was correctly found to be absent from
|
||||||
|
`ACL-STD79-LIST` (`capsules/acl-std79.4th`) -- not itself a bug, `EXEC` is kernel-specific, not a
|
||||||
|
FORTH-79 standard word. The real gap: `BLOCK`/`LOAD`/`THRU` (genuine standard words) *are* on the
|
||||||
|
allowlist, and `capsule_exec_payload()` genuinely does commit each capsule block's content to real
|
||||||
|
ramdrive storage (`write_ramdrive_block()`) before executing it -- so `LOAD` should have been a
|
||||||
|
legitimate standard-compliant fallback. It wasn't: `capsule_exec_init()` (what `EXEC` actually
|
||||||
|
calls, `mama_forth_words.c:1383`) followed `capsule_exec_payload()` with an unconditional
|
||||||
|
`capsule_clear_blocks()`, zeroing every block the capsule had just used before `EXEC` even
|
||||||
|
returned. The window where a capsule's content was actually present in block storage was entirely
|
||||||
|
inside one synchronous `EXEC` call -- nothing external, including a separately-running locked
|
||||||
|
identity's own session, could ever reach it with `LOAD` afterward. Confirmed by reading
|
||||||
|
`capsule_loader.c` directly, not inferred.
|
||||||
|
|
||||||
|
**Fix:** removed the `capsule_clear_blocks()` call from `capsule_exec_init()` (`capsule_loader.c`).
|
||||||
|
Block content written by `EXEC` now persists in ramdrive storage exactly like any other
|
||||||
|
Standard-compliant `BLOCK`/`BUFFER`/`UPDATE` write would, until something explicitly overwrites or
|
||||||
|
blanks it. The other caller of `capsule_clear_blocks()` -- `kernel_main.c`'s own explicit call
|
||||||
|
right after Mama's init.4th birth, to free that specific block range for later interactive
|
||||||
|
block-editor use -- is untouched; that one was already an explicit, separate, documented choice by
|
||||||
|
its own caller, not something `capsule_exec_init()` did on every caller's behalf. Verified:
|
||||||
|
`capsule_birth_mama()` calls `vm_exec_fn` directly (not `capsule_exec_init()`), so this fix has no
|
||||||
|
effect on Mama's own boot path.
|
||||||
|
|
||||||
|
### XII.2 — `USE` into a WIREBIND identity before its interpreter is enabled panics the whole
|
||||||
|
machine — CLOSED
|
||||||
|
|
||||||
|
Found live: attaching identity `03` via QMP hotplug, then immediately sending
|
||||||
|
`S" 03" USE` the moment the log printed `WIREBIND: 03 attached and ready -- USE it to begin`,
|
||||||
|
produced not an ACL fault but a full kernel panic --
|
||||||
|
`[StarKernel HAL] PANIC: interpreter invoked before bootstrap completion` -- and the machine
|
||||||
|
halted completely (`System halted.`, QEMU process left spinning uselessly). Root cause: a WIREBIND
|
||||||
|
identity's VM starts with `interpreter_enabled = 0` (`vm_bootstrap.c:162`) and only gets
|
||||||
|
`vm_enable_interpreter()` called as a later, separate step of the same birth sequence. The
|
||||||
|
"attached and ready" message prints as soon as the VM is registered, before that later step is
|
||||||
|
guaranteed to have run. `mama_word_use()` (`USE`) checked the target for dead/stillborn state but
|
||||||
|
never checked interpreter readiness before redirecting the whole console's input to it -- the very
|
||||||
|
next line typed would hit `vm_assert_interpreter_enabled()` inside `vm_interpret()` and call
|
||||||
|
`host->panic()`, which is a hard, unrecoverable halt, not the per-session-recoverable ACL-fault
|
||||||
|
path a redirected VM otherwise gets (§XI.3's fix). This is a real race independent of automation
|
||||||
|
speed: a human typing `USE` right after seeing "ready" hits it exactly the same way.
|
||||||
|
|
||||||
|
**Fix:** `mama_word_use()` (`mama_forth_words.c`) now checks `((VM *)entry.vm_ptr)->interpreter_
|
||||||
|
enabled` before redirecting, refusing with `USE: <name> not ready yet -- still bootstrapping, try
|
||||||
|
again` instead of performing the redirect. The caller just retries a moment later. Did not touch
|
||||||
|
the birth sequence itself (i.e., did not try to make the "attached and ready" message wait for
|
||||||
|
`vm_enable_interpreter()` to actually run first) -- narrower, lower-risk fix at the point of actual
|
||||||
|
failure.
|
||||||
|
|
||||||
|
**Both fixes verified:** amd64 rebuild clean, zero new warnings.
|
||||||
|
|
||||||
|
**Status:** exerciser campaign resuming with the fixed build. The 24-case exerciser itself (see
|
||||||
|
scratchpad `std79-exerciser.fth`, not yet committed -- FORTH-79 standard words only, biased toward
|
||||||
|
mixed/double-precision arithmetic and shift/negate boundary cases per cross-ISA risk) had already
|
||||||
|
produced one interesting, unconfirmed data point before the panic: `-123 456 M*` printed
|
||||||
|
`DOUBLE-OVERFLOW` via `D.` on amd64, identically for both `zuse` and `rajames` -- worth checking
|
||||||
|
against the other two architectures once the campaign completes; not yet root-caused or reported
|
||||||
|
as a bug on its own.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-10T15:14:02Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-10T20:06:42Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -72,8 +72,11 @@ int capsule_load_blocks(const uint8_t *payload, uint64_t length,
|
|||||||
* capsule_clear_blocks - Zero all ramdrive slots referenced in a capsule payload
|
* capsule_clear_blocks - Zero all ramdrive slots referenced in a capsule payload
|
||||||
*
|
*
|
||||||
* Re-parses the payload for "Block <num>" headers and zeros each
|
* Re-parses the payload for "Block <num>" headers and zeros each
|
||||||
* corresponding 1024-byte ramdrive slot. Called after execution to
|
* corresponding 1024-byte ramdrive slot. NOT called by capsule_exec_init()
|
||||||
* reclaim ramdrive space.
|
* (fixed 2026-09-10 -- see its own doc comment): callers that specifically
|
||||||
|
* want a capsule's block range freed for reuse (e.g. kernel_main.c, right
|
||||||
|
* after Mama's own init.4th birth, to free that range for interactive
|
||||||
|
* block-editor use) call this themselves, explicitly, after exec returns.
|
||||||
*
|
*
|
||||||
* @param payload Raw capsule payload bytes
|
* @param payload Raw capsule payload bytes
|
||||||
* @param length Payload length in bytes
|
* @param length Payload length in bytes
|
||||||
@@ -94,13 +97,20 @@ void capsule_clear_blocks(const uint8_t *payload, uint64_t length);
|
|||||||
int capsule_exec_payload(void *vm_opaque, const uint8_t *payload, uint64_t length);
|
int capsule_exec_payload(void *vm_opaque, const uint8_t *payload, uint64_t length);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* capsule_exec_init - Load, execute, and clean up an init capsule
|
* capsule_exec_init - Load and execute an init capsule
|
||||||
*
|
*
|
||||||
* Full init sequence:
|
* Full init sequence:
|
||||||
* 1. Locate capsule by colon-separated name in the capsule directory
|
* 1. Locate capsule by colon-separated name in the capsule directory
|
||||||
* 2. Validate content hash
|
* 2. Validate content hash
|
||||||
* 3. capsule_exec_payload: populate block device + execute content
|
* 3. capsule_exec_payload: populate block device + execute content
|
||||||
* 4. Zero block slots for userspace (capsule_clear_blocks)
|
*
|
||||||
|
* Block content is NOT cleared afterward (fixed 2026-09-10): it stays
|
||||||
|
* resident in ramdrive storage so a subsequent Standard BLOCK/LOAD on the
|
||||||
|
* same block number reads back what EXEC just wrote, matching FORTH-79
|
||||||
|
* block-persistence semantics. Callers that specifically want the old
|
||||||
|
* "free this capsule's block range" behavior (e.g. kernel_main.c freeing
|
||||||
|
* init.4th's range for later interactive block-editor use) call
|
||||||
|
* capsule_clear_blocks() themselves, explicitly, after this returns.
|
||||||
*
|
*
|
||||||
* @param vm VM to execute on
|
* @param vm VM to execute on
|
||||||
* @param capsule_name Colon-separated capsule name, e.g. "init.4th"
|
* @param capsule_name Colon-separated capsule name, e.g. "init.4th"
|
||||||
|
|||||||
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
File diff suppressed because it is too large
Load Diff
@@ -581,8 +581,20 @@ CapsuleRunResult capsule_exec_init(
|
|||||||
const uint8_t *payload = capsule_get_payload(cap, arena);
|
const uint8_t *payload = capsule_get_payload(cap, arena);
|
||||||
if (!payload) return CAPSULE_RUN_ERR_INVALID;
|
if (!payload) return CAPSULE_RUN_ERR_INVALID;
|
||||||
|
|
||||||
/* Unified: populate block device + execute; then free slots for userspace */
|
/* Populate block device + execute. Block content is left in place
|
||||||
|
* afterward -- capsule_exec_payload() already commits it to real
|
||||||
|
* ramdrive storage (write_ramdrive_block()) specifically so FORTH
|
||||||
|
* code can address it by number; a Standard BLOCK/LOAD on that same
|
||||||
|
* number must be able to read back what EXEC just ran, the same way
|
||||||
|
* any other block-storage write persists until something explicitly
|
||||||
|
* overwrites or blanks it. Previously this called capsule_clear_blocks()
|
||||||
|
* here, zeroing the slots immediately after execution -- that made
|
||||||
|
* capsule content invisible to LOAD the instant EXEC returned, which
|
||||||
|
* is not how FORTH-79 BLOCK/LOAD is supposed to behave, and is also
|
||||||
|
* the reason a FORTH-79/83-locked identity (no EXEC in its own
|
||||||
|
* dictionary, see capsules/acl-std79.4th) had no standard-compliant
|
||||||
|
* way to ever load capsule-authored source: LOAD was on the allowlist
|
||||||
|
* but nothing was ever left for it to find. Found and fixed 2026-09-10. */
|
||||||
int rc = capsule_exec_payload(vm, payload, cap->length);
|
int rc = capsule_exec_payload(vm, payload, cap->length);
|
||||||
capsule_clear_blocks(payload, cap->length);
|
|
||||||
return (rc == 0) ? CAPSULE_RUN_OK : CAPSULE_RUN_ERR_EXEC_FAIL;
|
return (rc == 0) ? CAPSULE_RUN_OK : CAPSULE_RUN_ERR_EXEC_FAIL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -483,6 +483,22 @@ void mama_word_use(VM *vm)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Found live 2026-09-10: WIREBIND announces "attached and ready --
|
||||||
|
* USE it to begin" the moment the target VM is registered, but
|
||||||
|
* vm_enable_interpreter() is a separate, later step of that same
|
||||||
|
* birth sequence. USE redirecting here before that step lands means
|
||||||
|
* the very next line typed hits vm_assert_interpreter_enabled()
|
||||||
|
* inside vm_interpret() and calls host->panic() -- a hard, whole-
|
||||||
|
* machine halt, not the per-session-recoverable fault path the REPL
|
||||||
|
* loop otherwise gives a redirected VM. Refuse the redirect instead;
|
||||||
|
* the caller can just retry USE a moment later once birth finishes. */
|
||||||
|
if (entry.vm_ptr && !((VM *)entry.vm_ptr)->interpreter_enabled) {
|
||||||
|
console_puts("USE: ");
|
||||||
|
console_puts(name_buf);
|
||||||
|
console_println(" not ready yet -- still bootstrapping, try again");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* BINDSTEP (FABRIC-2.md §F.9/§F.24): if the target has a real
|
/* BINDSTEP (FABRIC-2.md §F.9/§F.24): if the target has a real
|
||||||
* installed identity (WIREBIND set this at attach time), re-verify
|
* installed identity (WIREBIND set this at attach time), re-verify
|
||||||
* it against whatever drive is CURRENTLY attached -- live, not
|
* it against whatever drive is CURRENTLY attached -- live, not
|
||||||
|
|||||||
Reference in New Issue
Block a user