Correct FABRIC-3.7.md: the original elevation-entrypoint bug diagnosis was wrong
Found while starting Part A's implementation, before any code was written
against the original design: reading the actual deleted SEND-ELEVATE-REQUEST
source (git show 3e201c8^:capsules/common/messaging.4th, block 5040) shows
it copied the target word's name as literal character bytes into a scratch
buffer, building "S" <name-text>" <pk0> <pk1> <pk2> <pk3> ELEVATE-GRANT"
entirely in the sending VM's own memory, then sent that finished string --
never a raw address -- to Hera. waddr/wu never crossed the VM boundary as
numbers anywhere in this flow. The original write-up reasoned from
ELEVATE-GRANT's own signature alone, without first reading how the caller
actually built its message.
Corrected in place, wrong original text kept struck-through for
traceability rather than deleted, per this series' own convention.
Net effect: Part A (the buffer/message redesign) is not needed -- the
original mechanism was already safe. Part B (capsules/zuse.4th, already
committed and three-arch verified this session, 089ab21) stands on its
own, unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
089ab2160e
commit
81049da268
+81
-14
@@ -1,6 +1,31 @@
|
||||
# FABRIC-3.7.md — Phase 8 PKI: the elevation entrypoint, without the pointer-confusion hole
|
||||
# FABRIC-3.7.md — Phase 8 PKI: the elevation entrypoint
|
||||
|
||||
**Status: OPEN — design only, no code written or authorized.** Successor to
|
||||
**Status: OPEN — design only, no code written or authorized.**
|
||||
|
||||
**CORRECTION (2026-09-22, before any code was written against this document): §2's central
|
||||
claim — that the old `SEND-ELEVATE-REQUEST` passed a raw cross-VM address into `ELEVATE-GRANT`'s
|
||||
`waddr` — is wrong.** Found while starting Part A's implementation: reading the actual deleted
|
||||
source (`git show 3e201c8^:capsules/common/messaging.4th`, block 5040) shows
|
||||
`SEND-ELEVATE-REQUEST` copied the target word's **name as literal character bytes** (via
|
||||
`ELEVATE-REQ-APPEND`'s `CMOVE`) into a scratch buffer, building the text `S" <name-text>" <pk0>
|
||||
<pk1> <pk2> <pk3> ELEVATE-GRANT`, and sent *that whole string* to Hera. When Hera's own
|
||||
interpreter runs `S" <name-text>"`, it allocates a fresh string **in Hera's own memory** and
|
||||
pushes Hera's own valid address — no numeric cross-VM address ever appears anywhere in this
|
||||
flow. §2 was written from `ELEVATE-GRANT`'s signature alone, assuming the caller forwarded a raw
|
||||
address, without first reading how the caller actually built its message. It didn't.
|
||||
|
||||
**What is still real, much narrower than originally claimed:** if a future caller ever spliced
|
||||
*attacker-influenced* text into the name field without checking for an embedded `"` character,
|
||||
that could break out of the `S" ... "` literal early and inject arbitrary FORTH source, executed
|
||||
with Hera's privilege. That's an input-validation discipline question for whoever writes the new
|
||||
caller (validate: no embedded `"`, or just always use a compile-time-fixed literal name, never a
|
||||
runtime-supplied one) — not an architectural cross-VM-memory defect requiring the buffer/message
|
||||
redesign §3 originally called for. **§3's proposed mechanism (Hera-side fixed receive buffer,
|
||||
kernel-constructed integer-literal-only command) is not needed** — the original text-copy
|
||||
design was already safe against the bug as actually diagnosed. Kept below, struck through, for
|
||||
traceability, per this series' own rule against silently rewriting a prior state.
|
||||
|
||||
Successor to
|
||||
`FABRIC-3.5.md`/`FABRIC-3.6.md` (both CLOSED/ARCHIVAL at `v2.1.0`) for exactly one topic: the
|
||||
Ed25519-challenge-response elevation entrypoint that `.claude/CLAUDE.md`'s ACL section names as
|
||||
Phase 8, the last open item in the word-level ACL system. This is a **new document**, not a
|
||||
@@ -28,7 +53,11 @@ Captain Bob's decision at the time (`FABRIC-3.6.md`'s own Phase 4 entry): leave
|
||||
don't patch a caller back in as part of that strip. Phase 8 builds its own entrypoint instead of
|
||||
resuming this one. **This document is that entrypoint's design.**
|
||||
|
||||
## 2. The security hole in the old mechanism — found before any code was written
|
||||
<details>
|
||||
<summary>Original §2/§3 (WRONG — see the correction at the top of this document; kept for
|
||||
traceability, not current design)</summary>
|
||||
|
||||
### 2. The security hole in the old mechanism — found before any code was written
|
||||
|
||||
`ELEVATE-GRANT`'s signature, unchanged since it was written:
|
||||
|
||||
@@ -57,7 +86,7 @@ at all — not forge a signature, not defeat `zuse_eligibility_is_member()`, jus
|
||||
This was never exploited (the entrypoint has had zero live callers since the file that called it
|
||||
was deleted), and is reported here as a design defect found by inspection, not a live incident.
|
||||
|
||||
## 3. The fix: never cross an address, only ever cross bytes
|
||||
### 3. The fix: never cross an address, only ever cross bytes
|
||||
|
||||
This project already solved the general version of this problem once, this same session
|
||||
(`FABRIC-3.6.md` tasks 3.8/3.9, the payload-aliasing fix): a kernel-Hermes message's payload
|
||||
@@ -97,24 +126,62 @@ receiving side. The class of bug (cross-address-space pointer confusion) becomes
|
||||
construction, the same way `payload_buf` made use-after-free impossible by construction rather
|
||||
than by careful lifetime tracking.
|
||||
|
||||
</details>
|
||||
|
||||
## 2 (corrected). What the old mechanism actually did, and the one real gap in it
|
||||
|
||||
Re-read from the actual deleted source (`git show 3e201c8^:capsules/common/messaging.4th`,
|
||||
blocks 5039–5040): `SEND-ELEVATE-REQUEST ( pk3 pk2 pk1 pk0 waddr wu -- )` used `waddr`/`wu` only
|
||||
to `CMOVE` the target word's **name bytes**, as text, into a scratch buffer
|
||||
(`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`) it owned — building the literal string `S"
|
||||
<name-text>" <pk0> <pk1> <pk2> <pk3> ELEVATE-GRANT` entirely in the *sending* VM's own memory.
|
||||
Only that finished string — not `waddr` itself — went to `KH-ELEVATE-SEND` and across to Hera.
|
||||
When Hera's interpreter runs `S" <name-text>"`, Hera's own `S"` allocates a fresh string **in
|
||||
Hera's own memory** and pushes Hera's own valid address. `waddr`/`wu` never cross the VM
|
||||
boundary as numbers at any point — only as copied character content. There is no cross-VM
|
||||
pointer dereference anywhere in this flow.
|
||||
|
||||
**The one real, much narrower gap:** the name text is spliced into `S" ... "` with no check for
|
||||
an embedded `"` character. If a future caller ever passed attacker-influenced text as the name
|
||||
(none ever did — the word had zero live callers), a `"` in the name would close the string
|
||||
literal early and let the rest of the name execute as raw FORTH source, with Hera's privilege.
|
||||
This is a caller-discipline / input-validation question, not an architectural defect: either
|
||||
always use a compile-time-fixed name literal at the call site (no runtime input, no risk at
|
||||
all), or validate for an embedded `"` before building the command if a name ever does need to
|
||||
come from something less trusted than the call site's own source code.
|
||||
|
||||
**Net effect on Phase 8 v1's scope:** Part A, as originally conceived in §3 above, is not
|
||||
needed. If a `SEND-ELEVATE-REQUEST` replacement is ever built, it can follow the original
|
||||
text-copy design as-is, with the one-line `"`-check added if and only if the name is ever
|
||||
runtime-supplied rather than a fixed literal. No kernel-Hermes/`repl.c` changes required. Part B
|
||||
(`capsules/zuse.4th`, gating `ZUSE-ELIGIBILITY-ADD`) stands on its own, independently verified,
|
||||
unaffected by this correction.
|
||||
|
||||
## 4. What Phase 8 actually needs to build
|
||||
|
||||
This document is the shape of the fix, not the implementation. Concretely, when Phase 8 starts:
|
||||
Corrected per §2's re-read above. Concretely, when Phase 8 next picks this up:
|
||||
|
||||
- A new `SkHermesMessage`-based sender (`KH-ELEVATE-SEND`'s replacement or a rewritten version of
|
||||
it) that packs `[pubkey: 32 bytes][name_len: 1 byte][name bytes]` into `payload_buf`, instead
|
||||
of taking a `waddr`/`wu` pair off the stack.
|
||||
- A receive-side fixed buffer in Hera's memory (parallel to `g_kh_elevate_buf`'s existing
|
||||
send-side declaration) that the drain-checkpoint path copies the name bytes into before
|
||||
constructing the `vm_interpret()` command string.
|
||||
- `ELEVATE-GRANT` unchanged.
|
||||
- If a caller into `ELEVATE-GRANT` is ever needed again, rebuild it close to the original
|
||||
`SEND-ELEVATE-REQUEST` shape (`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`/text-copy into `S" ...
|
||||
"`) — it was already safe. Add the one-line embedded-`"` check only if the name is ever
|
||||
runtime-supplied rather than a call-site literal. No `kernel_hermes.c`/`kernel_hermes.h`/
|
||||
`repl.c` changes needed for this.
|
||||
- `ELEVATE-GRANT` unchanged either way.
|
||||
- **Part B is done** (`capsules/zuse.4th`, committed and three-arch verified this session,
|
||||
2026-09-22) — `ZUSE-ELIGIBILITY-ADD` denied by default, granted only inside `ACL-ZUSE-BOOT`'s
|
||||
authenticated branch. This closes the actual "grant yourself eligibility with no real drive at
|
||||
all" path — a real, independently-confirmed gap, unaffected by this correction.
|
||||
- The Ed25519 **challenge-response** itself — proving the caller holds the *private* key, not
|
||||
just presenting a pubkey that matches something in `zuse_eligibility_is_member()`'s list — is
|
||||
a separate, larger piece of this same Phase 8 item and is not designed here. Today, WIREBIND/
|
||||
MINT trust whatever identity is stored on an attached thumbdrive with no challenge at all
|
||||
(confirmed by grep: no `ed25519_sign`/`ed25519_verify` call anywhere in `capsule_wirebind.c` or
|
||||
`capsule_mint.c`). This document only fixes the elevation entrypoint's own transport; the
|
||||
broader "prove you hold the private key" mechanism is Phase 8's other open half.
|
||||
`capsule_mint.c`). Also confirmed this session: the private key seed itself is stored in
|
||||
plaintext on the drive, read in the same devblock as the pubkey/cert — a naive
|
||||
challenge-response wouldn't defend against a cloned drive, since the clone has the seed too.
|
||||
Defending against drive cloning (as opposed to "no real drive at all," which the cert-signature
|
||||
chain already defends against) is a separate, harder, undesigned problem — see the Phase 8 v1
|
||||
plan (`/home/rajames/.claude/plans/jiggly-cuddling-stallman.md`) for the full reasoning.
|
||||
|
||||
## 5. What this document is not
|
||||
|
||||
|
||||
Reference in New Issue
Block a user