Skip to content

The loader-slimming track, and the firmware rule in CLAUDE.md - #582

Merged
Japabu merged 9 commits into
mainfrom
wt/toyos-loadertrack
Sep 28, 2026
Merged

Japabu merged 9 commits into
mainfrom
wt/toyos-loadertrack

Conversation

@Japabu

@Japabu Japabu commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Places the owner's rulings on the loader audit: the loader-slimming track (issues/boot-media/the-loader-does-only-what-must-precede-the-handover.md), one rule in root CLAUDE.md, and the accepted cost of a security-version floor. PR #539 is closed in favour of the track; each of its pieces is placed in a stage or listed as deleted.

  • CLAUDE.md, Architecture: Firmware — the kernel calls no UEFI service; every UEFI call ToyOS makes is the loader's, before ExitBootServices. The loader's GetVariable, SetVariable, GetTime and ResetSystem all come before the handover, so the rule holds of the tree. kernel/src/arch/aarch64/rtc.rs' header loses the GetTime clause the rule forbids.
  • issues/boot-media/the-anti-rollback-floor-is-a-firmware-variable.md: one bullet records the accepted cost. Once the floor counts a security version, an older build the same key signed at that version boots.
  • issues/boot-media/the-machine-updates-itself-without-ubuntu.md: stage 2 gets an exit and names its wait on the track's --boot-first; the A/B later stage is the track's stage 5.

Gates

gate exit
cargo test -p toyos-build --lib 0 (412 passed, 3 ignored), at 8a9af8e
cargo run -- --clippy (x86_64 and aarch64 kernel, bootloader, workspace) 0, at 8e382e0

No QEMU run and no T14 run: the only code line is a comment.

Unsure

  • 3 tries is my number: it follows Android and libabr's small counts and bounds a T14 hang loop at three bounded boots.
  • update --good as the writer of the good flag is my choice. Init already mints the slots claim, and only update holds it. An image that grants no program the claim is never good and boots three times.
  • The ready pipe and [boot] up are my choice for the health gate's signal: the tree has none, and a swap's probation only asks whether a process still runs.
  • Stage 8 needs a boot map that maps bus 0's ECAM window before mm::init. Whether toyos-bootmap can do that cheaply is unmeasured.

🤖 Generated with Claude Code

The owner ruled on the loader audit (2026-09-28): slim the loader per the
audit; the hardware clock is always UTC; keep "no firmware calls after the
handover" and write it down; redo #539 inside this track rather than land it;
end at least as secure.

- CLAUDE.md gains the one briefed paragraph: no firmware code runs on the CPU
  after ExitBootServices; every firmware call ToyOS makes is the loader's.
- issues/boot-media/the-loader-does-only-what-must-precede-the-handover.md is
  the track: seven stages in dependency order, each with an exit a test or
  gate checks, and #539's pieces placed or deleted (--boot-next, the panic
  handler's fall and its two issues, the bootvars::state line).
