Files
LithosAnanake/docs/working/architecture/CAPSULE-BLOCK-PERSISTENCE-BOUNDARY-FINDING-20260709.md

5.7 KiB
Raw Permalink Blame History

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 02047. Fast working memory, presented in block shape.
  • Ramdrive — LBN 20483071. 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 02047 → RAM surface (g.ram_user = BLK_RAM_BLOCKS(2080) - BLK_FORTH_SYS_RESERVED(32) = 2048), LBN 20483071 → 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 (40004007, 4015), zuse.4th (40164018), lib.4th (4050), common:msg.4th (4055), doe-campaign.4th (40604065), hermes:init.4th (41004153), artemis:init.4th itself (41104141, 48514852), and the init-1.4th through init-9.4th / L8-variant capsules (4406+, 4506+, 4606+, 4706+, 48064842). Capsules whose headers stay within 20483071 (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:

  1. A hard boundary check in write_ramdrive_block() — rejected. Nothing is meant to reject or no-op a write based on block_num.
  2. Renumber the 4000+ capsules' Block N headers 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.