Skip to content

The loader calls UEFI through its own bindings, and uefi and uefi-services go - #815

Open
Japabu wants to merge 4 commits into
mainfrom
wt/toyos-uefi
Open

Japabu wants to merge 4 commits into
mainfrom
wt/toyos-uefi

Conversation

@Japabu

@Japabu Japabu commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

The loader calls UEFI through its own bindings, bootloader/src/efi/, and uefi 0.26 and uefi-services 0.23 leave the tree with the seven crates only they pulled in (uefi-raw, uefi-macros, ptr_meta and its derive, ucs2, bit_field, uguid). This follows the owner's ruling on the dependency audit's uefi row ("Own bindings"), which replaces the loader track's plan to move to the current uefi release.

Head: ab9772596, with origin/main at c757a86b8 merged in. It is a29d650e8 plus one commit that edits only issues/the-loader-does-only-what-must-precede-the-handover.md (git diff --shortstat a29d650e8 ab9772596: 1 file, +9/−2), so no built byte differs from a29d650e8 and every reading at a29d650e8 below stands for this head. Net lines: git diff --shortstat origin/main...HEAD gives 23 files, +2020/−481. Of that, production is about 1.8k lines of efi/ (the audit estimated 1.5–2.5k), against the 91 lockfile lines and about 27k lines of crate source that leave. No test is added.

What changed, and why

  • A module, not a crate. The loader is the only user. A crate would add a manifest, a description and a workspace entry for a second reader that does not exist. A host test cannot link a no_std, no_main UEFI binary. So the layouts are checked by compile-time size_of/offset_of! asserts that run at every build of both loader targets, on the targets themselves. Every struct cites its UEFI 2.10 section:
    • system table, boot services and runtime services (§4.3–4.5);
    • configuration table (§4.6);
    • memory descriptor (§7.2.3);
    • EFI_TIME (§8.3);
    • LoadedImage (§9.1.1);
    • DevicePath and HARDDRIVE (§10.2, §10.3.5.1);
    • text output (§12.4);
    • GOP and its mode (§12.9.2);
    • SimpleFileSystem, File and EFI_FILE_INFO (§13.4–13.5);
    • BlockIo and its media (§13.9);
    • PartitionInfo (§13.18);
    • PciRootBridgeIo (§14.2.1);
    • RNG (§37.5);
    • status codes (Appendix D).
  • Only what the loader calls. Every function slot it does not call is an untyped pointer, named in the spec's order.
  • Safe wrappers only where the hazard was ours:
    • A name reaches firmware only as a CStr16, which carries its NUL. Literals are checked at compile time by cstr16!. src/bootlog.rs reads the cstr16!("loader.log") spelling, and it is kept.
    • A protocol opens only through BootServices::get (GET_PROTOCOL) or ::exclusive, which requires the Exclusive marker. The raw opener is private. So the type now does the job of the two clippy.toml rules that named uefi's openers, and both rules go. Those lines name paths that no longer exist, so this is outside the brief's named fence, but it is forced.
    • Scoped closes the protocol on drop, and Handles frees its pool buffer.
    • exit_boot_services nulls the table once firmware accepts the exit. After that the console and the allocator refuse. No SIGNAL_EXIT_BOOT_SERVICES callback is registered, so end_this_pass has no event left to close.
    • From the first ExitBootServices call, accepted or not, the console refuses: UEFI 2.10 §7.4.6 allows only the memory allocation services after it. A flag, EXITING, is set before each exit call, and both print and the panic handler check it. A panic in the retry, such as fill_memory_map's assert, therefore skips the console and goes straight to ResetSystem.
    • BlockIo reads OptimalTransferLengthGranularity only at revision 3 or later. The old code read a whole revision 3 media struct on any revision, and needed an unsafe cast to reach revision. That cast is deleted.
    • The memory map is taken into a &mut [u64], which deletes watchdog.rs's unsafe byte-slice cast.
    • PartitionInfo's packed record is read as bytes.
    • The device-path walker refuses a node shorter than its own header.
  • Panic handler. Before EXITING it writes [PANIC]: <info> to the console, as uefi-services did, then calls ResetSystem(SHUTDOWN); from EXITING on it writes nothing and only resets. uefi-services stalled 10 s first; that is a flat wait, and it is not carried over. On metal, a loader panic's line is therefore on the panel for no time before power-off. Stage 4 of the track gives the line to loader.log.
  • Two HARDDRIVE decoders, as on main. efi::HardDrive::parse reads nodes of device paths that firmware built: boot_partition, bootnext::our_partition and boot_disk, the last through its type tag. It takes only a node of the spec's 42 bytes; uefi 0.26 did the same.
    • A Boot#### load option is any writer's bytes, so bootnext keeps main's own walk and decoder for it, unchanged: SignatureType at byte 41 and the signature at 24..40, for any node of 42 bytes or more. QEMU never reaches the positive arm, and no test at any tier would see a change to that rule. So it does not change here.
    • The loader track's stage 2 unifies the two decoders in toyos_update::entry::partition, where its host tests and its §10.3.5.1 oracle can hold them.
  • \toyos\log.guid is read from the SimpleFileSystem on LoadedImage.DeviceHandle (§9.1.1). The old code went through LocateDevicePath.
  • Status text. A status prints by its Appendix D name, so (NOT_FOUND) where the loader wrote (UEFI Error NOT_FOUND: ()). An unknown status prints in hex.
  • Issues.
    • issues/the-loader-does-only-what-must-precede-the-handover.md:
      • The ruling joins the owner's bounds as he gave it, "own bindings". The module path is this change's design, not his ruling.
      • Stage 4 loses its uefi upgrade bullet and its uefi-services exit.
      • Stage 4's panic-handler negative control now names the handler efi/mod.rs holds today.
      • Stage 4's handler skips loaderlog from EXITING on and goes straight to ResetSystem's shutdown: §7.4.6 forbids the File protocol from the first ExitBootServices call as it forbids the console, so the stage as written would have undone this change's EXITING rule.
      • Stage 2 names both decoders that go into toyos_update::entry::partition, efi::HardDrive::parse and bootnext::gpt_signature, and its exit gains rg 'fn gpt_signature|fn parse' bootloader/src/{bootnext.rs,efi/proto.rs} finding nothing; today it finds efi/proto.rs:153 and bootnext.rs:175.
    • issues/toyos-runs-on-arm64.md no longer says the bootloader is the uefi crate.

Checks (high-risk: the firmware boundary, both architectures)

Every run below is at a29d650e8, after the merge, except host, which was run again at ab9772596. The commit between them edits one issue file and no source.

check command result
x86-64 image cargo run -- --build-only EXIT=0
AArch64 image cargo run -- --build-only --arch aarch64 EXIT=0
clippy, both loader targets with undocumented_unsafe_blocks cargo run -- --clippy EXIT=0, 24 invocations clean
host, at ab9772596 cargo run -- --ci host EXIT=0, "78 step(s), all green"
whole guest suite cargo test --test toyos-build EXIT=0, 41 passed of 41; 44 guests

About the runs:

  • The suite's uptime load averages were 20.25 before and 28.03 after, on 14 cores. At 00e4c6076 it was also 41 of 41, at 59.65 → 61.59.
  • The 24 virt_* tests in the suite are the AArch64 boots, all green: the loader on AAVMF, with virt_user_mode's seed from EFI_RNG_PROTOCOL, virt_no_seed_refused, virt_el2_drop and virt_early_panic. The x86-64 boots are on OVMF.

Oracle 1: the old crate, as a differential. This was a one-off and is not landed; its patch is posted as a comment.

  • What it checks: about 190 compile-time asserts on both targets. They hold every layout, offset, GUID and constant of efi against uefi-raw 0.5.0 and uefi 0.26.0, and every status name in the table against uefi-raw's value for that name.
  • Run: sh differential.sh applies it, builds both targets and restores. x86_64 EXIT=0, aarch64 EXIT=0.
  • Negative control:
    • With PartitionInfo::REVISION set to 0x0001_0000, the build is red, EXIT=101. That is the value this branch first wrote. The spec spells it 0x0001000, which is 0x1000, so the differential caught a real slip.
    • With one byte of ACPI2_GUID changed, the build is red, EXIT=101.

Oracle 2: OVMF and AAVMF, base against branch. cargo test --test toyos-build -- screen_loader_clears virt_user_mode --nocapture ran on d6298c83e (EXIT=0) and on 00e4c6076 (EXIT=0).

  • Loader lines, with numbers, GUIDs and digests normalised: 94 lines each. The only differences are the per-image floor names, and (UEFI Error NOT_FOUND: ()) → (NOT_FOUND) on AArch64's black-box refusal.
  • Raw values are unchanged: Boot partition: LBA 2048+69632, the ROOT LBA and length, the GOP mode, stride and framebuffer, the GCD and seed lines, and the kernel size and relocations.
  • The kernel's lines about the handoff (132 each) differ only in per-image GUIDs.

Negative controls on the landed checks. Mutation patches are posted as comments.

  • EXITING has no test and no mutation: no QEMU firmware refuses the first ExitBootServices, so a panic between the two calls cannot be provoked. It rests on reading against §7.4.6.
  • Drop stall from BootServices: the build is red, EXIT=101, on size_of::<BootServices>() == 24 + 44 * 8.
  • Read PartitionInfo's GUID at the type GUID: screen_loader_clears is red, EXIT=1.
    • The log volume is never found, and the slot read panics.
    • The mutant's log also shows the new handler at work: [PANIC]: panicked at bootloader/src/main.rs:… on the console, then a guest shutdown.

Oracle 3: the T14's firmware.

  • At 00e4c6076, run by the orchestrator. Five boots, each image's sha256 checked against request.txt before it was flashed, and each toyos-metal --fat32-check EXIT=0. The judge gave EXIT=0, [metal] 252 passed, 0 failed, 5 boot(s), for boot:testcases loader_watchdog_arms blackbox_unclaimed_page blackbox_done_chain blackbox_foreign_record boot_deadline_ends_a_wedge.
    • Every readback holds a loader.log. So PartitionInfo answered on the T14, and \toyos\log.guid was read through the SimpleFileSystem on LoadedImage.DeviceHandle.
    • The lines it gives: Firmware watchdog: 60 s, Boot partition: LBA 2048+69632 signature [<guid>], ROOT: read into memory at … from LBA 212992+626688, and Seed: 32 bytes from EFI_RNG_PROTOCOL.
    • Boot chain: BootNext=0002, so this loader gets the machine back. This is the same line as main's last readback on that stick.
    • The kernel's gpt: firmware booted us from partition <guid> at LBA 2048+69632, with no but firmware said line. This is the hardware oracle for HardDrive::parse's start and size offsets.
  • At a29d650e8, run by the orchestrator; it stands for ab9772596, which changes no source. Since 00e4c6076, a29d650e8 changes the boot path in three places: the restored load-option decoder, which is main's; the EXITING flag; and the merge of main.
    • Five boots (deadlinewedge, foreignrecord, metalcase, testcases-watchdog, testcases), each image's sha256 checked against metal-r2/request.txt before its flash (9d4b9f7d…, 41545394…, 6c57c6b8…, 93215087…, 0cae5be0…), each toyos-metal --fat32-check EXIT=0.
    • Judge: cargo test --test toyos-build -- --metal --metal-readback <metal-r2> boot:testcases loader_watchdog_arms blackbox_unclaimed_page blackbox_done_chain blackbox_foreign_record boot_deadline_ends_a_wedge, EXIT=0, [metal] 252 passed, 0 failed, 5 boot(s).
    • All five readbacks hold a loader.log.
    • Boot chain: BootNext=0002, so this loader gets the machine back: the restored load-option decoder's positive arm, the same line as main's readback on this stick.
    • The kernel's gpt: firmware booted us from partition <guid> at LBA 2048+69632, with no but firmware said line. Every boot reached the kernel, so a successful ExitBootServices is unaffected by EXITING on the T14's firmware, and The fork's LLVM gives a loop counter's recurrence only the wrap flags proven for it, a sysroot whose compilers lose a loop's last exit is refused, and the ScalarEvolution issue is closed #790's compiler builds a loader that boots there.
    • testcases' loader.log, normalised (timestamps, hashes, GUIDs, addresses, tick counts and versions masked), against main's (testcases from An installed app sees its own package read-only and its own folder as HOME, and nothing else of /apps or /home #807's boot at 0ad87a593, base d6298c83e, same stick): 84 lines each, 26 differing on each side, every difference a per-image value — the anti-rollback floor names, kernel size and segment offsets, the root= id, the black box's arm time and GUID, counter values, the floor's value, and the previous boot's log tail. The GOP:, GCD: and root-bridge lines are identical after masking. No status-name line differs on this boot, as no refusal was printed.

What I am unsure of

  • Dropping the 10 s stall shortens what a person at the panel sees of a loader panic. It removes a flat wait; the owner may weigh that differently.

🤖 Generated with Claude Code

https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C

…vices go

The owner ruled on the dependency audit's uefi row: own bindings, in place
of the loader track's plan to move to the current uefi release.
`bootloader/src/efi/` holds UEFI 2.10's tables, the protocols the loader
opens (LoadedImage, DevicePath, SimpleFileSystem and File, PartitionInfo,
BlockIo, GraphicsOutput, Rng, PciRootBridgeIo), the console it writes, the
status codes and GUIDs, each struct cited to its section and pinned by
compile-time size_of/offset_of! asserts that run on both loader targets at
every build. A module and not a crate: the loader is its one user, and a
crate would add a manifest, a description and a workspace entry for no
second reader; a host test cannot link a no_std, no_main UEFI binary, and
the asserts check the layout on the targets themselves.

The wrappers are safe where the hazard was ours:
- a name crosses to firmware only as CStr16, which carries its NUL;
- a protocol is opened only through BootServices::get (GET_PROTOCOL) or
  ::exclusive, whose bound is the Exclusive marker; the opener is private,
  so the two clippy.toml rules that guarded uefi's openers go with them;
- an open protocol closes when its Scoped drops, a handle buffer is freed;
- the console and the allocator refuse once exit_boot_services has run,
  which nulls the table itself: no SIGNAL_EXIT_BOOT_SERVICES callback is
  registered, so end_this_pass has no event to close;
- BlockIo reads OptimalTransferLengthGranularity only at revision 3 and
  later, which deletes the unsafe cast rootimage.rs needed to reach the
  revision through uefi's type;
- the memory map is taken into &mut [u64], which deletes watchdog.rs's
  unsafe byte-slice cast.

The panic handler says `[PANIC]: <info>` on the console, as uefi-services'
did, and powers the machine off; uefi-services' ten-second stall before
the power-off, a flat wait, does not come with it.

One HARDDRIVE decoder (efi::HardDrive::parse, UEFI 2.10 §10.3.5.1) serves
boot_partition, bootnext's protocol path and its NVRAM load options, and
boot_disk; bootnext's own byte decoder goes, and a load option's HARDDRIVE
node is now held to the spec's 42 bytes.

The ESP's \toyos\log.guid is read off LoadedImage's DeviceHandle, the
volume firmware loaded the image from (§9.1.1), not through
LocateDevicePath.

A status prints by its Appendix D name: `(NOT_FOUND)` where the loader
wrote `(UEFI Error NOT_FOUND: ())`. That is the one change to the loader's
text, and it is on refusal lines alone.

The loader track's stage 4 loses its uefi bullet and its uefi-services
exit, and the owner's ruling joins its bounds.

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

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

One-off differential (not landed), applied, built on both targets and restored by the script below; $LOGS is the scratch log directory.

#!/bin/sh
# The one-off differential: apply, build the loader for both targets, restore.
L=$LOGS
cd <worktree> || exit 2
git apply --check $L/differential.patch || exit 2
git apply $L/differential.patch
CARGO_NET_OFFLINE=true cargo run -- --build-only > $L/differential-x86_64.log 2>&1; X=$?
CARGO_NET_OFFLINE=true cargo run -- --build-only --arch aarch64 > $L/differential-aarch64.log 2>&1; A=$?
git apply -R $L/differential.patch
git checkout -- Cargo.lock
echo "x86_64 EXIT=$X aarch64 EXIT=$A"
git status --porcelain --ignore-submodules=none

Control script:

#!/bin/sh
# Negative control of the differential: the PartitionInfo revision this branch
# first wrote (0x0001_0000, where the spec's 0x0001000 is 0x1000), and one GUID
# byte. Each must turn the differential's build red; then restore.
L=$LOGS
cd <worktree> || exit 2
git apply $L/differential.patch || exit 2
sed -i '' 's/    const REVISION: u32 = 0x1000;/    const REVISION: u32 = 0x0001_0000;/' bootloader/src/efi/proto.rs
git diff --stat bootloader/src/efi/proto.rs
CARGO_NET_OFFLINE=true cargo run -- --build-only > $L/differential-control-revision.log 2>&1; R=$?
sed -i '' 's/    const REVISION: u32 = 0x0001_0000;/    const REVISION: u32 = 0x1000;/' bootloader/src/efi/proto.rs
sed -i '' 's/0x8868e871, 0xe4f1, 0x11d3, \[0xbc, 0x22/0x8868e871, 0xe4f1, 0x11d3, [0xbc, 0x23/' bootloader/src/efi/mod.rs
CARGO_NET_OFFLINE=true cargo run -- --build-only > $L/differential-control-guid.log 2>&1; G=$?
sed -i '' 's/0x8868e871, 0xe4f1, 0x11d3, \[0xbc, 0x23/0x8868e871, 0xe4f1, 0x11d3, [0xbc, 0x22/' bootloader/src/efi/mod.rs
git apply -R $L/differential.patch
git checkout -- Cargo.lock
echo "revision mutant EXIT=$R guid mutant EXIT=$G"
git status --porcelain --ignore-submodules=none
diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml
index 9250af93c..298af099e 100644
--- a/bootloader/Cargo.toml
+++ b/bootloader/Cargo.toml
@@ -39,3 +39,6 @@ toyos-update = { path = "../toyos-update" }
 # with: this target is soft-float, and the x86 backend reaches for SSE and
 # SHA-NI registers a UEFI application may not assume are its own.
 sha2 = { version = "0.10", default-features = false, features = ["force-soft"] }
+# ONE-OFF differential, not landed.
+uefi-raw = "=0.5.0"
+uefi = { version = "=0.26.0", default-features = false }
diff --git a/bootloader/src/efi/boot.rs b/bootloader/src/efi/boot.rs
index 8009db982..a053863f2 100644
--- a/bootloader/src/efi/boot.rs
+++ b/bootloader/src/efi/boot.rs
@@ -400,3 +400,6 @@ impl BootServices {
     }
 }
 