- The anti-rollback floor stage keeps the audit's shrink in a form that is
  argued not to weaken anything: one floor per key (Scope::Machine's) for every
  loader, so the owner's floor is unchanged and a throwaway key's floor stops
  being resettable by a write of the log partition's GUID; stale-floor
  deletion goes, since it never touched a loader's own floor.
- issues/boot-media/the-machine-updates-itself-without-ubuntu.md loses what the
  new track owns: stage 2's Ubuntu items and exit, and the A/B-rollback later
  stage. The NVMe install stays there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Japabu and others added 2 commits September 28, 2026 19:47
…led accepted

CLAUDE.md's Firmware paragraph scoped to ToyOS's own calls, since the old
wording was false of SMM and the xHCI legacy-ownership handshake. Stage 3 of
the loader track now records the owner's ruling on the per-key floor's cost
as the stage's decision, rather than the track's own argument for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 1, at 97e8fb3

CI: host success at 97e8fb3 (run 36460912313; steps --ci host and --ci gate-stage both success). git merge-tree against origin/main a2c74f2 is clean. Size: +167/-22, all prose. No production or test lines change.

BLOCKER

  • CLAUDE.md:53 — "ToyOS calls no firmware after ExitBootServices" is false of the tree today. kernel/src/drivers/xhci/legacy.rs:101 ("Asks firmware for the controller") sets USBLEGSUP.OS_OWNED at :131, then spins up to 1 s at :134-138 waiting for firmware's SMM handler to answer. That is a firmware request made by the kernel after the handover. Either the owner rules that the ownership semaphore is not a call, or the breach gets an issue with an exit (for example: clear the SMI enables and reset without asking).
  • CLAUDE.md:53 — the rule forbids a stage of an open track. Stage 5 of issues/kernel/toyos-runs-on-arm64.md (:302-306, also :231) plans PSCI CPU_ON/CPU_OFF/SYSTEM_RESET/SYSTEM_OFF. These are SMC calls into EL3 firmware from the kernel. Nothing reconciles the two.
  • track:36-41 — stage 1 deletes the only reader of the evidence that an-armed-tco-has-never-reset-the-t14.md's exit needs (TCO1_STS, and no_reboot/timeout from bootloader/src/watchdog.rs:111-130, judged by the T14 row loader_watchdog_arms at tests/toyos.rs:1765-1776). The track says that evidence "moves to the kernel's read-back", but kernel/src/arch/x86_64/watchdog.rs:84-99 reads neither TCO1_STS nor no_reboot. The same stage also leaves ARMED_ON_ARRIVAL (kernel watchdog.rs:37, :86-90) and tests/common/power.rs:565-581,652-713 dead. The stage must name the kernel read-back it adds and the predicates it moves or deletes.
  • track:52-53 — stage 1's negative control cannot fail. After wall_clock_zone goes, no wall_clock_* test reads SYS_CLOCK_EPOCH against the staged RTC: probed_epoch (tests/common/wallclock.rs:85) has one caller, zone_from_firmware (:232). A zone only moves the epoch (kernel/src/clock.rs:160-163); names and FAT stamps stay local. Patch let offset_secs = -7200; into init_wall, and every remaining wall_clock_* stays green. Name the test that reds on it. This stage changes the ABI.
  • track:89 — stage 4 deletes the dead-slot retry (bootloader/src/slot.rs:95-107). Its reason is at slot.rs:13-16: "a machine with one slot, or with two that both died, boots what it has". The track never says what slot A's good flag and tries are on a freshly built image. Every QEMU image and every stick carries slot A alone. It also never says what a table with no bootable slot does. As written, a one-slot image whose kernel dies before marking itself good boots nothing once its tries run out.
  • track:88-107 — stage 4 names no control for a slot rewrite that leaves the good flag set. Patch: update installs into a previously-good idle slot without clearing good. That slot then skips the countdown and raises the floor on its first boot. None of the three named controls reds on it, and update_hang_kills_an_unproven_image installs into a never-good slot. Add a test that updates twice over the same slot.
  • track:93-95 — slot::proven's removal is sound only if the floor rises to the verified header's version, and no control holds that. Patch: raise to the slot table's version field. Honest tests plant equal versions, so every named update_* stays green. The test must plant a table version above the header's and see the floor stay at the header's.

NOTE

  • track:47-48 — on aarch64, counter_origin (bootloader/src/arch/aarch64.rs:29-34) is a deletion, not a move: the kernel already reads CNTFRQ_EL0 (kernel/src/arch/aarch64/cpu.rs:18-23). On x86, nothing checks the moved line: tests/toyos.rs:19630-19632 prints "unsaid" and does not fail.
  • track:37-39 — the span left unbounded is ExitBootServices (bootloader/src/main.rs:752) to deadline::start (kernel/src/main.rs:392) on boot-deadline= boots. It reaches arch::watchdog::init (:415) only on the others. It does not start at "the jump".
  • track:42-45 — once the zone goes, the kernel's local/UTC split (kernel/src/clock.rs:137-139, UTC_OFFSET_SECS) is constant zero. The stage names only the loader's side and the actuator.
  • track:61-79 — stage 3 is high-risk and names no independent oracle. The candidate is the host reading the vars store: UEFI 2.10 §8.2 attributes plus the name.
  • track:74-79 — the machine this changes is the T14: QEMU gets fresh vars per boot (tests/common/qemu.rs:3157-3164), and the exit is QEMU-only. There, one variable per checkout key accumulates with nothing deleting it. A refused older image has no reset path but firmware setup (bootloader/src/floor.rs:72-76), and "deliberately reset" names no tool.
  • track:61-79 — stage 3 makes the throwaway-key bullet of the-anti-rollback-floor-is-a-firmware-variable.md false and does not say it is deleted.
  • track:99 — "--boot-first is the only boot-variable write" is false from stage 4 until stage 6, while bootnext::point_at_us (bootloader/src/main.rs:1069) still writes BootNext on every pass.
  • track:96-98 — nothing says what keeps the running system on a --once slot from marking it good.
  • track:147-156 — stage 7 makes the exit of issues/diagnostics/a-wedged-reports-newest-records-were-cut-by-the-loaders-own-log-file.md (ENDS_AT_CHAIN on the stick) unreachable, and does not dispose of that issue. The stage also does not say that the kernel takes over harvest's log-GUID stale-record check.
  • track:24 — "Its pieces are placed in the stages below" is not true of these In QEMU, the loader writes boot variables on the running system's request and a failed pass falls to the entry behind its own or powers off; toyos-metal drives a boot through a machine running ToyOS alone #539 pieces, and no stage names them:
    • toybox date;
    • Guid::parse;
    • Ssh::probe/fetch and ssh-client-host;
    • AUTHORIZED_ON_ROOT;
    • SLOT_ONCE;
    • the src/bootlog.rs constants.
  • issues/boot-media/the-machine-updates-itself-without-ubuntu.md:39-44 — stage 2 lost its exit, and does not say that it depends on the new track's stage 4 (--boot-first).

REMOVE

  • track:17 — quotes CLAUDE.md in the wording 97e8fb3 withdrew as false of SMM ("no firmware code runs").
  • track:9-13 — narration no stage needs.
  • track:20-22 — an estimate from a document outside the tree.
  • track:49 — "the audit's ten worst comments": the list is not in the tree, so the item cannot be executed.

SEND BACK

Japabu and others added 2 commits September 28, 2026 20:33
… rebuilt on the owner's rulings

CLAUDE.md: ToyOS calls no UEFI runtime service; every UEFI call is the
loader's. PSCI on ARM64 and the xHCI legacy-ownership handshake are not
UEFI calls, which closes both CLAUDE.md blockers.

The track:
- Stage 1 is what #583 lands: UTC, the hex dump, ten comments, the
  KernelArgs layout identity, IA32_TSC_ADJUST read by the kernel. The
  loader's TCO arm stays; wall_clock_utc is the test a zone reds.
- Stage 2 scopes every volume lookup to the boot disk with exactly one
  match, and asks firmware once per pass.
- Stage 3 compares one floor per key on a signed security version; the
  accepted cost of refusing older builds is gone with the build time.
- Stage 4 is new: current uefi, the loader's own panic handler, the
  unsound allocations and relocation unsafes gone, typed KernelArgs,
  one CRC32.
- Stage 5 adopts the Android/libabr tries rules as pure host-tested
  toyos-update decisions: fresh slot A untried with 3 tries, no bootable
  slot powers off, the good flag set only past a health gate, and
  controls for a good flag left set and a floor raised to the table's
  version.
- Stage 8 is new: the kernel arms the TCO before mm::init and takes the
  read-back the TCO issue's exit needs; only then does the loader's arm go.
- Stage 9 rewrites the wedged-report issue's exit and gives the kernel
  harvest's stale-record check.
- Every #539 piece is placed or listed as deleted.

the-machine-updates-itself-without-ubuntu.md: stage 2 gets its exit back,
and names its wait on the track's --boot-first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 2, at dc6aebc

CI: host SUCCESS at dc6aebc (run 36466099535; --ci host and --ci gate-stage both success). git merge-tree --write-tree is clean against origin/main bc9ccad, and clean against PR #578's head bce73ab (it edits Build & test, not Architecture). Size: +293 −21, all prose: CLAUDE.md +2, issues +291 −21. No production or test lines change.

Round-1 BLOCKERs

  1. CLOSED: CLAUDE.md vs the xHCI legacy handoff. CLAUDE.md:53 now names UEFI calls, and USBLEGSUP is a PCI capability handshake, not a UEFI call.
  2. CLOSED: CLAUDE.md vs PSCI. PSCI is an SMC, not a UEFI call.
  3. CLOSED: the TCO evidence. The loader's arm stays (track:52). Stage 8 (track:241-257) names the read-back and each predicate it moves or deletes, and every name exists in the tree.
  4. CLOSED: stage 1's control. It is now wall_clock_utc (track:56-60). At Loader slimming stage 1: the RTC is UTC, KernelArgs refuses another layout by name, IA32_TSC_ADJUST moves to the kernel #583's local 8565cc5, rtc_is_utc judges both SYS_CLOCK_EPOCH and realtime= against RTC_BASE (tests/common/wallclock.rs:262-281). A local-time kernel moves the epoch by −7200, and REALTIME + 7200 moves the time of day by +7200. Both are red by construction. The red run is Loader slimming stage 1: the RTC is UTC, KernelArgs refuses another layout by name, IA32_TSC_ADJUST moves to the kernel #583's to post.
  5. CLOSED: a fresh image with no bootable slot. A fresh slot A starts untried (track:150), and with no bootable slot the machine powers off (track:159-160).
  6. CLOSED: the good flag left set by an install. The control is named (track:193-196). See the priority NOTE below.
  7. CLOSED: the floor raised to the table's version. The control is named (track:197-199). The table carries a per-slot version that the loader does not trust (toyos-update/src/slots.rs:12, 16-19), so the test can plant one.

BLOCKER

  • track:122-123, 138-140 — The loader's panic handler "resets the machine". A panic with a fixed cause then resets into the same panic, with no bound. Today uefi-services powers the machine off. The reset also contradicts stage 5's rule that a pass with nothing to boot powers off. Fix: write loader.log, then power off. The exit test expects QEMU's guest-shutdown, not a reset.
  • track:139, 155-158 — Take an update whose signed kernel does not load: not an ELF, the wrong machine, or a refused relocation.
    • Today, attempt::write_chosen (bootloader/src/main.rs:1017) runs before load_kernel_elf (:1057), which panics at :340-402. The next pass books the death and boots the other slot.
    • Stage 5 makes the decrement the pass's last write, and books no loader failure against the image. So that slot is chosen on every pass, its tries never fall, and the kept slot never boots. That is a regression that bricks the machine.
    • Fix: a kernel the loader cannot load is a refusal of that slot inside verify, and the pass falls to the other slot. Stage 4's panic test uses a failure the machine causes instead.
    • Control: update B with a signed non-ELF kernel, and A boots. Turning that refusal back into a panic must turn the test red.
  • track:192 — "Raising the floor on a slot not yet good fails an update_* test" names no test, and none can fail.
    • The security version is now constant across builds, so every update_* test installs images of equal version. Raising the floor on an untried slot changes nothing any of them sees.
    • Patch: raise the floor on any pass that boots a slot, good or untried.
    • The test that must go red: install into B an image signed at the running security version + 1 whose kernel hangs. B spends its tries and A boots, and vars::live reads the floor at A's version.
  • track:200 — "Booting after a failed decrement fails a host test of decide" cannot catch the loader's wiring. decide takes "whether the last write persisted" as an input, so a loader that passes true without reading the write's status stays green.
  • track:134-135, 138-143 — Stage 4 changes the KernelArgs layout, which is high-risk ABI, and its exit names no control that the layout identity moved. Every listed test boots a loader and kernel from one build.
    • Patch: leave the identity constant as stage 1 set it. Every listed test stays green.
    • Control: a stage-1 loader handing a stage-4 kernel is refused by the identity check's name.
  • track:102-107 — "Why nothing is weaker" is false.
    • Today's build-time floor refuses every build older than the newest one a boot proved. Stage 3 admits any older image the same key signed at the same security version.
    • So anyone who can write a slot can roll the machine back to a build whose hole nobody declared.
    • The ruling accepts that cost, and the track hides it. Delete the heading's claim. Add the gap to the "does not hold against" list of issues/boot-media/the-anti-rollback-floor-is-a-firmware-variable.md, which stage 3 already edits.

NOTE

REMOVE

SEND BACK

Japabu and others added 2 commits September 28, 2026 21:37
…s controls can fail

CLAUDE.md's Firmware paragraph now reads as the orchestrator ruled: the
kernel calls no UEFI service, and every UEFI call is the loader's, before
ExitBootServices. The loader's GetVariable, SetVariable, GetTime and
ResetSystem come before the handover, so the old wording was false of it.

The track:
- Stage 1 matches #583 at 8565cc5: KernelArgs::layout (0x5459_0001),
  kernel_args_layout_refused with the loader-writes-no-layout actuator,
  the probe's realtime=, and the kernel's IA32_TSC_ADJUST line. No test
  fails without that line, and the stage says so.
- Stage 3 drops "why nothing is weaker". A security version admits an
  older build the same key signed at that version. The floor issue records
  that as the owner's accepted cost. Raising the version is a reviewed PR
  that edits one constant and names the security fix. The loader deletes
  the build-time ToyOSImageFloor- variables instead of leaving them behind.
- Stage 4's panic handler writes loader.log and powers off, never resets.
  Its test panics on a floor planted in 9 bytes, a failure the machine
  causes. KernelArgs' layout word rises to 0x5459_0002, and
  kernel_args_last_layout_refused fails if it does not.
- Stage 5 refuses a signed kernel the loader cannot load inside verify,
  so the other slot boots instead of the pass bricking the machine. An
  install's priority rises above the kept slot's. update --good, run by
  init at the health gate under the slots claim, writes the good flag, and
  an image without that claim is never good. Each rule gets a named guest
  control: update_floor_waits_for_good, update_readonly_stick_boots_nothing
  (red under `let persisted = true;`) and
  update_unloadable_kernel_boots_the_other_slot. The slot-table oracle is
  decoded without production code.
- Every #539 piece the review listed is placed or deleted. The #539-only
  issue names and the stack-offset closure (#584's) are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 3, at 0a76929

CI: host SUCCESS at 0a76929 (run 36474642506, the only job, success). git merge-tree --write-tree origin/main 0a76929a is clean against e3a1cdc (exit 0). Size: +351 −21, all prose (CLAUDE.md +2, issues +349 −21). No production or test lines. Stage 1 matches #583 at 8565cc5: layout at 164, LAYOUT 0x5459_0001, the check after panic_console::arm and before blackbox::arm naming both words, loader-writes-no-layout writing 0, wall_clock_utc with a −120 zone and realtime=, the kernel's IA32_TSC_ADJUST line, and no counter_origin or TSC_ADJUST read left in bootloader/src.

Round-2 BLOCKERs

  1. CLOSED: the panic handler resetting. track:141-143 now says write loader.log, then power off. Its control is a new BLOCKER below.
  2. CLOSED: an unloadable signed kernel. track:183-185 makes it a verify refusal. Under the named mutation, B is chosen, the loader panics and powers off, and A never boots, so update_unloadable_kernel_boots_the_other_slot goes red.
  3. CLOSED: update_floor_waits_for_good (track:234-237). Under "raise on any booted slot", B's untried pass raises the floor to v+1. A is then refused, nothing boots, and the floor does not read at A's version.
  4. CLOSED: update_readonly_stick_boots_nothing (track:246-249). Under let persisted = true;, A boots and the test goes red.
  5. CLOSED: the identity bump. Under LAYOUT left at 0x5459_0001, LAST_LAYOUT equals LAYOUT and the kernel accepts. The offset half is a new BLOCKER below.
  6. CLOSED: "Why nothing is weaker" is gone, and the cost is on the floor issue's list (floor issue:37-41).

BLOCKER

  • track:147-149, 155-156 — Stage 4 deletes "the hand offset asserts", and with them 8565cc5's offset_of!(KernelArgs, layout) == 164 and every prefix offset. It also turns "u32 presence flags" into #[repr(C, u32)] enums, and boot_partition_present (144) and the boot-partition fields (112-148) sit before the word.
    • The claim "Every field before the layout word keeps its offset" is then checked by nothing, and the enum plan breaks it.
    • Patch: move pub layout: u32 after root_read_tsc in stage 4's KernelArgs. kernel_args_last_layout_refused stays green, because loader and kernel share the struct. A real stage-1 loader then hands the word at 164 to a kernel that reads it elsewhere.
    • Fix: stage 4 keeps compile-time offset_of! asserts for layout and every field before it. It says the prefix stays flat, so the enums are only after the word. That patch must then fail to build.
  • track:163-165 — loader_panic_powers_off can stay green under the mutation it names.
    • The refused-floor site writes its reason to loader.log itself before panic! (bootloader/src/main.rs:895-902).
    • uefi::helpers' handler also ends in ResetSystem(SHUTDOWN), so QEMU still reports guest-shutdown.
    • A test that finds "the loader's panic" by the refusal text is green under uefi::helpers.
    • Fix: name the needle as a line only the new handler writes (its panic message with location). The site's own pre-panic loader.log write goes, since the handler now owns that write.
  • track:192-196 — "once every service the image's system.toml names is up" / "Init starting is not the gate" defines "up" nowhere. Init has no readiness signal: services are spawned, and only a swap has a probation deadline (userland/init/src/main.rs:521-534).
    • This is high-risk: the good flag decides the floor's rise and whether a broken image is kept.
    • No control can fail on the claim. The hang tests hang before init, so they cannot tell a real gate from none.
    • Patch: init runs update --good right after spawning the [boot] start list. No named test goes red.
    • Fix: define "up" and name a control. An image with a named service that exits at start is never marked good and falls back after three boots. The patch above must turn it red.
  • track:103-105 — "this stage adds that check to .claude/agents/reviewer.md" does not say what line it adds. A stage that edits the review prompt quotes the exact line it adds.

NOTE

  • track:183-189 — The closed list "not an ELF, another machine's, or a relocation" leaves every other failure after verify in start_kernel looping on B with no fallback, because the decrement is the pass's last write. An example is a PT_LOAD memsz that alloc_kernel_memory cannot place. The list should be every refusal load_kernel_elf makes from the kernel's bytes.
  • track:250-252 — update_unloadable_kernel_boots_the_other_slot asserts only "A booted". It should also assert the loader's refusal of B with the non-ELF reason, so an install that never reached B cannot pass it.
  • track:149-152 — Stage 4 adds a "once on the running system's request" variant that nothing writes until stage 5. That is a dead variant at stage 4's landing: it belongs to stage 5, with its own LAYOUT bump.
  • track:116-117 — Stage 3's "at or above" makes the floor issue's "/system/bin/update's own check — newer than the running image" line false (floor issue:43-45). Stage 3 does not list deleting it.
  • track:234-236 — Once stage 3 makes the version one constant, nothing says how the test signs an image at "the running security version + 1". Name the harness path.

REMOVE

  • track:15 — "ToyOS ends at least as secure as it starts": false once stage 3 admits an older build at the floor's version.
  • kernel/src/arch/aarch64/rtc.rs:1-2 — "the UEFI runtime's GetTime or": it contradicts the Firmware rule this branch adds.
  • PR body, "What changed, per decision" (the stage list) and "The controls the track names" table: they restate the track in main's record.
  • PR body, Unsure: "I found them by counting boot sites and loops in tests/": provenance.

SEND BACK

…d "up" is a signal

- Stage 4 keeps an `offset_of!` assert for the layout word and every field
  before it, and the typed `KernelArgs` changes only fields after the word,
  so moving `layout` fails to build.
- `loader_panic_powers_off` looks for the handler's own `loader: panicked
  at` line, and the refused-floor site stops writing its reason to
  `loader.log` before it panics.
- Stage 5 defines the health gate: `[boot] up` names the services, and each
  writes one byte on a `ready` pipe init endows, since the tree has no
  readiness signal. `update_dead_service_is_never_good` fails when init
  marks good right after spawning.
- Stage 3 quotes the line it adds to the review prompt, and lists deleting
  the floor issue's "newer than the running image" line.
- Any failure after `verify` in `start_kernel` that the image causes is a
  refusal of that slot; the unloadable-kernel test also finds the non-ELF
  refusal of B; the once variant and its `LAYOUT` bump move to stage 5;
  `Rig::update` is the path that signs at +1.
- Removed: "at least as secure as it starts", and the aarch64 RTC header's
  `GetTime` clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 4, at 8e382e0

CI: host COMPLETED/SUCCESS at 8e382e0 (run 36477397591, the only check). git merge-tree --write-tree origin/main 8e382e0c against e3a1cdc exits 0 with no conflicts. Size: +377 −23. Everything is prose except two comment lines in kernel/src/arch/aarch64/rtc.rs. grep finds no UEFI runtime call in kernel/src, so the Firmware rule holds of the tree.

Round-3 BLOCKERs

  1. CLOSED: the stage-4 prefix. track:155-157 pins layout and every field before it with offset_of!, and "a field the typing changes moves after the word". The exit (track:179) makes moving layout after root_read_tsc fail to build. Loader slimming stage 1: the RTC is UTC, KernelArgs refuses another layout by name, IA32_TSC_ADJUST moves to the kernel #583's own asserts at 8565cc5 (toyos-abi/src/boot.rs 211-229) are what that exit keeps.
  2. CLOSED: loader_panic_powers_off. The needle is loader: panicked at bootloader/src/ (track:172-175), and the refused-floor site's pre-panic write goes (track:150-151; today it is bootloader/src/main.rs 895-902). uefi::helpers' handler writes neither the loader: prefix nor to loader.log.
  3. CLOSED: the health gate. Readiness is defined as [boot] up plus a ready pipe endowed by init (track:203-211). The control is update_dead_service_is_never_good (track:273-276): under "update --good right after spawn", B turns good and A never boots, so the test goes red. The design fits the capability model. Init mints the pipe and moves the write end in under a label, as it already does with ALIVE (userland/init/src/main.rs 1608-1616). There is no registry and nothing named. ALIVE's read end goes to logd, so it cannot carry init's signal, and a second pipe is the smallest mechanism.
  4. CLOSED: the reviewer.md line is quoted verbatim (track:106-108).

BLOCKER

None.

NOTE

  • Named change, track:59, 166-168, 178-179, 226-228. Under Loader slimming stage 1: the RTC is UTC, KernelArgs refuses another layout by name, IA32_TSC_ADJUST moves to the kernel #583's derived LAYOUT = 0x5459_0000 | size_of::<KernelArgs>(), 0x5459_0001/2/3 and the LAST_LAYOUT literals are wrong.
    • Stage 1's word is 0x5459_0000 | 1272 (8565cc5 asserts size_of::<KernelArgs>() == 1272).
    • Each later LAST_LAYOUT is the prior stage's derived value, written as a literal.
    • Stage 4's control "leaving LAYOUT at 0x5459_0001" is no longer a patch anyone can apply.
    • Restate these values and say which check catches which change:
      • A size change moves the word, and kernel_args_last_layout_refused catches it.
      • A moved prefix field fails the prefix offset_of! asserts.
      • A change that keeps the size and happens after the word (a reorder, a type swap of equal size) is caught by neither.
    • So stage 4 keeps an offset_of! assert on every field, not only the prefix, and adds const _: () = assert!(LAYOUT != LAST_LAYOUT);. A stage that keeps the size then fails to build.
    • Control: stage 4's struct padded back to 1272 bytes must fail to build.
  • Named change, track:226-228. Stage 5's once variant keeps both the size and every offset, so with a derived word LAYOUT does not rise.
    • Stage 3's "at or above" admits an older kernel at the same security version into a slot.
    • A stage-5 loader writing the once discriminant to a stage-4 kernel is then an out-of-range repr(C, u32) discriminant read in the kernel, which is UB.
    • The track must say how stage 5 moves the word (the assert above forces it), or that the kernel reads each discriminant as a u32 and refuses an unknown one by name.
  • track:273-276 — update_dead_service_is_never_good stays green under "update --good once any named service wrote its byte" if the test image's [boot] up names only the dead service. Name a live service beside it.
  • track:194-196 — "any failure after verify … that the image causes" replaces the closed list with a judgment. Stage 5's PR should list which start_kernel refusals are the image's (every load_kernel_elf refusal of the kernel's bytes) and which are the loader's.

REMOVE

LAND AFTER NAMED CHANGES

…d-picked

Restates the track's LAYOUT/LAST_LAYOUT literals as
`0x5459_0000 | size_of::<KernelArgs>()`, so a size change moves the word on
its own and `kernel_args_last_layout_refused` catches it; names the
remaining gap a same-size reorder or equal-size type swap leaves, closed by
pinning every field's offset (not only the prefix's) and
`assert!(LAYOUT != LAST_LAYOUT)`; states how stage 5's once variant, which
keeps the struct's size, still moves the word and how the kernel refuses an
unrecognized discriminant by name instead of reading it out of range; gives
`update_dead_service_is_never_good`'s image a live service beside the dead
one, so "any named service wrote its byte" is distinguished from "every
one did"; has stage 5's PR list which `start_kernel` refusals are the
image's; and drops the PR body's Unsure bullet on #583's pushed head, now
stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 90836b5 Sep 28, 2026
1 check passed
@Japabu
Japabu deleted the wt/toyos-loadertrack branch September 28, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant