Skip to content

Memory overwrite on every boot; the black box goes, and a clean stop proves an image - #1196

Merged
Japabu merged 12 commits into
mainfrom
loader-sets-mor
Nov 5, 2026
Merged

Japabu merged 12 commits into
mainfrom
loader-sets-mor

Conversation

@Japabu

@Japabu Japabu commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Status: evidence complete. Review rounds 1 to 3 are addressed, through 7bbfecf61. Host checks are green, both builds exit 0, and the stop-path guest rows pass. Metal boots on the T14 and the AMD bench, both flashed by this branch, passed, with memory_overwrite_requested green on each. The T14 metal boot (flashed by this branch's harness at 8aee19205) is in; the AMD bench is pending a power cycle.

Problem

Linux's EFI stub asks the firmware to overwrite RAM after a reset (TCG MemoryOverwriteRequestControl). ToyOS had declined that under the owner's 2026-10-03 ruling, "Keep crash records". As a result, anyone who reset a ToyOS machine into their own boot medium could read what the last boot left in RAM. The owner reversed that ruling on 2026-11-05: "just wipe it … if that means we remove the blackbox then so be it". The same day he ruled that updates are fix-forward, with no automatic fallback to the previous slot.

What changes

1. The loader requests the overwrite on every pass (loader/src/overwrite.rs).

  • It reads MemoryOverwriteRequestControl (vendor e20939be-32d4-41be-a150-897f85d49829), logs the value it found, and writes 0x01 with NV|BS|RT.
  • If the firmware does not define the variable, it writes nothing.
  • The facts come from the TCG PC Client Platform Reset Attack Mitigation Specification 1.2, cited at the site:
    • The firmware creates the variable; an OS loader never does (§4.1).
    • Bit 0 is ClearMemory. DisableAutoDetect (bit 4) and the reserved bits stay 0 (§4.1.2).
    • The firmware clears the request after it overwrites memory (§4.1.3), so the loader sets it again on every pass.
  • A request another OS already set is logged and kept.
  • The MOR lock (§4.2) is not used. The firmware unlocks it on every boot, so it cannot block the loader. Nothing after the handoff can call SetVariable, so the lock would protect nothing extra.

2. Measured on the T14 (at 1e459d3f2; evidence below):

  • The pass after Ubuntu's reboot started 9.753 s after reset, read [00] and set [01].
  • The pass after ToyOS's own reset started at 10.725 s, read [00] and found no black-box record.
  • So this firmware overwrites memory after a ToyOS reset, then clears the request. It costs about 1 s: 0.97 s against the pass after Ubuntu's reboot in the same run, and 1.1 s against the 9.63 s median of 148 earlier passes.
  • It does not overwrite after Ubuntu's reboot: that pass read [00] at the usual time, so the firmware treats Ubuntu's shutdown as orderly and clears the request without overwriting.
  • AMD: the firmware defines the request and accepts it. Its overwrite cost is unmeasured, because its loader states no counter rate.

3. Proving an image moves from RAM to the log partition.

  • The supervisor's stop now runs: flush the log, sync its files, then ask logkeeper for the stop mark stopped (MARK/MARKED), then stop its drivers. The mark is the last write to the log volume before its storage driver stops. On RESUME, logkeeper deletes the mark if it wrote one.
  • Before each handoff, the loader records which image it boots, overwriting the fixed-length record booted in place once the last mark is gone.
  • The next pass takes the mark. Only when the mark and the record are both there does it raise the anti-rollback floor, to the version in that slot's re-verified signed header.
  • Who can write them. Only the row that keeps the log (it holds the log's origins) has /log read-write; every other declared row's view holds it read-only. So only logkeeper, the supervisor and the loader can write the mark or the record.
  • The one window still credited. A hang after the mark (the drivers' stop, the kernel's quiesce, the reset) that the deadline then ends. The metal verdict sees it only as a boot that came back late; it is recorded in the crash-record track.
  • Removed by the fix-forward ruling: marking a slot dead, the dead-slot fallback, Refusal::Died, the attempt count and its hang bound.

4. The boot chain and the black box are deleted.

  • The loader no longer points BootNext at itself, and every pass boots a kernel. The pass that read the black box, ended the chain and reset the machine is gone, and so is loader.log's append mode.
  • Deleted:
    • lib/blackbox, kernel/src/blackbox.rs and loader/src/blackbox.rs.
    • Everything only they used:
      • the fault entry's register seal;
      • the panic's and the stop's seals, and the stop's log tail;
      • the census's sealed form;
      • the deadline's and the hard-lockup detector's wedge reports, and the state kept only for those reports (each CPU's last kernel pc and NMI frame, the lock a spin waits on, the boot phase);
      • the x86 cache write-back;
      • three kernel test switches (actuators): wedge-before-reset, hard-lockup-probe and blackbox-foreign-identity.
  • The deadline and the hard-lockup detector still reset the machine; they just leave no record.

5. The metal verdict uses only what survives the boot. The contract is stated once, in build/bootlog.rs's header: the log partition (logkeeper's files and the stop mark) and the machine coming back within its bench's recorded time. back_secs is now a recorded number per boot, held to the bench record like complete_ms; the first run under this harness records it.

  • Rows deleted because their only evidence was on the page: blackbox_unclaimed_page, blackbox_foreign_record, blackbox_done_chain, irq_census_conservation, boot_deadline_ends_a_wedge, watchdog_resets and hard_lockup_ends_a_deaf_cpu. The panel timings and the stop-record check go with them.
  • Now judged from the volume: machine_reboot, watchdog_fed, the disk-load row and amdvi_usbd.
  • A boot that is meant to be ended by its own deadline now owes its boot record and must not have a stop mark.
  • QEMU: screen_loader_clears relied on a loader pass that booted no kernel, so it is deleted and the gap is filed. The page read in screen_fatal_halt_composited goes.
  • New: a_clean_stop_proves_its_image (x86-64 q35). It boots, reboots, and checks that the next loader pass reads the mark and raises the floor.

6. Records and manual.

  • Filed: a-crash-record-survives-a-reset-outside-ram.md, a track for a crash record that survives a reset outside RAM. It lists ERST, a UEFI variable (pstore / NVRAM) and a disk dump; the owner chooses the route.
  • Filed: the-loaders-screen-clear-is-tested-by-nothing.md.
  • Closed: nine records — the two MOR records, plus seven whose subject was the page.
  • Corrected: the records that cited the page, including the loader track, the logging track's persistent-RAM ring, and the security-parity track's declined line.
  • The manual's boot, kernel and diagnostics pages now say what is true.

Temporary gaps (recorded in the crash-record track)

  • A panic or fault on a machine with no serial port can be read only from its panel.
  • The deadline and the lockup detector have no metal test that shows they fire.
  • The census, the stop record and the panel timings that a stop writes after the last flush reach no file on metal.

Settled by the orchestrator

  • Deleting wedge-before-reset with its rows is accepted. a-frozen-toyos-waits-for-a-hand-on-the-power-button.md says its T14 TCO reading brings the switch back.
  • The verification fallback — booting the other slot when the marked one fails its signature, hash or floor check — stays in this PR. It is removed separately, in the update track's stage 2, under fix-forward. This PR removes only the death-driven fallback.

Checks

  • cargo run -- --build-only and --build-only --arch aarch64 at the head: exit 0 (logs below).
  • cargo run -- --ci host at 2ae221e53: exit 0, 108 steps all green. dd878948a adds only the guest row, which cargo check --tests compiles. the_stop_owes_its_record, a_bound_fires_within_one_period_of_itself and a_cleared_page_owes_no_panel were deleted with what they judged. The planted-readback checks were updated.
  • Guest rows (dispatched): x86-64 machine_shutdown_short_stop, screen_* fatal rows, session_lifecycle, preempt, a_clean_stop_proves_its_image; AArch64 virt_reboot, virt_timer_preempts, virt_early_panic, virt_fatal_halts_the_others_first, virt_first_entry, virt_ring0_timer_in_syscall. The AArch64 rows run because its trap entry changed.
  • Metal, T14: one testcases boot of dd878948a, flashed by this branch at 8aee19205. Verdict passed under the new contract, and memory_overwrite_requested passes (evidence r4).
  • Metal, AMD: the same image, flashed by this branch at 7bbfecf61. Verdict passed, and memory_overwrite_requested passes. The firmware defines the request (0x7), read [00] and took [01]. Its overwrite cost is unmeasured, because this bench's loader states no counter rate (evidence r7).

Evidence (ToyOSOrg/evidence, branch pr-1196):

🤖 Generated with Claude Code

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Decision for stage 2: what replaces the black box's other jobs

These are the implementer's proposals, not verified results. The owner's ruling accepts losing crash records. The black-box page also does three other jobs, and these need a decision before deleting it.

A. Proving an image (the anti-rollback floor and dead slots). This must be replaced in the same landing. Today a DONE record is Ended::Proven. It credits the slot and is the only event that raises the floor. A panic, fault or wedge is Ended::Died, and the slot is marked dead.

  • A1 (proposed). The running system marks its boot good on disk before its reset. logkeeper owns the log volume and is its last writer at a stop, so it writes a stop mark naming the booted image beside attempts. The next pass treats "booted, and its stop mark is there" as Proven, and "booted, and no mark" as unreported.
    • An unreported boot of an image above the floor is dead. This merges today's Died and Hung, because without RAM a panic and a hang look alike.
    • Trust is unchanged. The black box was also written by the kernel, and the loader still raises the floor only to the version it verifies in the slot's signed header.
  • A2. The supervisor or update service marks the boot good once the system reaches health, as Android's markBootSuccessful and systemd's boot-complete do, rather than at the stop. An image that runs for days without a clean stop is still proven. Choosing that health point is a policy question.

B. The boot chain (BootNext back to the loader, and pass 2). Pass 2 exists to read the page. Without the page, I propose deleting bootnext::point_at_us and end_this_pass.

  • The kernel's reset goes to the firmware's own boot order. A reboot just reboots. On a bench, that order hands the machine back to Ubuntu.
  • The record of how the last boot ended is read whenever this image next boots.

C. Records written after logkeeper stops (the stop record, the IRQ census, the panel census, the log tail, Rebooting.). The page is their only channel.

  • C1 (proposed). Write what can be written before logkeeper's last flush into /log, and drop the rest. The metal rows that judge them get reworked or deleted:

    • irq_census_conservation, the_stop_owes_its_record, the panel census, boot_deadline_ends_a_wedge, hard_lockup_ends_a_deaf_cpu, machine_reboot, watchdog_resets;
    • blackbox_* and the guest [panic] page read;
    • bootlog's HANDED_BACK verdict.

    A wedge row can only be judged from the panel, so it loses its metal oracle until the follow-up track (ERST, pstore or a disk dump) exists.

Records to update alongside: step 1 of the logging track plans a ring in persistent RAM, and the wipe rules that out. A new track, a-crash-record-survives-a-reset-outside-ram.md, is drafted and lists ERST, a UEFI variable (pstore or NVRAM, which would break the rule that the loader makes every UEFI call) and a disk dump.

Estimated size of stage 2 (inference): roughly −3,000 lines across lib/blackbox, kernel/src/blackbox.rs, loader/src/{blackbox,bootnext,attempt}.rs, lib/update/src/record.rs, build/bootlog.rs, tests/common/{power,metal}.rs and tests/metal, plus about 30 issue files that cite the black box.

Japabu added a commit that referenced this pull request Nov 5, 2026
- Delete the fair-band record: its aim was adopted as J1, and only the
  reservation layer it waited on was declined, which the cpu-time record
  holds. Nothing cited it.
- Remove the reservation model from the records that still stated it as
  done or as the partner of placement.
- The guids record names stage 5's good flag, not tries; the planned
  `hang_bounded_by_the_stick` row bounds a `--once` image's hang.
- The package track's stage 6 and installer bullet keep the ruling date,
  what is built and the exit; the design is the manual's.
- The cpu.rs comment cites the corpse-deferral finding instead of restating
  it; the scheduling page drops the per-stream line the general one covers.
- Update-track stage 2 keeps the next boot's report of the last boot's
  death, and loader stage 5 says its good flag replaces the black-box page
  as an image's proof, leaving its order against the page's removal to #1196.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 5, 2026
…s mark

The loader's memory-overwrite request has the firmware wipe RAM after a
reset, so nothing the black-box page carried survives it. Measured on the
T14 at 1e459d3 (#1196): the pass after the kernel's reset read the request
back as [00] and found no record, and began at 10.725 s after reset against
9.753 s for the pass after Ubuntu's reboot and a 9.63 s median over 148
earlier passes: the firmware overwrote memory, in about a second.

The owner ruled on 2026-11-05 ("just wipe it ... if that means we remove the
blackbox then so be it") and the same day made updates fix-forward, with no
automatic fallback to the previous slot. This removes the page and every job
it did:

- Proof. A boot proves its image by stopping cleanly: logkeeper writes the
  stop mark (toyos_logstream::STOPPED) on the log partition once a stop's
  flush has made the log durable, and takes it back on RESUME. The loader
  records the image it boots (lib/update's record, now just that image) and
  the next pass takes record and mark, raising the anti-rollback floor to the
  verified header's version only where both are there. Death marking, the
  dead-slot fallback, Refusal::Died, the attempt count and its hang bound go.
- The boot chain. The loader no longer points BootNext at itself, and every
  pass boots a kernel: the pass after a reset that read the page, ended the
  chain and reset the machine is gone, with loader.log's append mode.
- The page. lib/blackbox, kernel/src/blackbox.rs and loader/src/blackbox.rs
  go, with everything only they used: the fault entry's register seal, the
  panic's and the stop's seals, the stop's log tail, the census's sealed
  form, the deadline's and the hard-lockup detector's wedge reports and the
  state kept only for them (each CPU's last kernel pc and NMI frame, the lock
  a spin waits on, the boot phase), the x86 cache write-back in both, and
  the wedge-before-reset, hard-lockup-probe and blackbox-foreign-identity
  arms. The deadline and the detector still reset the machine.
- The harness. A metal boot is judged by what survives it: the log
  partition (logkeeper's files and the stop mark) and the loop seeing the
  machine come back; build/bootlog.rs states it once. The rows whose oracle
  was the page go (blackbox_unclaimed_page, blackbox_foreign_record,
  blackbox_done_chain, irq_census_conservation, boot_deadline_ends_a_wedge,
  watchdog_resets, hard_lockup_ends_a_deaf_cpu), with the panel timings and
  the stop-record check; machine_reboot, watchdog_fed, the disk-load row and
  amdvi_usbd are judged off the volume. screen_loader_clears, which held a
  loader pass that booted no kernel, goes, and its gap is filed.

Records: the follow-up track for a crash record that survives a reset
outside RAM is filed (ERST, a UEFI variable, a disk dump; the owner chooses).
Seven records whose subject was the page close; the rest that cited it are
corrected. The manual's boot, kernel and diagnostics pages say what is true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title loader: request the firmware's memory overwrite on every boot (stage 1; black-box deletion pending a decision) Memory overwrite on every boot; the black box goes, and a clean stop proves an image Nov 5, 2026
Japabu added a commit that referenced this pull request Nov 5, 2026
Found while judging #1196, whose branch reds the row; main at ec89121
reds it alone the same way (guest-rows run 42094494342).

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

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Guest rows for #1196 (guest-rows workflow):

  • x86-64, run 42085977616 at dd878948a: machine_shutdown_short_stop, screen_fatal_halt_composited, screen_panic_muted, screen_fatal_behind_a_painter, session_lifecycle and preempt pass.
  • a_clean_stop_proves_its_image:
    • Its first version read the loader's line off the kernel-only console capture, which never carries it; fixed in e6f9abab7.
    • At e6f9abab7 it passes (run 42091551186). The next loader pass logs "Proof: the last boot, of slot A's image bfe20a50…, stopped cleanly" and "Anti-rollback floor: 1793906773, raised from 0 by the boot that proved it".
    • Negative control (run 42091556713): the same head, with logkeeper writing its mark under another name. The test is red, and the second pass logs "the last boot left no stop mark".
  • AArch64, run 42085130353 at 2ae221e53: virt_reboot, virt_fatal_halts_the_others_first, virt_early_panic, virt_timer_preempts, virt_first_entry and virt_ring0_timer_in_syscall pass.
  • virt_reboot_refused_without_psci stalls on this branch, and also on main at ec8912181, where it ran alone (run 42094494342). That makes it independent of this change; filed as issues/virt-reboot-refused-without-psci-goes-quiet-before-its-job.md.
  • While reading that red I found and fixed a fault of my own (395c1cd62): logkeeper deleted the stop mark on a refused stop even where none was written, which is a filesystem call on a boot whose /log may never answer.

@Japabu

Japabu commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator Author

T14 metal for #1196: one testcases boot of the image staged at dd878948a, flashed by this branch's toyos-metal at 8aee19205, run by the orchestrator. Evidence: https://github.com/ToyOSOrg/evidence/tree/06a9cad9251a22f0227ee555f9d41a020518fc8e/r4-t14-flasher

Boot verdict: passed (rc 0) under the new contract: the volume carries the boot record, the supervisor's stop and logkeeper's stopped mark.

Judged with this branch's harness (--metal-readback over the readback, at 8aee19205, exit 0): PASS memory_overwrite_requested. The loader logged "MemoryOverwriteRequestControl read [00] (attributes 0x7) and is now [01]". No row rides this boot as a shared member. boot.testcases.complete_ms = 136 ms, within its record.

Proof lines:

  • "Proof: the last boot left no stop mark": every flash rewrites the log partition, so no metal run ever sees a second pass.
  • "Proof: slot A's image is written down".
  • The floor raise on the next pass is the guest row a_clean_stop_proves_its_image.

Overwrite cost on the second boot: not readable from this run.

  • The request this boot set takes effect at its reset into Ubuntu, and no ToyOS stamp sees that boot.
  • Back in 48 s, and that figure is mostly the boot itself.
  • The loader's first stamp was 10.183 s. It follows Ubuntu's reboot, which this firmware treats as orderly, so that pass reads [00]. That stamp is 0.30 s above the same pass in the earlier main-flashed run (9.885 s), 0.43 s above stage 1's (9.753 s), and 0.55 s above the 148-log median (9.63 s). That is within the spread of those passes, not the 1 s step stage 1 measured after a ToyOS reset.
  • So the ~1 s cost stands on stage 1's single paired reading (pass after ToyOS reset 10.725 s against 9.753 s the same run).

Not judged here: test_rs_wake_onto_busy rode the boot's job list and exited non-zero (64). It also did so on both earlier T14 boots: code 9 at stage 1, where only the overwrite request was new, and code 53 on the main-flashed run. Its row, wake_onto_busy, was not selected.

The bench record still holds 167 numbers this run measured nothing for. Among them are the deleted panel_us and panel_max_us; a name is deleted to re-record it, so those entries are now dead.

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #1196 at 8aee19205 (base ec8912181)

Architecture

The shape matches the rulings. The loader owns the one UEFI write the wipe needs. The black box, the chain pass, BootNext, the attempt count, death marking and its fallback are all deleted. Proof moves to a record the loader writes plus a mark logkeeper writes, and the floor is still raised only to the version in a re-verified signed header. Code changes come to +729/−5942 lines of .rs/.toml (+965/−6545 overall). New production code: overwrite.rs (60), proof.rs (129), about 40 lines in logkeeper, and bootorder.rs, which is moved code. I found no remaining code reader of the black box. No reference to the nine closed records dangles.

MOR (loader/src/overwrite.rs) is correct. I checked it against the implementer's copy of TCG PRAM 1.2 (11/18/2025):

  • The GUID matches §4.1.1. NV|BS|RT and the bit layout (ClearMemory bit 0, DisableAutoDetect bit 4, reserved bits written 0) match §4.1.2 Table 1.
  • "The OS loader does not create the variable" is §4.1's own sentence, so not creating it is right.
  • The firmware clears the bit after the overwrite (§4.1.3), and the OS is expected to clear it on a normal shutdown. That supports "set on every pass, never cleared".
  • Auto-detect is §3.3 and the lock is §4.2. The section numbers are right.
  • A request another OS left is logged and rewritten as 0x01. On conforming firmware it reads [00], which the T14 shows.

Required

  1. kernel/src/arch/x86_64/idt/timer.rs:63: a Rust-ABI function is called from naked asm. On main, deadline = sym crate::deadline::poll_in_kernel named an extern "C" fn. This PR points it at crate::deadline::poll (kernel/src/deadline.rs:114), a plain pub fn with the unspecified Rust ABI. That is the shipping arm (cfg(not(feature = "mask-windows"))), and every other sym in this file is extern "sysv64". The call happens to work because the function takes no arguments, but nothing guarantees its clobbers match the sysv64 set this stub saves. Fix: an extern "sysv64" fn wrapper in timer.rs, as ring0_tick already is under mask-windows. That keeps generic deadline.rs free of an x86 ABI.

  2. Any program that holds /log can write the mark and the record, and the PR says only logkeeper and a stopping boot can.

    • Every declared row's view holds /log read-write (lib/manifest/src/lib.rs ROLES and a_declared_row_sees_the_whole_tree_but_state_and_of_state_its_own). logkeeper's own store.rs header says "/log is userland-writable".
    • So any program can create stopped at any time during a boot. A boot that later panics or hangs is then credited.
    • Any program can also rewrite booted to name the other slot. slot::proven re-verifies only that the digest is the slot's current signed header. The floor then rises to any signed image present in either slot, even one never booted.
    • The floor cannot be lowered by design, so the effect is permanent. Older signed images are refused. It cannot enable a rollback, but it is a permanent denial of service that any program on the machine can cause. Before this PR, only the kernel could write DONE.
    • These claims are now false:
      • lib/update/src/policy.rs:4-7: "raises it only for an image whose boot logkeeper marked stopped cleanly".
      • loader/src/proof.rs:6-7: "a boot that dies before its stop leaves none".
      • lib/logstream/src/lib.rs STOPPED's doc.
      • docs/src/architecture/boot.md:56-59.
      • The decision comment's "Trust is unchanged".
    • Required: correct those claims, and file the authority gap with an owner and an exit. One exit: only logkeeper's view may write the mark and only the loader the record. Another: loader stage 5's slot-table flag supersedes them.
    • Orchestrator: the ruled design put the mark on the log partition. Whether the floor may rest on a file every program can write is a security decision. Confirm it with the owner if that was not part of the ruling.
  3. A hang after logkeeper's flush now passes the metal verdict and raises the floor, and nothing records that.

    • In system/supervisor/src/stopping.rs halt, the order is: log.flush() (which writes the mark), then sync_files(), stop_drivers(), the kernel's quiesce, the census and the reset.
    • A wedge anywhere in that window ends at the deadline. The machine comes back, and the volume carries Boot: complete, the supervisor's stop line and stopped. So tests/metal boot_verdict passes it, and the next ToyOS pass would credit the image.
    • On main, the missing DONE caught exactly this. back_secs is printed (tests/common/metal.rs:1060) but not judged, and "the machine came back" does not tell a deadline reset from a clean one.
    • Hangs before the stop line are still caught (Unfit::Unfinished). Not coming back at all is still Refusal::Silent.
    • Required:
      • State this blind spot in build/bootlog.rs's header, which is meant to state the contract once.
      • Add the come-back half the ruling names. The header currently names only the log partition.
      • Add this window to issues/a-crash-record-survives-a-reset-outside-ram.md's list of what was lost, beyond "the records a stop writes after the flush". The stop path has history here (a-t14-boot-said-rebooting-and-the-host-never-came-back.md, the-t14-hung-after-rebooting-with-its-i219-faulted.md).
  4. Stale records the change directly affects:

    • issues/the-machine-updates-itself-without-ubuntu.md:18-21 still says the loader boots the other slot where the marked one "died on its last boot".
    • issues/the-loader-does-only-what-must-precede-the-handover.md:40 still names "the attempts file", which this PR deletes.
    • Line 348 still requires "a slot that dies falls back on its own", which contradicts the fix-forward ruling. If the agent recording that ruling owns that stage, say so in the PR. Otherwise fix it here.
    • docs/src/architecture/boot.md:22-24 and diagnostics.md:32-34 say a reset leaves nothing in RAM. That holds only where the firmware defines and honours the request. The loader's own "defines no MemoryOverwriteRequestControl" line exists for the case where it does not. Qualify both.
  5. Evidence still missing before landing:

    • The AMD boot. memory_overwrite_requested is registered for the AMD bench, and the verdict contract changed for every metal row.
    • Host checks at the head. The last host run is at 2ae221e53, before 395c1cd62 (logkeeper) and e6f9abab7 (tests/common). The draft's CI was skipped, so making the PR ready covers this.

Suggestions

  • A stale record can pair with a fresh mark.
    • Case: take deletes the mark but fails to delete booted. Then cleared is false and nothing new is written (loader/src/main.rs:880-890), so the old record survives.
    • The next boot's clean stop then credits the image the old record names, not the one that ran. The guard covers only the reverse case.
    • Cheaper and simpler: the record has a fixed length, so overwrite it in place on every pass where the mark is gone, and drop the delete.
    • Pure decision logic could also move into lib/update with host tests: which of record and mark is present, and what the pass may write. That would have exposed this case. Today only the guest row exercises it.
  • lib/update/src/record.rs keeps two representations nothing uses. encode(None) / letter == 0 was the "record of no boot", and no writer produces it now. Booted.version is written and decoded but never read, since the floor uses header.version. Decode to Booted and drop both.
  • The deadline does not need a crash record for its oracle. A wedge staged in a job (before the stop line), on a boot whose only bound is the deadline, is judged by Boot: complete, no stop line, no mark and the machine coming back. ended_by_its_deadline already judges that shape for usbload. That could bring back a "the deadline fires" metal row now rather than with the crash-record track.
  • kernel/src/arch/x86_64/idt/nmi.rs:3 is an unwrapped line of about 150 characters.
  • Handoff note: ps2server::armed's census check is vacuous on metal. Record that rather than leave it in the handoff.
  • For the orchestrator, not the branch: the published r4-t14-flasher/loader.log carries the T14 disk's NVMe EUI-64 in its device paths (NVMe(0x1,AC-E4-…)), which is a hardware identifier.

Evidence checked

  • Guest runs:
    • 42091551186 (e6f9abab7): a_clean_stop_proves_its_image PASS; the log shows "stopped cleanly" and the floor raised.
    • 42091556713: the negative control (mark written under another name) is red with "left no stop mark". That is a real control for this claim.
    • 42094494342 (ec8912181): virt_reboot_refused_without_psci STALLED on main too, so the filed sighting is independent of this change. The AArch64 trap change (irq(from_el0), one deadline::poll) is a straightforward deletion, and its six rows passed at 2ae221e53.
  • T14 r4: verdict passed, back_secs 48; the judge passes memory_overwrite_requested, exit 0; loader.log reads [00], then [01] with attributes 0x7.
  • The ~1 s wipe cost rests on stage 1's one paired reading, and the PR says so.
  • 395c1cd62 is correct: the mark is removed on RESUME only where the flush wrote it, and a failed removal is logged as a credited death.
    • One window remains: between the refusal and the removal, a death reads as clean. It is the same class as finding 2.

SEND BACK

Japabu added a commit that referenced this pull request Nov 5, 2026
… and the metal verdict judges the come-back

- Only the row that keeps the log (it holds the log's origins) has /log
  read-write; every other declared row's view holds it read-only
  (toyos_manifest::Program::view). No program but logkeeper, the supervisor
  and the loader can write the stop mark or the loader's record, so none can
  credit a boot or name another slot's image for the floor.
- The mark is written as late as the log volume is still written: the
  supervisor's stop flushes the log, syncs its files, then asks logkeeper
  for the mark (MARK/MARKED) before it stops its drivers, the log volume's
  among them. A hang after that is the one window still credited; it is
  named in build/bootlog.rs's contract and in the crash-record track.
- The metal verdict judges the machine coming back: back_secs is a recorded
  number per boot, held to its bench record like complete_ms, so a stop the
  deadline ended comes back late and reds. build/bootlog.rs states the
  contract once: the log partition and the come-back, and the window
  neither sees.
- The loader's record is overwritten in place once the mark is gone, rather
  than deleted and rewritten, so no old record pairs with a fresh mark;
  record.rs keeps one form (no "no boot" record, no version nobody reads).
- The x86 timer stub calls the deadline poll through an extern "sysv64"
  wrapper, as every other symbol it calls is.
- Records: the slot fallback and the loader track's stage 7 exit under
  fix-forward, the attempts file, the manual's overwrite claims qualified to
  firmware that honours the request, the authority claims corrected, and the
  vacuous metal census check in ps2server filed.

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

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Review r1 response (d62b284e0, checks fixed in 114babfe8), following the orchestrator's rulings on the required findings:

  1. Timer stub ABI. The Ring 0 branch now calls ring0_deadline, an extern "sysv64" wrapper in timer.rs, the way ring0_tick already does under mask-windows. deadline.rs stays free of any x86 ABI.

  2. The proof is fixed, not just documented.

    • Only the row that keeps the log has /log read-write. Every other declared row's view holds it read-only (toyos_manifest::Program::view). The row is identified by what it holds (the log's origins acceptor, which only one row can hold), not by its name.
    • So stopped and booted can be written only by logkeeper, the supervisor (whose view is the whole tree) and the loader. No other program can credit a boot or name the other slot's image.
    • The grant machinery has no way to exclude a subdirectory from a /log grant. Making /log read-only for everyone else is the form of the ruling that the existing views carry.
    • The one other writer in the tree is metalprobe's usbwrite, whose device boot no row registers.
    • New host test: only_the_log_keeper_writes_the_log. a_declared_row_sees_… now expects /log read-only.
    • Corrected: policy.rs, proof.rs, STOPPED's doc, store.rs's header and boot.md now state the real authority.
    • Correction to my decision comment: "Trust is unchanged" was false at 8aee19205, because every row could write /log. It holds now.
  3. A hang after the flush.

    • The mark moved out of the flush. The supervisor's stop now runs: flush → sync files → MARK/MARKED (logkeeper writes and syncs the mark) → stop drivers → kernel.
    • "After the drivers stop" is not reachable: the log volume's storage driver is one of the drivers stopped, so the mark is the last write before that.
    • back_secs is now a recorded number per boot, held to the bench record's ceiling like complete_ms. A stop the deadline ended comes back late by about the deadline, and reds.
    • Caveat: the record has no back_secs names yet, so the first run under this harness records them rather than judging them.
    • build/bootlog.rs's header states the contract once: the log partition plus the come-back. It names the window neither half sees: drivers' stop, kernel quiesce, reset.
    • That window is added to issues/a-crash-record-survives-a-reset-outside-ram.md.
  4. Stale records.

    • the-machine-updates-itself-without-ubuntu.md: the fallback is now verification refusals only, under fix-forward.
    • Loader track: line 40 no longer names the attempts file, and the stage 7 exit no longer has a dying slot fall back.
    • boot.md and diagnostics.md: qualified to firmware that defines and honours the request.
  5. Evidence at the head 114babfe8 (https://github.com/ToyOSOrg/evidence/tree/06a9cad9251a22f0227ee555f9d41a020518fc8e/r5):

    • cargo run -- --ci host: 108 steps, all green.
    • --build-only for x86-64 (at d62b284e0; 114babfe8 changes only host checks) and for aarch64: both exit 0.
    • Guest run 42103506203 on the new stop order: a_clean_stop_proves_its_image, machine_shutdown_short_stop, session_lifecycle and screen_fatal_halt_composited all pass.
    • The AMD bench waits for its power cycle and will use this branch as the flasher.

Suggestions taken:

  • The record is overwritten in place once the mark is gone, instead of deleted and rewritten.
  • record.rs keeps one form: no "no boot" record and no version. Chosen.version, which had no reader left, is gone too.
  • nmi.rs:3 is wrapped.
  • The vacuous ps2server census check is filed as issues/ps2servers-census-check-reads-a-log-that-carries-no-census.md.

Suggestion not taken: a separate deadline row. The "a deadline ends the boot" shape already has one, the_disk_survives_a_reset_under_load. Its verdict needs Boot: complete, no stop mark and the machine coming back, and its judge needs no stop line.

Evidence leak. The T14 NVMe EUI-64 appeared in the device paths of two published loader.log files (r2, r4).

  • The pr-1196 evidence branch is rewritten with it masked as <eui-64>; it is now at 0dbc2735. I scanned every published file: no other EUI, serial, MAC or model string remains.
  • The old commits (b50a8e70, ae32de64, and 06227c2e/72707b60 above them) can still be fetched by SHA from GitHub until it garbage-collects them. Purging them needs a GitHub support request; that is the orchestrator's to file.
  • The PR body's links are repinned.

Requesting a focused review of 8aee19205..114babfe8.

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2 of #1196: 8aee19205..114babfe8

Architecture

This round adds 223 lines and removes 117 across 22 files. The new production code:

  • the MARK/MARKED exchange (about 25 lines in the supervisor, 15 in logkeeper);
  • Program::keeps_the_log and the /log arm of view (about 10 lines);
  • the timer wrapper (6 lines).

record.rs, slot.rs and proof.rs get smaller. The rest is comments, records and planted test data.

Deviation (a), /log read-only for every row except the log keeper's: accepted. A grant covers a whole subtree, so it cannot exclude a subdirectory. Narrowing the role's grant per row is therefore the existing mechanism's way to give logkeeper alone write access. Nothing else in the tree needs write access except metalprobe (below). I checked each way another program might still write stopped or booted:

  • Declared rows. Program::view grants /log write access only where keeps_the_log. The supervisor (supervisor/src/main.rs:3270) refuses a second holder of log-origins, and HOLDINGS is the signed image's alone.
  • Installed packages. Their view has no /log. A folder grant is confined to the session's home (grants.rs header, folder).
  • Launches and direct children. A launch is minted from its row's view. A child spawned directly inherits a duplicate of its parent's svc, so it can be no wider than its parent.
  • Exported connections. These inherit the exporter's writes (fileserver/src/main.rs:1336).
  • Read-only connections. rights::changes refuses every write, create, unlink, rename and link before the volume sees it. The policy is closed by default.
  • Handles held earlier. Views are minted per start and never widened later, so no program holds an older read-write handle.
  • Path aliases. /log is its own role and partition, and every other role serves a different volume, so no path reaches the log partition another way.
  • The supervisor's namespace. It is the whole tree, read-write. A spawn that endows no svc would inherit it. Every row the supervisor starts has a non-empty view or a minted port in view, so I found no row that gets it.

What remains is logkeeper, the supervisor, the loader, and the storage stack serving the volume (file server, block service, disk driver). The storage stack can write those blocks by construction.

Deviation (b), the mark written before the drivers stop: accepted. The log volume's storage driver is one of the drivers stopped, so no later point can write the mark. The remaining window is named once, in build/bootlog.rs's header, and in a-crash-record-survives-a-reset-outside-ram.md. The policy.rs and STOPPED comments state it honestly.

Timer. ring0_deadline is an extern "sysv64" wrapper in timer.rs under cfg(not(feature = "mask-windows")), mirroring ring0_tick, and deadline.rs stays ABI-free. Resolved.

back_secs. It is judged per boot through back.measured("boot.{label}.back_secs"), against timings::ceiling of the bench record.

  • The planted readbacks carry it.
  • a_reading_past_its_record_fails_and_moves_nothing reds a 40 s come-back against a recorded 19 s and leaves the record unmoved.
  • a_name_two_owners_measured_is_refused (4 names) shows a first run records it rather than judging it, as timings.rs's header states.
  • Is the bound sensitive enough? The deadline is armed from boot and is at least 2 × (60 s + the members' share). The T14's testcases boot came back in 48 s, a ceiling of 96 s. A hang that the deadline ends comes back after at least 120 s plus firmware time, so it reds there. See suggestion 1 for the general condition.

Records and the stale-record fixes. the-machine-updates-itself-without-ubuntu.md, the loader track's lines 40 and 348, boot.md and diagnostics.md are corrected as r1 asked.

record.rs. The record now has one form, with no "no boot" record and no version. Chosen.version is gone too. The layout is still 64 bytes, and decode ignores bytes 17..32. The in-place overwrite reduces the stale pairing to a failed write, which the loader now names in its log line.

Required

  1. system/supervisor/src/main.rs:441: the supervisor panics on a late MARKED. answered tolerates a late FLUSHED ("A flush answered late is counted whatever is waited for"), but not a late MARKED. The sequence:

    • mark() times out after FLUSH_BOUND (5 s). A sync on a slow stick can take that long.
    • The stop goes on and the kernel refuses it. The refused path is real and tested (virt_reboot_refused_without_psci, halt's resume).
    • logkeeper then handles the queued MARK and sends MARKED.
    • At the next stop, flush() → answered(None, …) pumps that MARKED. It reaches panic!("supervisor: logkeeper answered a flush with frame type 11"), and the supervisor's panic takes the machine down.
    • A second variant: when answered(Some(MARKED)) reads a stale MARKED, it returns before the new mark is written, and the drivers stop under it.

    This is new in this round. On main, a late answer was by construction the same type that was awaited. Fix: count marks still unanswered as flushes are counted, so either kind of late answer is drained. This needs no new test.

  2. issues/the-device-boot-is-flashed-by-no-metal-row.md: metalprobe's usbwrite now fails, and no record says so. tests/guest/metalprobe/src/usb.rs:50-51 writes /log/metal-write.bin. Its row's view now holds /log read-only, so the exit this issue names (a row that rides the device boot) will hit ReadOnly with no explanation. The review reply mentions this, but the repository does not. Required: point the probe at a path its row may write, or add a line to that issue (or to metalprobes-usb-read-is-answered-from-fileservers-cache.md, which also describes the probe writing /log).

  3. Two comments state the authority or the invariant falsely.

    • lib/logstream/src/lib.rs:143 and loader/src/proof.rs:8 say "Only logkeeper's view holds /log read-write". The supervisor's namespace is whole_tree(), read-write, which policy.rs, store.rs and boot.md say correctly. Keep the statement in one place (Program::view, which already says it) and refer to it, or state it the same way everywhere.
    • loader/src/proof.rs:6-8: "a mark found here is always the boot's that the record names". This is false when proof::write fails, and main.rs:305 now logs exactly that case ("may credit the image the last record names"). Qualify it or drop it.

Suggestions

  1. When back_secs catches a hang. A hang after the mark reds only when the deadline is more than twice the boot's run time plus the come-back's firmware and Ubuntu time (D > 2R + F, where D is the deadline, R the run time and F that fixed come-back time). This holds for the T14's testcases boot (at least 120 s against 48 s back). It does not hold in general for a list whose run nears its bound. The header's "told apart only by coming back late" is accurate. The reply's "comes back late … and reds" is true only for those boots.
  2. lib/manifest/src/lib.rs:1087-1100. The doc of only_the_log_keeper_writes_the_log promises controls the test does not have: "no row that merely receives its wire or is named like it". The uart row receives nothing, and no row named logkeeper lacks the hold. Either add those two rows or shorten the doc to what is checked.

Evidence checked

  • Guest run 42103506203. Its headSha is 114babfe8. a_clean_stop_proves_its_image, screen_fatal_halt_composited, machine_shutdown_short_stop and session_lifecycle passed, 4 of 4, on the new MARK stop order.
  • r5/ci-host-5.log. "Host(None): 108 step(s), all green", exit 0. It started at 20:27:12Z, six seconds after 114babfe8 was committed. The log does not name its revision, but the timing and r5's labelling fit.
  • r5/build-r1.log and build-r1-a64.log. Both exit 0 and were run after d62b284e0. 114babfe8 changes only tests/checks/metal.rs, which the host run covers.
  • Not yet: the AMD boot, which is pending a power cycle, and the first T14 run that records back_secs. Neither blocks this review. The orchestrator owns them.

SEND BACK: required items 1 to 3. Each is small.

Japabu added a commit that referenced this pull request Nov 5, 2026
…t, true comments

- The supervisor counts unanswered stop marks as it counts flushes
  (toyos_logstream::Unanswered), so a MARKED that arrives after its wait was
  given up is counted off the marks during a later flush or mark rather than
  panicking the supervisor or ending a newer mark's wait before that mark is
  written. Host test: a_late_answer_is_counted_off_its_own_kind.
- metalprobe's usbwrite and usbread write /boot on the same disk, since /log
  is the log keeper's alone; both device-boot records say so.
- STOPPED's doc and loader/src/proof.rs refer to Program::view for who may
  write /log, and proof.rs no longer claims a found mark always pairs with
  the record when that pass's write failed.
- build/bootlog.rs states when back_secs catches a hang after the mark: where
  the deadline is past twice the boot's recorded come-back.
- only_the_log_keeper_writes_the_log carries its two controls: a row that
  receives the log wire, and one named logkeeper that holds nothing.

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

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2 response, head a261141c4 (dc7f1363f plus one removed import):

  1. Late MARKED.
    • The supervisor counts unanswered marks the way it counts flushes, in toyos_logstream::Unanswered. Every answer is counted off its own kind.
    • A late MARKED is therefore drained during a later flush or mark rather than panicking the supervisor. A stale MARKED counts off the stale ask, so it no longer ends a newer mark's wait before that mark is written. An answer nothing asked for is still refused loudly.
    • Host test: a_late_answer_is_counted_off_its_own_kind.
  2. metalprobe.
    • usbwrite and usbread now write /boot on the same disk, which their row's view writes.
    • the-device-boot-is-flashed-by-no-metal-row.md and metalprobes-usb-read-is-answered-from-fileservers-cache.md say so.
  3. Comments.
    • STOPPED's doc and proof.rs refer to Program::view for who may write /log.
    • proof.rs qualifies the mark/record pairing with the failed-write case the loader logs.

Suggestions taken:

  • build/bootlog.rs states when back_secs catches a hang after the mark: where the deadline exceeds twice the boot's recorded come-back. That is true of a short list, not of one running near its bound. My r1 reply overstated it.
  • only_the_log_keeper_writes_the_log now has both controls: a row that receives the log wire, and a row named logkeeper that holds nothing.

Evidence (https://github.com/ToyOSOrg/evidence/tree/3fb30fc8722101623b5c488bb9b2b7f5bcf72662/r6):

  • cargo run -- --ci host at dc7f1363f: 108 steps, all green. a261141c4 changes one import in the supervisor, which the host run does not build.
  • --build-only at dc7f1363f failed on that unused import (build-r2.log). The import is removed in a261141c4, where x86-64 and aarch64 both exit 0.
  • Guest run 42109762432 at a261141c4: a_clean_stop_proves_its_image, machine_shutdown_short_stop, session_lifecycle and screen_fatal_halt_composited all pass.

Requesting a focused review of 114babfe8..a261141c4.

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Review r3 of #1196: 114babfe8..a261141c4

Assessment

This round is small, about +117/−47 across 9 files. In production it replaces the supervisor's single flush counter with toyos_logstream::Unanswered, which counts flushes and marks separately, and it moves metalprobe's two files. The rest is comments, two issue records, and one manifest test that gains its controls.

The counter resolves r2's item 1. The metalprobe move does not resolve item 2. It points the probe at a volume that is served read-only.

r2 items

1. Late MARKED: resolved. I checked the code rather than the reply.

  • A late answer is drained against its own kind. answered passes every FLUSHED and MARKED frame to Unanswered::answer, which decrements that kind's count. A MARKED that arrives during a flush wait is counted off marks, and the loop continues until flushes reaches zero (system/supervisor/src/main.rs:449-460).
  • A stale answer cannot end a newer wait early. A wait returns only when every request of its kind has been answered. logkeeper handles the origins connection in order: FLUSH returns to flushed() before the next frame is read, then MARK → mark(), then RESUME (system/logkeeper/src/main.rs:357-361). It sends exactly one answer for each request (:705, :723), so the newest request's answer comes last. With a stale mark and a new one, marks == 2, and the stale MARKED leaves 1.
  • Can an unrequested answer panic? Only on a real protocol violation. Each count is incremented only after its signal succeeds (:424-426, :437-439). flush() and mark() are the only senders of FLUSH and MARK. Every FLUSHED or MARKED therefore has a counted request, and only an extra answer from logkeeper reaches the panic. That is a bug in the trusted peer, so panicking on it is correct.
  • The test. a_late_answer_is_counted_off_its_own_kind covers the late-mark drain, a stale mark next to a new one, and both kinds of refusal, including a frame that is no answer at all. It is focused and runs on the host.

3. Comments: resolved. STOPPED's doc and proof.rs now defer to Program::view for who may write /log. proof.rs names the failed-write exception that main.rs logs.

Suggestions taken. build/bootlog.rs no longer promises that every hang shows up as a late come-back. only_the_log_keeper_writes_the_log now has both negative controls: uart actually receives the wire, which the test asserts, and a logkeeper-named row with no hold is refused. Both resolve r2's suggestions.

Required

  1. tests/guest/metalprobe/src/usb.rs:5-9,42-44,51-52: /boot is read-only, so usbwrite and usbread still fail. The new comment and both issue records state the opposite.

    • Program::view marks /boot write: true, but only the view does. Writes are refused further down:
      • The boot role's block grant is writes: false (system/supervisor/lifecycle/src/storage.rs:25, tested at :96).
      • The file server mounts it with fat_on(disk, role == Role::Log), which is unwritable (system/fileserver/src/main.rs:345). Its pager allows 0 dirty pages (:294).
      • FatVolume refuses a create on an unwritable mount with ReadOnly (system/fileserver/src/fat.rs:244). Its doc says "the one read-only grant is the BOOT server's, whose volume is mounted unwritable" (:49-53).
      • The supervisor's sync_files skips boot (system/supervisor/src/main.rs:2130).
    • So File::create("/boot/metal-write.bin") fails with ReadOnly, as /log did after r1.
    • The reply's "which their row's view writes", the header's "/boot is the one place on the boot disk a job may write", and the-device-boot-is-flashed-by-no-metal-row.md:15-17 ("moved … to /boot") are all false.
    • The BYTES doc's "beside the slot's own files" is also wrong. The storage test shows that /boot serves the booted disk's ESP (reached("boot", …) == its_esp), not a slot volume.
    • Safety. Even if /boot were writable, it would be a bad target. The ESP carries the loader and toyos/log.guid. The device config says this boot's bound can reset the machine inside a transfer. A torn FAT update there could stop the stick from booting, whereas a torn write on the log volume costs only a log.
    • Fix. The stick has no volume this row can write. The honest change within this PR's scope is what r2 offered as its alternative:
      • revert the probe to its /log paths, or leave them, but do not claim they work;
      • state in the-device-boot-is-flashed-by-no-metal-row.md that usbwrite/usbread now fail with ReadOnly because /log is logkeeper's, and that the probe needs a writable place on the stick, a decision for whoever revives that row;
      • restore metalprobes-usb-read-is-answered-from-fileservers-cache.md to name /log (or the record above), not /boot.

    No guest or metal run is needed. This is settled by inspection.

Suggestions

  1. lib/logstream/src/lib.rs:728-750. The new test sits between use super::*; and the module's other use lines. Move it below them.
  2. lib/logstream/src/lib.rs:140-166. Unanswered is supervisor-only state that lives in the shared protocol crate, apparently so it can be host-tested, since the supervisor has no host tests. It is defensible as "the protocol's answer accounting". Keeping it in the supervisor would also be fine. Either is acceptable.

Evidence checked

  • Guest run 42109762432. headSha is a261141c4. The rows job succeeded, covering the four stop-path rows named in the reply.
  • dc7f1363f..a261141c4. This only drops FLUSHED and MARKED from the supervisor's import, so host 108/108 at dc7f1363f carries over. The x86-64 and aarch64 build claims at a261141c4 are consistent with that change. I took the r6 logs as the reply cites them. I did not re-open them, because the blocker above is independent of them.
  • No run exercises metalprobe's usbwrite/usbread. The issue record admits this. The required finding rests on code, not on a run.

SEND BACK: required item 1.

Japabu added a commit that referenced this pull request Nov 5, 2026
… fails

/boot is read-only at every layer below the view, and is the ESP: no place
for a probe to write. The probe keeps its /log paths; its header and
the-device-boot-is-flashed-by-no-metal-row.md say usbwrite and usbread
now fail with ReadOnly under the /log rule, and that the probe needs a
writable place on the stick, for whoever revives that row. The cache record
names /log again. The late-answer test sits below the module's uses.

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

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator verification of the r3 fix (a261141..7bbfecf):

  • metalprobe's two paths go back to their main-era /log/metal-*.bin values, and its header now states honestly that usbwrite and usbread fail with ReadOnly under the /log rule. The device-boot record says so, and the cache record's wording is restored.
  • The only other code change moves three use lines inside a test module. Everything else is comments and records.
  • The r3 required finding (Memory overwrite on every boot; the black box goes, and a clean stop proves an image #1196 (comment)) is resolved. metalprobe compiles; logstream 20/20.

Landing waits only on the AMD bench boot (with this branch as flasher), which is pending a power cycle of that bench.

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

AMD metal for #1196: one testcases boot of the image staged at dd878948a, flashed by this branch's toyos-metal at 7bbfecf61 and run by the orchestrator. Evidence, with machine and disk identifiers masked: https://github.com/ToyOSOrg/evidence/tree/7b03f6c114e173cc65d85f80462a13101d1e190a/r7-amd

Boot verdict: passed (rc 0) under the new contract: the volume carries the boot record, the supervisor's stop and stopped. The machine came back in 36 s.

Judged with this branch's harness at 7bbfecf61 (--metal-readback over the readback, exit 0): PASS memory_overwrite_requested. boot.testcases.complete_ms (242 ms) and the new boot.testcases.back_secs (36 s) are this bench's first readings, so they are recorded, not judged. Nothing past a record.

What the AMD firmware did with the request:

  • It defines MemoryOverwriteRequestControl with the spec's attributes 0x7 (NV|BS|RT).
  • The loader's pass after Ubuntu's reboot read [00].
  • It accepted [01].
  • Whether it then overwrote memory is not observable from this run. The overwrite would happen at this boot's reset into Ubuntu, and no ToyOS stamp sees that boot. Unlike the T14, this bench's loader states no counter rate (--.--- on every line), so even a ToyOS pass after a ToyOS reset would carry no firmware time. The one firmware figure is the kernel's own "power-on to loader 3585 ms", and it belongs to the pass after Ubuntu's reboot.
  • [00] after Ubuntu fits either reading: Ubuntu's shutdown cleared the request, or the firmware did. I have not read the bench's Ubuntu configuration.
  • So on AMD the request is confirmed set. The overwrite cost stays unmeasured. Measuring it needs a ToyOS pass after a ToyOS reset, timed by something other than the loader's counter.

Proof lines: "Proof: the last boot left no stop mark", which is expected after a reflash, and "Proof: slot A's image is written down".

With this, the PR's evidence is complete:

  • T14 and AMD metal boots under the PR's flasher;
  • host checks green at dc7f1363f, unaffected by the commits after it;
  • both builds and the stop-path guest rows at a261141c4;
  • 7bbfecf61 is text, a path revert and a test move, compile-checked.

Japabu and others added 11 commits November 5, 2026 23:46
Every loader pass now sets the TCG MemoryOverwriteRequestControl variable
(vendor e20939be-32d4-41be-a150-897f85d49829) to 0x01 where the firmware
defines it, so firmware that honours the request overwrites memory before
the next boot: whoever resets a ToyOS machine into a medium of their own
reads nothing the last boot left in RAM. This is the line of Linux's
reset-attack mitigation ToyOS declined under the 2026-10-03 "Keep crash
records" ruling, which the owner reversed on 2026-11-05.

The facts are the TCG PC Client Platform Reset Attack Mitigation
Specification 1.2's: the firmware creates the variable and an OS loader
never does (section 4.1), so a firmware that has none is told nothing; the
byte is ClearMemory in bit 0 with DisableAutoDetect and the reserved bits
0, under NV|BS|RT (4.1.2); the firmware clears the request once it has
overwritten memory (4.1.3), so the loader sets it again on every pass.
DisableAutoDetect stays 0, so firmware that can tell an orderly shutdown
may skip the wipe (3.3). No kernel makes a runtime SetVariable, so ToyOS
never clears the request at a clean shutdown.

The pass logs the value it found, which on the T14 is the request Ubuntu
sets on every boot, and the new metal row memory_overwrite_requested holds
the T14 and the AMD bench to the firmware defining the request and taking
the write. It rides the testcases boot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loader now sets MemoryOverwriteRequestControl on every pass, so:

- the-loader-never-sets-the-firmwares-memory-overwrite-request (rejected
  under "Keep crash records") is resolved and deleted;
- a-memory-overwrite-request-ubuntu-left-set-stays-set-under-toyos is
  deleted: its exit cleared a request another system left set so crash
  records would survive. A request another system set is now the one ToyOS
  makes itself; the loader logs the value it found and writes 0x01;
- the security track no longer declines that line of Linux's;
- toyos-explains-itself records the owner's reversal of "Keep crash
  records" (2026-11-05).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s mark

The loader's memory-overwrite request has the firmware wipe RAM after a
reset, so nothing the black-box page carried survives it. Measured on the
T14 at 1e459d3 (#1196): the pass after the kernel's reset read the request
back as [00] and found no record, and began at 10.725 s after reset against
9.753 s for the pass after Ubuntu's reboot and a 9.63 s median over 148
earlier passes: the firmware overwrote memory, in about a second.

The owner ruled on 2026-11-05 ("just wipe it ... if that means we remove the
blackbox then so be it") and the same day made updates fix-forward, with no
automatic fallback to the previous slot. This removes the page and every job
it did:

- Proof. A boot proves its image by stopping cleanly: logkeeper writes the
  stop mark (toyos_logstream::STOPPED) on the log partition once a stop's
  flush has made the log durable, and takes it back on RESUME. The loader
  records the image it boots (lib/update's record, now just that image) and
  the next pass takes record and mark, raising the anti-rollback floor to the
  verified header's version only where both are there. Death marking, the
  dead-slot fallback, Refusal::Died, the attempt count and its hang bound go.
- The boot chain. The loader no longer points BootNext at itself, and every
  pass boots a kernel: the pass after a reset that read the page, ended the
  chain and reset the machine is gone, with loader.log's append mode.
- The page. lib/blackbox, kernel/src/blackbox.rs and loader/src/blackbox.rs
  go, with everything only they used: the fault entry's register seal, the
  panic's and the stop's seals, the stop's log tail, the census's sealed
  form, the deadline's and the hard-lockup detector's wedge reports and the
  state kept only for them (each CPU's last kernel pc and NMI frame, the lock
  a spin waits on, the boot phase), the x86 cache write-back in both, and
  the wedge-before-reset, hard-lockup-probe and blackbox-foreign-identity
  arms. The deadline and the detector still reset the machine.
- The harness. A metal boot is judged by what survives it: the log
  partition (logkeeper's files and the stop mark) and the loop seeing the
  machine come back; build/bootlog.rs states it once. The rows whose oracle
  was the page go (blackbox_unclaimed_page, blackbox_foreign_record,
  blackbox_done_chain, irq_census_conservation, boot_deadline_ends_a_wedge,
  watchdog_resets, hard_lockup_ends_a_deaf_cpu), with the panel timings and
  the stop-record check; machine_reboot, watchdog_fed, the disk-load row and
  amdvi_usbd are judged off the volume. screen_loader_clears, which held a
  loader pass that booted no kernel, goes, and its gap is filed.

Records: the follow-up track for a crash record that survives a reset
outside RAM is filed (ERST, a UEFI variable, a disk dump; the owner chooses).
Seven records whose subject was the page close; the rest that cited it are
corrected. The manual's boot, kernel and diagnostics pages say what is true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The proof chain crosses two components and a reset: logkeeper writes the
stop mark at the stop's flush, and the next loader pass takes it with the
loader's record and raises the anti-rollback floor. No host test has the
loader's second pass on the same disk and firmware variables, and a metal
run reflashes before any second pass, so this is a guest row: a boot whose
first pass proves nothing, a reboot the guest asks for, and the next pass
naming the clean stop and raising the floor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused stop's RESUME took the mark back unconditionally, so a boot whose
/log never opened asked a file server that may never answer for a file it
never wrote. The flush now records whether it wrote the mark, and RESUME
removes it only then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The boot console the harness keeps starts at the kernel, so the first
pass's proof line is read off the loader's channel, and the second pass is
found on the console after the reboot or as the 16550 file's second pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while judging #1196, whose branch reds the row; main at ec89121
reds it alone the same way (guest-rows run 42094494342).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the metal verdict judges the come-back

- Only the row that keeps the log (it holds the log's origins) has /log
  read-write; every other declared row's view holds it read-only
  (toyos_manifest::Program::view). No program but logkeeper, the supervisor
  and the loader can write the stop mark or the loader's record, so none can
  credit a boot or name another slot's image for the floor.
- The mark is written as late as the log volume is still written: the
  supervisor's stop flushes the log, syncs its files, then asks logkeeper
  for the mark (MARK/MARKED) before it stops its drivers, the log volume's
  among them. A hang after that is the one window still credited; it is
  named in build/bootlog.rs's contract and in the crash-record track.
- The metal verdict judges the machine coming back: back_secs is a recorded
  number per boot, held to its bench record like complete_ms, so a stop the
  deadline ended comes back late and reds. build/bootlog.rs states the
  contract once: the log partition and the come-back, and the window
  neither sees.
- The loader's record is overwritten in place once the mark is gone, rather
  than deleted and rewritten, so no old record pairs with a fresh mark;
  record.rs keeps one form (no "no boot" record, no version nobody reads).
- The x86 timer stub calls the deadline poll through an extern "sysv64"
  wrapper, as every other symbol it calls is.
- Records: the slot fallback and the loader track's stage 7 exit under
  fix-forward, the attempts file, the manual's overwrite claims qualified to
  firmware that honours the request, the authority claims corrected, and the
  vacuous metal census check in ps2server filed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t, true comments

- The supervisor counts unanswered stop marks as it counts flushes
  (toyos_logstream::Unanswered), so a MARKED that arrives after its wait was
  given up is counted off the marks during a later flush or mark rather than
  panicking the supervisor or ending a newer mark's wait before that mark is
  written. Host test: a_late_answer_is_counted_off_its_own_kind.
- metalprobe's usbwrite and usbread write /boot on the same disk, since /log
  is the log keeper's alone; both device-boot records say so.
- STOPPED's doc and loader/src/proof.rs refer to Program::view for who may
  write /log, and proof.rs no longer claims a found mark always pairs with
  the record when that pass's write failed.
- build/bootlog.rs states when back_secs catches a hang after the mark: where
  the deadline is past twice the boot's recorded come-back.
- only_the_log_keeper_writes_the_log carries its two controls: a row that
  receives the log wire, and one named logkeeper that holds nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unanswered counts them now.

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

/boot is read-only at every layer below the view, and is the ESP: no place
for a probe to write. The probe keeps its /log paths; its header and
the-device-boot-is-flashed-by-no-metal-row.md say usbwrite and usbread
now fail with ReadOnly under the /log rule, and that the probe needs a
writable place on the stick, for whoever revives that row. The cache record
names /log again. The late-answer test sits below the module's uses.

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

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (0b2dec674, after #1189, #1188 and #1197). Head is 03c1042d8.

Range-diff 7bbfecf61~12..7bbfecf61 against origin/main..03c1042d8: 10 of 12 commits are identical. Two changed, both from conflict context only:

The loader's proof, overwrite and slot code is unchanged, and it applies to #1189's loader flow without conflict.

Checks at 03c1042d8:

  • cargo run -- --ci host: 108 steps, all green.
  • --build-only for x86-64 and aarch64: both exit 0.
  • Guest run 42125743161: all 7 pass. The 4 stop-path rows, plus virt_first_entry, virt_timer_preempts and virt_fatal_halts_the_others_first, because kernel/src/main.rs is shared by both architectures.

Evidence: the r8 logs could not be published. Every push to ToyOSOrg/evidence is refused right now with "fatal error in commit_refs_rules", even a push of small files to a new branch. That looks like a GitHub-side rule failure. The logs are kept locally and go up once pushes work again.

@Japabu

Japabu commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator verification of the rebase onto main (7bbfecf → 03c1042), which adds no logic:

  • git range-diff: 10 of 12 commits are identical (=). Commits 3 and 8 differ only by conflict resolution: the regenerated Cargo.lock, the kernel's reserved-region list taking main's kernel_image_* names, loader/Cargo.toml adding toyos-logstream beside main's toyos-kimage, import context following The loader is the program's, and the kernel parses no ELF #1189, and issue-record wording that takes Record the owner's design rulings of 2026-11-05 in the manual, the issues and CLAUDE.md #1197's fix-forward text. Commit 3's code hunks in loader/src/main.rs are otherwise identical.
  • At 03c1042: host 108/108, x86-64 and aarch64 builds exit 0, and guest run 42125743161 is 7/7, including the four stop-path rows and three virt_* rows for the shared kernel/src/main.rs.
  • The T14 and AMD metal (both flashed by this branch) and the review verdicts apply to this head.
  • The r8 logs could not be pushed to the evidence repo, because GitHub refused every push with a rule error. They're kept locally and will be published once pushes work.

Landing.

@Japabu
Japabu marked this pull request as ready for review November 5, 2026 23:25
@Japabu
Japabu added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 3022fd7 Nov 5, 2026
11 checks passed
@Japabu
Japabu deleted the loader-sets-mor branch November 5, 2026 23:47
Japabu added a commit that referenced this pull request Nov 5, 2026
…om the replay

#1190's logkeeper rounds r1 to r5 (1815b94, 7d45162, a99667b,
7011568, bc12850), rebased onto #1195's opener thread and #1196's stop
mark as #1195's reviewer planned.

- A refused create, append, sync or continuation is asked again
  (`policy::Retry`, 1 s doubling to 64 s). `Volume` keeps the file's bytes
  (`size`, `durable`) apart from the replay's offsets (`from`, `through`,
  `durable_through`). `catch_up` opens the part again, cut to its last
  sync, or rebuilds it whole where it is shorter, and gives it what it is
  owed from the replay (`Hub::owed(at, max)` → `store::Owed`), a part's
  worth a round, through the ordinary part-sized writes and continuations.
  A gap the replay let go of is said in the file only.
- The stop (`Volume::finish`) catches up within `STOP_CATCH_UP`, half the
  supervisor's flush bound, syncs what it wrote, and says any shortfall in
  the file and on the console; a refusal there leaves the part lost and the
  count from the last sync.
- #1195: its opener thread stays; `adopt` only takes the volume, whose
  file stands at the replay's start, so the next round's catch-up gives it
  the boot so far; #1195's own catch-up loop, `Hub::owed(&mut u64)` and
  `serve::Owed` go. A volume still opening at the stop is waited for within
  the same `STOP_CATCH_UP`, then given the boot by `finish`, or said not to
  have opened.
- #1196: the stop mark is written only over a part that is open, since a
  refused volume's file is not the boot's whole log.

Host tests in `store.rs` (27 in all): the bookkeeping through an unsynced
write, a replay gap, a refused continuation and a part cut behind its
back; a refused first create; a stop with and without time; a stop whose
reopen is refused after an unsynced write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 6, 2026
…lings

#1190's records (1e70db4, 1728915), rebased:
- logkeeper-ends-the-boots-log-on-one-refused-create-and-nothing-durable-says-so
  stays open, rewritten to what is built. Its exit is the row, which was
  green before #1196 (run 42040641298). The row frees the full volume with
  a test job, and #1196 made `/log` read-only to every row but the log
  keeper's, so the row now stalls there and needs another way to free it.
- a-file-held-across-a-file-servers-restart-answers-gone is narrowed to
  DATA reopen by inode number, waiting on a volume identity, and names the
  swapped-stick hazard of reopening by path.
- std-file-lock-answers-ok-and-locks-nothing stays with M4.
- The storage track records the DATA commit's two flushes and FUA missing
  end to end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 6, 2026
…ng rulings

#1190's records (1e70db4, 1728915), rebased:
- logkeeper-ends-the-boots-log-on-one-refused-create-and-nothing-durable-says-so
  closes on the host tests, by the coordinator's ruling (option 3).
  `store.rs`'s tests cover each case:
  - a refused first create;
  - an unsynced write with bytes torn past it;
  - a gap the replay let go of;
  - a refused continuation;
  - a part cut short behind the volume's back;
  - a stop with and without time;
  - a stop whose reopen is refused after an unsynced write.
  End to end, `a_refused_log_is_written_whole_once_the_volume_takes_it` was
  green on the guest runner before #1196 (run 42040641298 at `6405def8d`):
  the stick read back held the boot from its first kernel record, each
  CPU's records as the console carried them. That row is not carried here.
  It freed its full log volume with a test job, and #1196 leaves `/log`
  writable to the log keeper alone. Filling the volume with logkeeper's own
  old parts instead does not heal it either: retention sweeps only at open,
  before the first write, and at a rotation, which a refused part never
  reaches.
- a-file-held-across-a-file-servers-restart-answers-gone is narrowed to
  DATA reopen by inode number, waiting on a volume identity, and names the
  swapped-stick hazard of reopening by path.
- std-file-lock-answers-ok-and-locks-nothing stays with M4.
- The storage track records the DATA commit's two flushes and FUA missing
  end to end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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