+
+#[path = "differential/boot.rs"]
+mod differential;
diff --git a/bootloader/src/efi/differential/boot.rs b/bootloader/src/efi/differential/boot.rs
new file mode 100644
index 000000000..30e2cfd6c
--- /dev/null
+++ b/bootloader/src/efi/differential/boot.rs
@@ -0,0 +1,32 @@
+//! ONE-OFF, NOT LANDED: `efi::boot` held against uefi-raw 0.5.0 and uefi 0.26.0.
+include!("macros.rs");
+use super::*;
+use uefi_raw::table as rt;
+
+same_size!(BootServices, rt::boot::BootServices);
+same_offset!(BootServices, allocate_pages, rt::boot::BootServices, allocate_pages);
+same_offset!(BootServices, free_pages, rt::boot::BootServices, free_pages);
+same_offset!(BootServices, get_memory_map, rt::boot::BootServices, get_memory_map);
+same_offset!(BootServices, allocate_pool, rt::boot::BootServices, allocate_pool);
+same_offset!(BootServices, free_pool, rt::boot::BootServices, free_pool);
+same_offset!(BootServices, exit_boot_services, rt::boot::BootServices, exit_boot_services);
+same_offset!(BootServices, set_watchdog_timer, rt::boot::BootServices, set_watchdog_timer);
+same_offset!(BootServices, open_protocol, rt::boot::BootServices, open_protocol);
+same_offset!(BootServices, close_protocol, rt::boot::BootServices, close_protocol);
+same_offset!(BootServices, locate_handle_buffer, rt::boot::BootServices, locate_handle_buffer);
+same_offset!(BootServices, create_event_ex, rt::boot::BootServices, create_event_ex);
+same_size!(MemoryDescriptor, rt::boot::MemoryDescriptor);
+same_offset!(MemoryDescriptor, ty, rt::boot::MemoryDescriptor, ty);
+same_offset!(MemoryDescriptor, phys_start, rt::boot::MemoryDescriptor, phys_start);
+same_offset!(MemoryDescriptor, virt_start, rt::boot::MemoryDescriptor, virt_start);
+same_offset!(MemoryDescriptor, page_count, rt::boot::MemoryDescriptor, page_count);
+same_offset!(MemoryDescriptor, att, rt::boot::MemoryDescriptor, att);
+const _: () = {
+    assert!(LOADER_DATA == rt::boot::MemoryType::LOADER_DATA.0);
+    assert!(MemoryDescriptor::MMIO == rt::boot::MemoryType::MMIO.0);
+    assert!(MemoryDescriptor::MMIO_PORT_SPACE == rt::boot::MemoryType::MMIO_PORT_SPACE.0);
+    assert!(MemoryDescriptor::WRITE_BACK == rt::boot::MemoryAttribute::WRITE_BACK.bits());
+    assert!(GET_PROTOCOL == uefi::table::boot::OpenProtocolAttributes::GetProtocol as u32);
+    assert!(EXCLUSIVE == uefi::table::boot::OpenProtocolAttributes::Exclusive as u32);
+    assert!(PAGE_SIZE == uefi::table::boot::PAGE_SIZE);
+};
diff --git a/bootloader/src/efi/differential/macros.rs b/bootloader/src/efi/differential/macros.rs
new file mode 100644
index 000000000..053f36f74
--- /dev/null
+++ b/bootloader/src/efi/differential/macros.rs
@@ -0,0 +1,33 @@
+// ONE-OFF, NOT LANDED: the differential's shared macros.
+#[allow(unused_macros)]
+macro_rules! same_size {
+    ($ours:ty, $theirs:ty) => {
+        const _: () = assert!(core::mem::size_of::<$ours>() == core::mem::size_of::<$theirs>());
+    };
+}
+#[allow(unused_macros)]
+macro_rules! same_offset {
+    ($ours:ty, $of:ident, $theirs:ty, $tf:ident) => {
+        const _: () = assert!(core::mem::offset_of!($ours, $of) == core::mem::offset_of!($theirs, $tf));
+    };
+}
+#[allow(dead_code)]
+const fn bytes_eq(a: &[u8], b: &[u8]) -> bool {
+    if a.len() != b.len() {
+        return false;
+    }
+    let mut i = 0;
+    while i < a.len() {
+        if a[i] != b[i] {
+            return false;
+        }
+        i += 1;
+    }
+    true
+}
+#[allow(unused_macros)]
+macro_rules! same_guid {
+    ($ours:expr, $theirs:expr) => {
+        const _: () = assert!(bytes_eq(&$ours.0, &$theirs.to_bytes()));
+    };
+}
diff --git a/bootloader/src/efi/differential/proto.rs b/bootloader/src/efi/differential/proto.rs
new file mode 100644
index 000000000..03f25879d
--- /dev/null
+++ b/bootloader/src/efi/differential/proto.rs
@@ -0,0 +1,103 @@
+//! ONE-OFF, NOT LANDED: `efi::proto` held against uefi-raw 0.5.0 and uefi 0.26.0.
+include!("macros.rs");
+use super::*;
+use uefi_raw::protocol as rp;
+
+// Protocols.
+same_size!(TextOutput, rp::console::SimpleTextOutputProtocol);
+same_offset!(TextOutput, output_string, rp::console::SimpleTextOutputProtocol, output_string);
+same_offset!(TextOutput, clear_screen, rp::console::SimpleTextOutputProtocol, clear_screen);
+
+same_size!(LoadedImage, rp::loaded_image::LoadedImageProtocol);
+same_offset!(LoadedImage, device_handle, rp::loaded_image::LoadedImageProtocol, device_handle);
+same_offset!(LoadedImage, image_base, rp::loaded_image::LoadedImageProtocol, image_base);
+same_offset!(LoadedImage, image_size, rp::loaded_image::LoadedImageProtocol, image_size);
+same_offset!(LoadedImage, image_data_type, rp::loaded_image::LoadedImageProtocol, image_data_type);
+
+same_size!(DevicePath, rp::device_path::DevicePathProtocol);
+same_size!([u8; HardDrive::LEN], uefi::proto::device_path::media::HardDrive);
+
+same_size!(SimpleFileSystem, rp::file_system::SimpleFileSystemProtocol);
+same_offset!(SimpleFileSystem, open_volume, rp::file_system::SimpleFileSystemProtocol, open_volume);
+same_size!(FileProtocol, rp::file_system::FileProtocolV1);
+same_offset!(FileProtocol, open, rp::file_system::FileProtocolV1, open);
+same_offset!(FileProtocol, close, rp::file_system::FileProtocolV1, close);
+same_offset!(FileProtocol, delete, rp::file_system::FileProtocolV1, delete);
+same_offset!(FileProtocol, read, rp::file_system::FileProtocolV1, read);
+same_offset!(FileProtocol, write, rp::file_system::FileProtocolV1, write);
+same_offset!(FileProtocol, set_position, rp::file_system::FileProtocolV1, set_position);
+same_offset!(FileProtocol, get_info, rp::file_system::FileProtocolV1, get_info);
+same_offset!(FileProtocol, flush, rp::file_system::FileProtocolV1, flush);
+same_offset!(RawFileInfo, file_size, rp::file_system::FileInfo, file_size);
+same_offset!(RawFileInfo, attribute, rp::file_system::FileInfo, attribute);
+const _: () = assert!(size_of::<RawFileInfo>() == offset_of!(rp::file_system::FileInfo, file_name));
+
+same_size!(PartitionInfo, uefi::proto::media::partition::PartitionInfo);
+same_offset!(PartitionInfo, revision, uefi::proto::media::partition::PartitionInfo, revision);
+same_offset!(PartitionInfo, kind, uefi::proto::media::partition::PartitionInfo, partition_type);
+
+same_size!(BlockIo, rp::block::BlockIoProtocol);
+same_offset!(BlockIo, revision, rp::block::BlockIoProtocol, revision);
+same_offset!(BlockIo, media, rp::block::BlockIoProtocol, media);
+same_offset!(BlockIo, read_blocks, rp::block::BlockIoProtocol, read_blocks);
+same_offset!(BlockIoMedia, media_id, rp::block::BlockIoMedia, media_id);
+same_offset!(BlockIoMedia, media_present, rp::block::BlockIoMedia, media_present);
+same_offset!(BlockIoMedia, block_size, rp::block::BlockIoMedia, block_size);
+same_offset!(BlockIoMedia, io_align, rp::block::BlockIoMedia, io_align);
+same_offset!(BlockIoMedia, last_block, rp::block::BlockIoMedia, last_block);
+same_size!(BlockIoMediaRevision3, rp::block::BlockIoMedia);
+same_offset!(
+    BlockIoMediaRevision3,
+    optimal_transfer_length_granularity,
+    rp::block::BlockIoMedia,
+    optimal_transfer_length_granularity
+);
+
+same_size!(Gop, rp::console::GraphicsOutputProtocol);
+same_offset!(Gop, mode, rp::console::GraphicsOutputProtocol, mode);
+same_size!(GopMode, rp::console::GraphicsOutputProtocolMode);
+same_offset!(GopMode, info, rp::console::GraphicsOutputProtocolMode, info);
+same_offset!(GopMode, frame_buffer_base, rp::console::GraphicsOutputProtocolMode, frame_buffer_base);
+same_offset!(GopMode, frame_buffer_size, rp::console::GraphicsOutputProtocolMode, frame_buffer_size);
+same_size!(GopModeInfo, rp::console::GraphicsOutputModeInformation);
+same_offset!(GopModeInfo, horizontal_resolution, rp::console::GraphicsOutputModeInformation, horizontal_resolution);
+same_offset!(GopModeInfo, vertical_resolution, rp::console::GraphicsOutputModeInformation, vertical_resolution);
+same_offset!(GopModeInfo, pixel_format, rp::console::GraphicsOutputModeInformation, pixel_format);
+same_offset!(GopModeInfo, pixels_per_scan_line, rp::console::GraphicsOutputModeInformation, pixels_per_scan_line);
+
+same_size!(Rng, rp::rng::RngProtocol);
+same_offset!(Rng, get_rng, rp::rng::RngProtocol, get_rng);
+
+same_guid!(<LoadedImage as super::super::boot::Protocol>::GUID, rp::loaded_image::LoadedImageProtocol::GUID);
+same_guid!(<DevicePath as super::super::boot::Protocol>::GUID, rp::device_path::DevicePathProtocol::GUID);
+same_guid!(<SimpleFileSystem as super::super::boot::Protocol>::GUID, rp::file_system::SimpleFileSystemProtocol::GUID);
+same_guid!(FILE_INFO, rp::file_system::FileInfo::ID);
+same_guid!(<BlockIo as super::super::boot::Protocol>::GUID, rp::block::BlockIoProtocol::GUID);
+same_guid!(<Gop as super::super::boot::Protocol>::GUID, rp::console::GraphicsOutputProtocol::GUID);
+same_guid!(<Rng as super::super::boot::Protocol>::GUID, rp::rng::RngProtocol::GUID);
+same_guid!(<PartitionInfo as super::super::boot::Protocol>::GUID, <uefi::proto::media::partition::PartitionInfo as uefi::Identify>::GUID);
+same_guid!(<PciRootBridgeIo as super::super::boot::Protocol>::GUID, uefi::guid!("2f707ebb-4a1a-11d4-9a38-0090273fc14d"));
+// Constants.
+const _: () = {
+    assert!(Mode::Read.bits() == rp::file_system::FileMode::READ.bits());
+    assert!(Mode::ReadWrite.bits() == rp::file_system::FileMode::READ.union(rp::file_system::FileMode::WRITE).bits());
+    assert!(
+        Mode::CreateReadWrite.bits()
+            == rp::file_system::FileMode::CREATE
+                .union(rp::file_system::FileMode::READ)
+                .union(rp::file_system::FileMode::WRITE)
+                .bits()
+    );
+    assert!(DIRECTORY == rp::file_system::FileAttribute::DIRECTORY.bits());
+    assert!(PixelFormat::RGB.0 == rp::console::GraphicsPixelFormat::PIXEL_RED_GREEN_BLUE_RESERVED_8_BIT_PER_COLOR.0);
+    assert!(PixelFormat::BGR.0 == rp::console::GraphicsPixelFormat::PIXEL_BLUE_GREEN_RED_RESERVED_8_BIT_PER_COLOR.0);
+    assert!(PixelFormat::BIT_MASK.0 == rp::console::GraphicsPixelFormat::PIXEL_BIT_MASK.0);
+    assert!(PixelFormat::BLT_ONLY.0 == rp::console::GraphicsPixelFormat::PIXEL_BLT_ONLY.0);
+    assert!(PartitionInfo::REVISION == uefi::proto::media::partition::PartitionInfoRevision::PROTOCOL_REVISION.0);
+    assert!(PartitionInfo::GPT == uefi::proto::media::partition::PartitionType::GPT.0);
+    assert!(HardDrive::TYPE.0 == uefi::proto::device_path::DeviceType::MEDIA.0);
+    assert!(HardDrive::TYPE.1 == uefi::proto::device_path::DeviceSubType::MEDIA_HARD_DRIVE.0);
+    assert!(HardDrive::GPT == uefi::proto::device_path::media::PartitionFormat::GPT.0);
+    assert!(END_ENTIRE.0 == uefi::proto::device_path::DeviceType::END.0);
+    assert!(END_ENTIRE.1 == uefi::proto::device_path::DeviceSubType::END_ENTIRE.0);
+};
diff --git a/bootloader/src/efi/differential/runtime.rs b/bootloader/src/efi/differential/runtime.rs
new file mode 100644
index 000000000..68fcb0a8f
--- /dev/null
+++ b/bootloader/src/efi/differential/runtime.rs
@@ -0,0 +1,31 @@
+//! ONE-OFF, NOT LANDED: `efi::runtime` held against uefi-raw 0.5.0.
+include!("macros.rs");
+use super::*;
+use uefi_raw::table as rt;
+
+same_size!(RuntimeServices, rt::runtime::RuntimeServices);
+same_offset!(RuntimeServices, get_time, rt::runtime::RuntimeServices, get_time);
+same_offset!(RuntimeServices, get_variable, rt::runtime::RuntimeServices, get_variable);
+same_offset!(RuntimeServices, get_next_variable_name, rt::runtime::RuntimeServices, get_next_variable_name);
+same_offset!(RuntimeServices, set_variable, rt::runtime::RuntimeServices, set_variable);
+same_offset!(RuntimeServices, reset_system, rt::runtime::RuntimeServices, reset_system);
+same_offset!(RuntimeServices, query_variable_info, rt::runtime::RuntimeServices, query_variable_info);
+same_size!(Time, uefi_raw::time::Time);
+same_offset!(Time, year, uefi_raw::time::Time, year);
+same_offset!(Time, month, uefi_raw::time::Time, month);
+same_offset!(Time, day, uefi_raw::time::Time, day);
+same_offset!(Time, hour, uefi_raw::time::Time, hour);
+same_offset!(Time, minute, uefi_raw::time::Time, minute);
+same_offset!(Time, second, uefi_raw::time::Time, second);
+same_offset!(Time, nanosecond, uefi_raw::time::Time, nanosecond);
+same_offset!(Time, time_zone, uefi_raw::time::Time, time_zone);
+same_offset!(Time, daylight, uefi_raw::time::Time, daylight);
+same_guid!(GLOBAL_VARIABLE, rt::runtime::VariableVendor::GLOBAL_VARIABLE.0);
+const _: () = {
+    assert!(ResetType::COLD.0 == rt::runtime::ResetType::COLD.0);
+    assert!(ResetType::WARM.0 == rt::runtime::ResetType::WARM.0);
+    assert!(ResetType::SHUTDOWN.0 == rt::runtime::ResetType::SHUTDOWN.0);
+    assert!(VariableAttributes::NON_VOLATILE == rt::runtime::VariableAttributes::NON_VOLATILE.bits());
+    assert!(VariableAttributes::BOOTSERVICE_ACCESS == rt::runtime::VariableAttributes::BOOTSERVICE_ACCESS.bits());
+    assert!(VariableAttributes::RUNTIME_ACCESS == rt::runtime::VariableAttributes::RUNTIME_ACCESS.bits());
+};
diff --git a/bootloader/src/efi/differential/top.rs b/bootloader/src/efi/differential/top.rs
new file mode 100644
index 000000000..b5e73825d
--- /dev/null
+++ b/bootloader/src/efi/differential/top.rs
@@ -0,0 +1,52 @@
+//! ONE-OFF, NOT LANDED: `efi`'s own tables, GUIDs and status codes held
+//! against uefi-raw 0.5.0 and uefi 0.26.0, at compile time, on the target built.
+include!("macros.rs");
+use super::*;
+use uefi_raw::table as rt;
+
+same_size!(Guid, uefi_raw::Guid);
+same_size!(TableHeader, rt::Header);
+same_offset!(TableHeader, revision, rt::Header, revision);
+same_offset!(TableHeader, header_size, rt::Header, size);
+same_size!(RawSystemTable, rt::system::SystemTable);
+same_offset!(RawSystemTable, firmware_revision, rt::system::SystemTable, firmware_revision);
+same_offset!(RawSystemTable, con_out, rt::system::SystemTable, stdout);
+same_offset!(RawSystemTable, runtime_services, rt::system::SystemTable, runtime_services);
+same_offset!(RawSystemTable, boot_services, rt::system::SystemTable, boot_services);
+same_offset!(RawSystemTable, number_of_table_entries, rt::system::SystemTable, number_of_configuration_table_entries);
+same_offset!(RawSystemTable, configuration_table, rt::system::SystemTable, configuration_table);
+same_size!(ConfigurationTable, rt::configuration::ConfigurationTable);
+same_offset!(ConfigurationTable, guid, rt::configuration::ConfigurationTable, vendor_guid);
+same_offset!(ConfigurationTable, address, rt::configuration::ConfigurationTable, vendor_table);
+same_guid!(ACPI2_GUID, uefi::table::cfg::ACPI2_GUID);
+same_guid!(crate::gcd::DXE_SERVICES_TABLE_GUID, uefi::guid!("05ad34ba-6f02-4214-952e-4da0398e2bb9"));
+same_guid!(crate::floor::VENDOR, uefi::guid!("33be3d4a-30e6-49f5-8050-f169d93a20fb"));
+const _: () = {
+    // Every name the table gives, against the crate's value for that name.
+    let mut i = 0;
+    while i < NAMES.len() {
+        let (code, name) = NAMES[i];
+        assert!(code == their(name), "a status code's name and value disagree with uefi-raw");
+        i += 1;
+    }
+    assert!(Status::SUCCESS.0 == uefi_raw::Status::SUCCESS.0);
+    assert!(Status::BUFFER_TOO_SMALL.0 == uefi_raw::Status::BUFFER_TOO_SMALL.0);
+    assert!(Status::NOT_FOUND.0 == uefi_raw::Status::NOT_FOUND.0);
+    assert!(Status::ABORTED.0 == uefi_raw::Status::ABORTED.0);
+    assert!(Status::NOT_FOUND.0 == toyos_update::floor::NOT_FOUND);
+};
+const fn their(name: &str) -> usize {
+    macro_rules! table {
+        ($($n:ident),*) => {{
+            $(if bytes_eq(name.as_bytes(), stringify!($n).as_bytes()) { return uefi_raw::Status::$n.0; })*
+            panic!("a name uefi-raw has no status for")
+        }};
+    }
+    table!(SUCCESS, WARN_UNKNOWN_GLYPH, WARN_DELETE_FAILURE, WARN_WRITE_FAILURE, WARN_BUFFER_TOO_SMALL,
+        WARN_STALE_DATA, WARN_FILE_SYSTEM, WARN_RESET_REQUIRED, LOAD_ERROR, INVALID_PARAMETER, UNSUPPORTED,
+        BAD_BUFFER_SIZE, BUFFER_TOO_SMALL, NOT_READY, DEVICE_ERROR, WRITE_PROTECTED, OUT_OF_RESOURCES,
+        VOLUME_CORRUPTED, VOLUME_FULL, NO_MEDIA, MEDIA_CHANGED, NOT_FOUND, ACCESS_DENIED, NO_RESPONSE, NO_MAPPING,
+        TIMEOUT, NOT_STARTED, ALREADY_STARTED, ABORTED, ICMP_ERROR, TFTP_ERROR, PROTOCOL_ERROR,
+        INCOMPATIBLE_VERSION, SECURITY_VIOLATION, CRC_ERROR, END_OF_MEDIA, END_OF_FILE, INVALID_LANGUAGE,
+        COMPROMISED_DATA, IP_ADDRESS_CONFLICT, HTTP_ERROR)
+}
diff --git a/bootloader/src/efi/mod.rs b/bootloader/src/efi/mod.rs
index 22e0fcfcc..7660682bf 100644
--- a/bootloader/src/efi/mod.rs
+++ b/bootloader/src/efi/mod.rs
@@ -16,6 +16,8 @@
 mod boot;
 mod proto;
 mod runtime;
