5.7 KiB
Capsule Loader — Missing Persistence Boundary Check
Date: 2026-07-09
Branch: lithosananke
Status: CLOSED — not a bug. Captain Bob's direct correction
(2026-07-10), after this finding drove a design doc
(CAPSULE-LOADER-DEFINITIONS-ONLY-DESIGN-20260709.md) and a real
implementation that removed the write entirely — both wrong, both
reverted (commits 43d81d49/548062ab, three-arch Tripod acceptance
re-confirmed at ee91280d): "all capsule does is LOAD a block with a
given Block xxxxx header to THAT BLOCK. that's all. They are not ordered
in the capsule necessarily." The behavior documented below —
write_ramdrive_block writing every capsule's content to the literal LBN
named by its own Block N header, unconditionally, including onto
Artemis's real disk for 4000+-numbered capsules — is the intended
mechanism, not a boundary violation. Neither candidate fix in "Not fixed"
below applies; no fix is needed. Kept as a record of a wrong turn, per
this repo's revision-history convention, not deleted.
Author: Captain Bob / Claude Code
Surfaced while scoping HERMES-MESSAGE-BLOCK-STORAGE-DESIGN-20260708.md
(picking an LBN range for Hermes's new message-block storage) — not
something either doc set out to find, but relevant to both.
The architecture, as confirmed by Captain Bob
Inside a VM, block-addressed storage has two internal surfaces:
- RAM surface — LBN 0–2047. Fast working memory, presented in block shape.
- Ramdrive — LBN 2048–3071. A mounted block device, still internal
to the VM (volatile, RAM-backed via
krd_buf) — not real disk.
Both are internal to the VM. Capsule persistence mechanics — the part of
capsule loading that writes a capsule's parsed blocks somewhere so a
later LOAD N can find them — are meant to reach only into these two
surfaces, never past LBN 3071 into a real external device.
The defect
write_ramdrive_block() (src/starkernel/capsule/capsule_loader.c:146)
has no boundary check. It calls blk_get_buffer(block_num, 1)
(src/block_subsystem.c:576) with the literal block_num taken from a
capsule's own Block N source header — unconditionally, regardless of
value.
blk_get_buffer() resolves any LBN against the full chain of registered
block devices, in registration order (src/block_subsystem.c:491-546):
LBN 0–2047 → RAM surface (g.ram_user = BLK_RAM_BLOCKS(2080) - BLK_FORTH_SYS_RESERVED(32) = 2048), LBN 2048–3071 → the ramdrive raw
device (KRD_MAX_BLOCKS = 1024, registered in
capsule_blk_init()), LBN 3072+ → whatever device is attached next. In
the current boot sequence that next device is Artemis's virtio-blk disk,
attached in kernel_main.c:520-530 via virtio_blk_find_artemis() +
blk_subsys_attach_device(), whose own start_lbn is 3072
(artemis/init.4th's own 3072 CONSTANT ART-HDR-LBN, matching the
total_user_lbn accumulated by the two prior device registrations).
So a capsule whose Block N headers are ≥ 3072 has its content written
by write_ramdrive_block() directly onto whatever real device occupies
that LBN — currently Artemis's persistent disk — as an unbounded side
effect of capsule loading. This happens on every VM birth, not just the
interactive EXEC word: capsule_birth_set_hooks()
(src/starkernel/capsule/capsule_vm_hooks.c:121-124) registers
capsule_exec_hook (which wraps capsule_exec_payload) as the
vm_exec_fn used by both capsule_birth_mama() and
capsule_birth_baby() (src/starkernel/capsule/capsule_birth.c:374, 449).
One corroborating detail: capsule_loader.c:50-51 defines
CAPSULE_RAM_OFFSET 2048u with the comment dest = source - CAPSULE_RAM_OFFSET, but write_ramdrive_block() never references this
macro — block_num is passed to blk_get_buffer() unmodified. The macro
is unused dead code; whatever offsetting scheme its comment describes was
never implemented.
Scope — which capsules are affected
From capsules/BLOCK_MAP.md (auto-generated), every capsule with Block N headers ≥ 4000 is exposed: ACL.4th (4000–4007, 4015), zuse.4th
(4016–4018), lib.4th (4050), common:msg.4th (4055),
doe-campaign.4th (4060–4065), hermes:init.4th (4100–4153),
artemis:init.4th itself (4110–4141, 4851–4852), and the
init-1.4th through init-9.4th / L8-variant capsules (4406+, 4506+,
4606+, 4706+, 4806–4842). Capsules whose headers stay within 2048–3071
(init.4th, init-l8-omni.4th, init-6.4th, doe.4th, init-4.4th,
init-7.4th, init-8.4th, init-0.4th) are unaffected — their writes
land in the volatile ramdrive regardless.
Mitigating factor, also confirmed
blk_get_buffer() returns NULL for an LBN with no device registered
there; write_ramdrive_block() no-ops on a NULL buffer
(capsule_loader.c:150-151). So on any boot where Artemis's virtio-blk
disk isn't attached (kernel_main.c:528, "Artemis: no virtio-blk disk
(continuing without)"), every 4000+-numbered capsule's persistence
write silently fails rather than landing anywhere — harmless in that
case, but also meaning LOAD N on those block numbers would return
whatever stale/zeroed content already occupies that LBN, not the
capsule's own text.
Not fixed — because there is nothing to fix
Two candidate fix shapes were raised in conversation. Both are now moot, confirmed directly:
- A hard boundary check in
write_ramdrive_block()— rejected. Nothing is meant to reject or no-op a write based onblock_num. - Renumber the
4000+capsules'Block Nheaders back under 3072 — also unnecessary; a capsule's header number naming a real block anywhere in the address space, including Artemis's disk, is correct.
write_ramdrive_block() and its call site in capsule_exec_payload
remain exactly as they were before this finding was ever filed.