+#[path = "differential/top.rs"]
+mod differential;
 
 use core::ffi::c_void;
 use core::fmt;
diff --git a/bootloader/src/efi/proto.rs b/bootloader/src/efi/proto.rs
index d60c848a7..5a7f36236 100644
--- a/bootloader/src/efi/proto.rs
+++ b/bootloader/src/efi/proto.rs
@@ -237,7 +237,7 @@ pub enum Mode {
 }
 
 impl Mode {
-    fn bits(self) -> u64 {
+    const fn bits(self) -> u64 {
         const READ: u64 = 0x1;
         const WRITE: u64 = 0x2;
         const CREATE: u64 = 0x8000_0000_0000_0000;
@@ -659,3 +659,6 @@ impl super::Scoped<'_, PciRootBridgeIo> {
         unsafe { (self.configuration)(self.this(), &mut resources) }.ok().map(|()| resources)
     }
 }
+
+#[path = "differential/proto.rs"]
+mod differential;
diff --git a/bootloader/src/efi/runtime.rs b/bootloader/src/efi/runtime.rs
index 16bca2e34..88dcd32bf 100644
--- a/bootloader/src/efi/runtime.rs
+++ b/bootloader/src/efi/runtime.rs
@@ -173,3 +173,6 @@ impl RuntimeServices {
         unsafe { (self.reset_system)(kind, status, 0, ptr::null()) }
     }
 }
+
+#[path = "differential/runtime.rs"]
+mod differential;
diff --git a/bootloader/src/floor.rs b/bootloader/src/floor.rs
index ba57078db..a0b6ef240 100644
--- a/bootloader/src/floor.rs
+++ b/bootloader/src/floor.rs
@@ -19,7 +19,7 @@ const SCOPE: Scope = Scope::from_word(env!("TOYOS_IMAGE_FLOOR"));
 
 /// The vendor every floor is under: `33BE3D4A-30E6-49F5-8050-F169D93A20FB`,
 /// minted for this and used for nothing else.
-const VENDOR: Guid = Guid::new(0x33be3d4a, 0x30e6, 0x49f5, [0x80, 0x50, 0xf1, 0x69, 0xd9, 0x3a, 0x20, 0xfb]);
+pub(crate) const VENDOR: Guid = Guid::new(0x33be3d4a, 0x30e6, 0x49f5, [0x80, 0x50, 0xf1, 0x69, 0xd9, 0x3a, 0x20, 0xfb]);
 
 /// The only attributes the floor is written with.
 const ATTRIBUTES: u32 = VariableAttributes::NON_VOLATILE | VariableAttributes::BOOTSERVICE_ACCESS;
diff --git a/bootloader/src/gcd.rs b/bootloader/src/gcd.rs
index e33da4457..808a0c0e7 100644
--- a/bootloader/src/gcd.rs
+++ b/bootloader/src/gcd.rs
@@ -24,7 +24,7 @@ use crate::efi::{Guid, Status, SystemTable};
 const HEAD: &str = "GCD:";
 
 /// The configuration table entry carrying [`DxeServices`] (PI 1.8 Vol. 2 §7.1).
-const DXE_SERVICES_TABLE_GUID: Guid =
+pub(crate) const DXE_SERVICES_TABLE_GUID: Guid =
     Guid::new(0x05ad34ba, 0x6f02, 0x4214, [0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9]);
 
 /// `EFI_DXE_SERVICES_TABLE_SIGNATURE`: the eight bytes `DXE_SERV`, read as a

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches (negative controls), applied, run and restored by the script below.

#!/bin/sh
# Negative controls, each a checked patch applied, run and restored.
L=$LOGS
cd <worktree> || exit 2
# 1. PartitionInfo read at the type GUID, not the unique one: the log volume is never found.
git apply --check $L/mutation-partition.patch && git apply $L/mutation-partition.patch || exit 2
cargo test --test toyos-build -- screen_loader_clears > $L/mutation-partition.log 2>&1; P=$?
git apply -R $L/mutation-partition.patch
# 2. A boot service dropped from the table: every later function pointer moves.
git apply --check $L/mutation-boot-services.patch && git apply $L/mutation-boot-services.patch || exit 2
cargo run -- --build-only > $L/mutation-boot-services.log 2>&1; B=$?
git apply -R $L/mutation-boot-services.patch
echo "partition mutant EXIT=$P boot-services mutant EXIT=$B"
git status --porcelain --ignore-submodules=none
diff --git a/bootloader/src/efi/proto.rs b/bootloader/src/efi/proto.rs
index d60c848a7..7b0520fa6 100644
--- a/bootloader/src/efi/proto.rs
+++ b/bootloader/src/efi/proto.rs
@@ -405,7 +405,7 @@ impl PartitionInfo {
             return None;
         }
         let record = self.record;
-        Some(record[16..32].try_into().expect("sixteen bytes"))
+        Some(record[0..16].try_into().expect("sixteen bytes"))
     }
 }
 
diff --git a/bootloader/src/efi/boot.rs b/bootloader/src/efi/boot.rs
index 8009db982..08d206b9d 100644
--- a/bootloader/src/efi/boot.rs
+++ b/bootloader/src/efi/boot.rs
@@ -62,7 +62,6 @@ pub struct BootServices {
     unload_image: Unused,
     exit_boot_services: unsafe extern "efiapi" fn(image: *mut c_void, key: usize) -> Status,
     get_next_monotonic_count: Unused,
-    stall: Unused,
     set_watchdog_timer:
         unsafe extern "efiapi" fn(seconds: usize, code: u64, data_size: usize, data: *const u16) -> Status,
     connect_controller: Unused,

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at 00e4c6076, run by the orchestrator: the request's five boots (deadlinewedge, foreignrecord, metalcase, testcases-watchdog, testcases), each image's sha256 checked against request.txt before its flash, each toyos-metal --fat32-check exit 0. Judge cargo test --test toyos-build -- --metal --metal-readback <dir> boot:testcases loader_watchdog_arms blackbox_unclaimed_page blackbox_done_chain blackbox_foreign_record boot_deadline_ends_a_wedge: EXIT=0, [metal] 252 passed, 0 failed, 5 boot(s). Every readback holds a loader.log. From testcases (partition GUIDs masked): Firmware watchdog: 60 s, until ExitBootServices disables it; Boot partition: LBA 2048+69632 signature [<guid>]; ROOT: read into memory at … from LBA 212992+626688; Boot chain: BootNext=0002, so this loader gets the machine back (the same line as main's last readback on this stick); Seed: 32 bytes from EFI_RNG_PROTOCOL for the kernel's generator; the kernel's gpt: firmware booted us from partition <guid> at LBA 2048+69632 with no but firmware said line. This head is superseded by the review's fix round, which owes a new reading.

Japabu and others added 2 commits October 9, 2026 19:55
…nic after a refused ExitBootServices goes straight to the reset

The round-1 review of #815 found the load-option walk in bootnext.rs
narrowed from main's rule (SignatureType at byte 41, signature at 24..40,
any node at least 42 bytes long) to HardDrive::parse's exact 42 bytes. Those
bytes are any OS's to write through runtime variable access, and no test at
any tier can see the rule change: QEMU only reaches the "no Boot#### entry"
arm. Main's decoder is restored byte for byte; HardDrive::parse stays for
device paths firmware built (boot_partition, our_partition), and the loader
track's stage 2 still unifies the two under its host tests.

UEFI 2.10 §7.4.6 allows only the memory allocation services after a first
ExitBootServices call, failed or not. A panic in the retry (fill_memory_map's
assert) reached a handler that still wrote through ConOut. EXITING is set
before each exit call; print and the panic handler read the console only
while it is clear, so the handler goes straight to ResetSystem.

The loader track's bound no longer puts the module path in the owner's
ruling, and the arm64 issue no longer says the bootloader is the uefi crate.

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

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at a29d650e8, run by the orchestrator: the request's five boots, each image's sha256 checked against metal-r2/request.txt before its flash (deadlinewedge 9d4b9f7d…, foreignrecord 41545394…, metalcase 6c57c6b8…, testcases-watchdog 93215087…, testcases 0cae5be0…), each toyos-metal --fat32-check exit 0. Judge cargo test --test toyos-build -- --metal --metal-readback <metal-r2> boot:testcases loader_watchdog_arms blackbox_unclaimed_page blackbox_done_chain blackbox_foreign_record boot_deadline_ends_a_wedge: EXIT=0, [metal] 252 passed, 0 failed, 5 boot(s).

  • All five readbacks hold a loader.log.
  • Boot chain: BootNext=0002, so this loader gets the machine back: the restored load-option decoder's positive arm, the same line as main's readback on this stick.
  • The kernel's gpt: firmware booted us from partition <guid> at LBA 2048+69632, no but firmware said line; every boot reached the kernel.
  • testcases' loader.log normalised (timestamps, hashes, GUIDs, addresses, tick counts and versions masked) against main's (the testcases readback of An installed app sees its own package read-only and its own folder as HOME, and nothing else of /apps or /home #807's boot at 0ad87a593, base d6298c83e, same stick): 84 lines each, 26 lines differ on each side, and every difference is a per-image value: the image's anti-rollback floor names, kernel size and segment offsets, the root= id, the black box's arm time and GUID, counter values, the floor's value, and the previous boot's log tail. The GOP:, GCD: and root-bridge lines are identical after masking. No status-name line differs on this boot (no refusal printed).

…NG on, and stage 2 names both HARDDRIVE decoders it folds

Stage 4's handler writes loader.log through the File protocol, which UEFI
2.10 §7.4.6 forbids from the first ExitBootServices call as it forbids the
console; as written it would undo the EXITING fix. It now skips loaderlog
from EXITING on and goes straight to ResetSystem's shutdown.

Stage 2 called toyos_update::entry::partition the one HARDDRIVE rule but
named neither of the loader's two decoders going, efi::HardDrive::parse and
bootnext::gpt_signature, so it could close with three. Both are named, and
the exit gains an rg that finds them today.

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

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #815 at ab9772596, round 3.

Net lines (git diff --shortstat origin/main...ab9772596): 23 files, +2020/−481. Production and records only, with no test lines. Since a29d650e8: one issue file, +9/−2 (git diff --stat a29d650e8 ab9772596). No built byte changes, so the a29d650e8 reading stands for this head.

Round-2 findings

  • BLOCKER, T14 reading at a29d650e8: CLOSED. I checked each of the seven conditions against the files themselves.
    1. Image hashes. logs/t14-uefi-r2-<image>.log line 2 of each boot log gives the sha256 of the flashed image. All five equal metal-r2/request.txt's values: 9d4b9f7d…, 41545394…, 6c57c6b8…, 0cae5be0… and 93215087….
    2. Boot exits and fat32 check. logs/t14-uefi-r2.log gives rc=0 for each boot. Each boot log ends toyos-fat32-check: the log partition's 35651584 bytes check out, then PASS: the machine booted ToyOS.
    3. Judge. t14-uefi-r2.log reads judge EXIT=0 [metal] 252 passed, 0 failed, 5 boot(s), and metal-r2/judge.log ends on the same line.
    4. loader.log. All five readbacks hold one, at 5547 to 24973 bytes.
    5. Boot chain. Boot chain: BootNext=0002, so this loader gets the machine back is in all five loader.logs, at lines 28–29. It is the same line as main's appgrants/metal-r2/testcases/loader.log:29.
    6. Kernel reached. All five kernel.logs carry gpt: firmware booted us from partition <guid> at LBA 2048+69632. rg 'but firmware said' finds nothing in any of them. All five verdict.txt read passed.
    7. loader.log comparison. I stripped the timestamp prefix and masked hex, GUIDs, hashes and digits myself. testcases' loader.log is then 84 lines, as is main's, and as is metal/testcases' (00e4c6076). Against each of them, diff differs only on lines 8, 9 and 28: the boot and log partition signatures and the black box's GUID. These are per-image values. The GOP:, GCD: and root-bridge lines are identical.
  • NOTE, stage 4's handler and §7.4.6: CLOSED. The issue file's lines 158–160 now say that from EXITING on, the handler skips loaderlog and goes straight to ResetSystem's shutdown.
  • NOTE, stage 2 naming both HARDDRIVE decoders: CLOSED. Lines 88–91 name efi::HardDrive::parse and bootnext::gpt_signature. The exit at line 102 is rg 'fn gpt_signature|fn parse' bootloader/src/{bootnext.rs,efi/proto.rs} finds nothing. At this head that search matches exactly the two decoders, bootnext.rs:175 and proto.rs:153, so the exit can fail and can be met.

BLOCKER

None.

NOTE

  • PR body, "Panic handler": it says the handler writes [PANIC]: <info> to the console and then resets. From EXITING on, it writes nothing and only resets (efi/mod.rs:550-556). The bullet does not say this. Separately, the body's Oracle 3 gives the loader.log comparison against main only, not the one against 00e4c6076's readback; that comparison holds as measured above.

LAND

@Japabu
Japabu marked this pull request as ready for review October 9, 2026 18:33
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Reposted by the orchestrator with the T14's model and firmware strings masked; the text is otherwise the reviewer's (original comment 6086105246, deleted because GitHub keeps edit history).

Review of #815 at 00e4c6076, round 1.

Net lines (git diff --shortstat origin/main...00e4c6076): 22 files, +1997/−491. Production is +1784 in bootloader/src/efi/, with the loader's callers about net-flat, protocol.rs (−41) gone, and Cargo.lock −91. The records change too. No test lines change. I accept the growth: it is the owner's ruling, and the source of 9 crates leaves with it.

BLOCKER

  • PR body, "Oracle 3" — the T14 reading is owed, not posted. This change sits on the firmware boundary of every boot, and three of its claims are reached only on hardware: the HardDrive::parse offsets, the SimpleFileSystem on LoadedImage.DeviceHandle, and PartitionInfo on the T14's firmware. The run at 00e4c6076 must show all of the following, with its command, exit code and readback directory in the body:
    • The images' sha256 match request.txt. boot:testcases, loader_watchdog_arms, blackbox_unclaimed_page, blackbox_done_chain, blackbox_foreign_record and boot_deadline_ends_a_wedge are each green.
    • Every boot has a loader.log on the stick. Its existence is the proof of two things. First, PartitionInfo answered revision 0x1000/GPT with the unique GUID at record bytes 16..32. Second, \toyos\log.guid was read through SimpleFileSystem on LoadedImage.DeviceHandle. Main reached it through LocateDevicePath, and a miss now panics before the first line and powers off at once, leaving nothing.
    • The loader lines Boot partition: LBA a+b signature G, Seed: N bytes from EFI_RNG_PROTOCOL, Firmware watchdog: N s, the GOP line, the GCD/Root bridge lines and ROOT: read into memory at.
    • The kernel's gpt: firmware booted us from partition G at LBA a+b, with no but firmware said line, and the boot volume held. This is the only oracle for start at byte 8 and size at byte 16: swapping them passes every QEMU test.
    • The Boot chain: line equal to the last main readback on the same stick. QEMU only ever reaches the "no Boot#### entry" arm, so the load-option path is measured here or nowhere.
    • The normalised loader lines against a main metal readback, differing only in status-name format and per-image names.
  • bootloader/src/bootnext.rs:166-170, bootloader/src/efi/proto.rs:151-163 — the change narrows how a load option's HARDDRIVE node is accepted, from ≥42 bytes to exactly 42. The node comes from Boot#### bytes, which any OS with runtime variable access can write. No test at any tier can fail on that rule: the bootloader binary has no host tests, and QEMU never takes the positive arm. The track's stage 2 (issues/the-loader-does-only-what-must-precede-the-handover.md:87-88) still names toyos_update::entry::partition as "the one HARDDRIVE rule" for the same three callers, with host tests. That would be a second decoder beside this one. The fix: restore main's load-option decoder in bootnext.rs and keep HardDrive::parse for firmware's own device paths. Stage 2 then unifies the two where its host tests and its §10.3.5.1 oracle can hold them. Alternatively, amend stage 2 in this diff to move efi::HardDrive::parse there, and show the T14 Boot chain: line taking the narrowed rule.

NOTE

  • bootloader/src/efi/mod.rs:379-393 with :536-541 — after a first ExitBootServices that fails, UEFI 2.10 §7.4.6 allows only the memory allocation services. A panic in the retry (for example fill_memory_map's assert, boot.rs:330) reaches a handler that still sees SYSTEM set and writes through ConOut. A flag set before the first exit call that makes the handler skip the print and go straight to ResetSystem closes it.
  • issues/toyos-runs-on-arm64.md:185 — "The bootloader is the uefi crate (0.26, aarch64-capable already)" is false of the tree at this head.
  • issues/the-loader-does-only-what-must-precede-the-handover.md:17-18 — the owner ruled "Own bindings". The bound adds the module path bootloader/src/efi/ as if it were part of his ruling. The path is the implementer's design.

Judged, no finding

  • Every #[repr(C)] layout was checked field by field against UEFI 2.10: the system, boot-services (44 slots, exit_boot_services at 232, open_protocol at 280, locate_handle_buffer at 312) and runtime tables; the memory descriptor; EFI_TIME; LoadedImage; TextOutput; SimpleFileSystem; File (through Flush, 88 bytes); EFI_FILE_INFO head; BlockIo and its media (rev3 granularity at 44); GOP and its mode; RNG; PciRootBridgeIo (Configuration at 136); and packed PartitionInfo (144 bytes). Every GUID, attribute, allocate/reset/search type and Appendix D code agrees.
  • extern "efiapi" is on every pointer and on efi_main, which is win64 on x86-64 and AAPCS64 on AArch64. Both are exercised: on OVMF by the x86 boots, and on AAVMF by the 24 virt_* boots, virt_user_mode's RNG seed among them.
  • The differential against uefi-raw 0.5.0/uefi 0.26.0 counts as an independent oracle. It ran on head's blobs: mod.rs differs from the patch base only in its module comment. It was red under both controls. It is right not to land: keeping it keeps the crates.
  • The 10 s Stall came with uefi-services, not from any spec, and is a flat wait. So is its 300M-iteration fallback after the exit. Dropping both follows root CLAUDE.md. The panel loses the line until stage 4 writes it to loader.log.
  • clippy.toml: both rules named paths that no longer exist. The raw opener is now private, and exclusive takes only P: Exclusive, so the type does what the rules did. The removal is forced.
  • PartitionInfo revision 0x1000: uefi 0.26's gpt_partition_entry refused any other revision too, so main already stood on this on the T14.
  • Scoped's close-on-drop assert matches uefi 0.26's assert_eq!.
  • Each unsafe block is the single FFI call or pointer read it names. The only casts left are the allocator's header word and the rev3 media read under its revision check.
  • The suite, host and clippy runs postdate the head commit. Host: 78 steps all green, EXIT=0. Suite: 41/41, EXIT=0.

SEND BACK

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Reposted by the orchestrator with the T14's model and firmware strings masked; the text is otherwise the reviewer's (original comment 6086705633, deleted because GitHub keeps edit history).

Review of #815 at a29d650e8, round 2.

Net lines (git diff --shortstat origin/main...a29d650e8, merge base c757a86b8): 23 files, +2011/−479, all production and records, no test lines. The fix round 00e4c6076..a29d650e8 (excluding the merge) is 5 files, +39/−13. The growth was accepted in round 1 and nothing since changes that.

Round-1 findings

  • BLOCKER, T14 reading — CLOSED for 00e4c6076, superseded. The orchestrator's post gives judge EXIT=0, [metal] 252 passed, 0 failed, 5 boot(s), a loader.log in every readback, Boot chain: BootNext=0002, and the gpt: line with no but firmware said. This head changes the boot path again (the load-option decoder, EXITING, and The fork's LLVM gives a loop counter's recurrence only the wrap flags proven for it, a sysroot whose compilers lose a loop's last exit is refused, and the ScalarEvolution issue is closed #790's compiler through the merge), so the requirement carries forward as the open BLOCKER below.
  • BLOCKER, load-option decoder narrowed — CLOSED. git diff origin/main a29d650e8 -- bootloader/src/bootnext.rs has no hunk in load_option_names's match, MEDIA_HARD_DRIVE or gpt_signature. Main's rule is back byte for byte: SignatureType at 41, signature at 24..40, any node of 42 bytes or more. What remains in the diff is the imports, the signatures, our_partition/hard_drive_guid (firmware's own path), entry_for and entry_number. HardDrive::parse keeps only firmware-built paths (bootnext.rs:81, main.rs:178).
  • NOTE, console after a refused ExitBootServices — CLOSED. EXITING is set at efi/mod.rs:392, before each exit call. print goes through console() (:417, :441). The handler prints only when console() is Some, and otherwise still resets through runtime services while SYSTEM is set (:550-556). The allocator still uses live(), which §7.4.6 permits.
  • NOTE, issues/toyos-runs-on-arm64.md:185 — CLOSED.
  • NOTE, the module path stated as the owner's ruling — CLOSED. The bound now reads "its own bindings, never the uefi crate's".

Merge of origin/main at c757a86b8

git diff-tree --cc d3ffcc41e is empty: no hunk was resolved by hand, and no file of the branch was touched by main's side. The PR body's builds, clippy, host and suite runs postdate the merge and the fix commit (logs 18:01–18:10 UTC, commit 17:56 UTC):

  • host: EXIT=0, [ci] Host: 78 step(s), all green;
  • suite: EXIT=0, test result: ok. 41 passed, 41 total, with 44 guests including the AArch64 boots on AAVMF;
  • both image builds and clippy: EXIT=0.

origin/main has since moved to 198a9d38e (#804). Those commits touch only src/metal.rs and tests/, which this branch does not touch.

BLOCKER

  • PR body, "Oracle 3" — the T14 reading at a29d650e8 is owed. This head's boot path differs from the one read at 00e4c6076. The load-option walk is the only code that reaches a Boot#### HARDDRIVE node, and the T14 is the only place it is exercised. The reading must show all of the following:
    • The five images' sha256 equal metal-r2/request.txt:
      • deadlinewedge 9d4b9f7d…
      • foreignrecord 41545394…
      • metalcase 6c57c6b8…
      • testcases 0cae5be0…
      • testcases-watchdog 93215087…
    • Each toyos-metal --fat32-check gives EXIT=0. The judge cargo test --test toyos-build -- --metal --metal-readback <metal-r2> boot:testcases loader_watchdog_arms blackbox_unclaimed_page blackbox_done_chain blackbox_foreign_record boot_deadline_ends_a_wedge gives EXIT=0 with 0 failed, 5 boot(s).
    • Every readback holds a loader.log.
    • Boot chain: BootNext=0002, so this loader gets the machine back. This is the restored decoder's positive arm, and it must equal main's last readback on that stick.
    • The kernel's gpt: firmware booted us from partition <guid> at LBA 2048+69632, with no but firmware said line. Every boot reaches the kernel, which is the only evidence that a successful ExitBootServices is unaffected by EXITING on the T14's firmware and that The fork's LLVM gives a loop counter's recurrence only the wrap flags proven for it, a sysroot whose compilers lose a loop's last exit is refused, and the ScalarEvolution issue is closed #790's compiler builds a loader that boots there.
    • The normalised loader.log of testcases against metal/testcases' (00e4c6076) and against a main readback on that stick, differing only in per-image names and the status-name format. The round-1 request asked for the comparison against main, along with the GOP, GCD and Root bridge lines, and the 00e4c6076 post showed none of them.

NOTE

  • issues/the-loader-does-only-what-must-precede-the-handover.md:150-155 — stage 4's handler writes loader.log through the File protocol, which §7.4.6 forbids from the first ExitBootServices call just as it forbids the console. The stage does not carry EXITING's rule, so as written its handler would undo this fix. It should say that from EXITING on, the handler skips loaderlog and goes straight to ResetSystem.
  • issues/the-loader-does-only-what-must-precede-the-handover.md:86-99 — stage 2 calls toyos_update::entry::partition "the one HARDDRIVE rule", but this branch leaves two decoders, efi::HardDrive::parse and bootnext::gpt_signature. Neither the stage's bullets nor its exit names either of them going, so the stage could close with three. Name both in the stage, with an exit that rg 'fn gpt_signature|fn parse' bootloader/src/{bootnext.rs,efi/proto.rs} (or a build) can fail.

SEND BACK

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