Skip to content

diskserver refuses no session for being the running ROOT; the loader names and types the booted slot - #955

Merged
Japabu merged 5 commits into
mainfrom
wt/toyos-no-running
Oct 20, 2026
Merged

Japabu merged 5 commits into
mainfrom
wt/toyos-no-running

Conversation

@Japabu

@Japabu Japabu commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Problem and result

diskserver's --running <guid> flag, passed by the supervisor with the ROOT the loader read, made diskserver refuse every session to that partition Held, on top of the grant its connector was minted with. The owner decided (2026-10-20, answer C): delete the flag and its refusal path; explicit grants alone govern access to the system partition, with ordinary authorization and read/write restrictions preserved. Automatic rollback is not part of the argument; the update policy is fix-forward.

Two gaps in "grants alone" are closed here, both ruled into this PR by the coordinator:

  1. A ROOT of another type. Grants reach partitions by GPT type, but the loader took the slot's ROOT by unique GUID without checking its type. A ROOT typed DATA would therefore have been writable through the DATA file server's grant. The loader now refuses, by name, a slot whose ROOT partition is not typed TOYOS_ROOT.
  2. A holder choosing its next grant. slots::grant learned the running slot's volume from the slot table, and the updater it grants to writes that table. Suppose a table pairs a decoy volume with the running ROOT as one slot, and pairs the booted volume with the idle ROOT as the other. The updater was then granted the booted volume for writing on the next boot. Now the loader names the booted slot's volume in the kernel inventory, as it names ROOT, and slots::grant judges the running slot by the loader's naming alone.

Correction to commit 3fd6d46's message. It says "the updater's write grants name only the idle slot, which slots::grant refuses to be the running one's". That held for ROOT, not for the volume, until ce4f91a. The message stays as written, since history is not rewritten; this paragraph and ce4f91a's message correct it.

Changes

  • diskserver (system/diskserver/src/main.rs): removes the RUNNING flag and its parsing, the running state through Service::new and admitted, the log line, the header paragraph and the two refusal assertions. Authorization tests are unchanged.
  • supervisor (system/supervisor/src/main.rs):
    • storage_endowment no longer reads the inventory for a block service and serves only file servers. Its unused program parameter is gone.
    • slot_grant reads Role::Slot beside Role::Root and passes it to slots::grant on both branches: claimed_slots for disks the kernel drives, and the block-service branch. If the inventory names no slot volume, nothing is granted.
  • ABI (sdk/abi):
    • KernelArgs::slot_volume_guid is appended at offset 1488. The size becomes 1504, so LAYOUT changes and the kernel refuses a mismatched loader by name.
    • inventory::Role::Slot is added, encoded as 3.
    • Role::Boot's doc said "the running slot's volume". It is actually the partition firmware loaded the loader from, the ESP. The doc is corrected, and so are the comments on the boot file server in images/*.toml and all eight tests/images/*case.toml files. kernel/src/gpt.rs's header now names the loader's four partitions (b157714).
  • kernel (kernel/src/gpt.rs): names Role::Slot from slot_volume_guid, as it names Role::Root. lib/inspect prints it as loaded.slot.
  • loader:
    • Chosen carries the slot's volume GUID into KernelArgs.
    • verify checks the located ROOT's GPT type against TOYOS_ROOT right after locate and before reading any of the slot. A mismatch logs Slot X: ROOT's partition is typed <guid>, not TOYOS-ROOT, returns Refusal::Type("root"), and reaches the kernel as slot-refused=X:type.
    • Per the review's suggestion, this check is inline; the earlier policy::typed wrapper and its test are gone.
  • lib/update/src/slots.rs: grant(table, running, volume, listed, kinds) refuses an idle slot that names the booted volume or the running ROOT, with Stray::Running ("is one of the running slot's"). It no longer takes the running slot's partitions from the table.
  • Issues:
    • issues/diskserver-serves-the-slot-the-machine-runs-from.md becomes the rejected record issues/the-block-service-does-not-refuse-the-running-root.md. It has no citations elsewhere.
    • issues/the-running-slots-volume-is-claimable.md keeps only its kernel-claim half: on a disk the kernel drives, a device holder can still claim the volume. That half closes when step 10 deletes the kernel's USB storage path. The updater half closes here.

Size: 23 files, +165 / −158, including the last commit, which changes only comments.

  • diskserver and supervisor: +42 / −86.
  • ABI, kernel, inspect and loader: +29 / −7.
  • lib/update: +45 / −9, of which about 25 lines are the new test.
  • Issues and manifests: +32 / −40.

Grants after this change

Every block grant is minted in the supervisor. Who can write the running slot:

  • DATA file server, by kind, can write: not the running ROOT, because the loader guarantees that ROOT is typed TOYOS_ROOT.
  • Log, by unique GUID, can write: names the log partition only.
  • Boot volume (the ESP) and the slot reader's ROOT listing: read-only.
  • Updater: can write the slot table and the idle slot's partitions. slots::grant now refuses the booted volume and the running ROOT as the loader named them, plus partitions of the wrong type, on another disk, unlisted or carried twice.

Checks

b157714 changes only comments: cargo run -- --build-only exits 0 at it (log). The runs below, at ce4f91a, still apply, because nothing a test or build reads has changed since.

At the head, ce4f91a (x86_64, macOS host):

check exit log
cargo test -p toyos-update --lib && cargo test -p toyos-abi --lib (51 + 53 tests, including a_table_cannot_name_the_booted_volume_idle, the review's decoy table) 0 r3-unit.log
Negative control: mutation restoring the table-trusting check, applied with git apply --check, built and tested, then reverted and cmp-verified in the same run 101: the decoy test fails with left: Ok((B, Slot { boot: [1; 16], .. })) mutation-grant.log
cargo run -- --ci host 0: 86 steps, all green ci-host-3.log
cargo run -- --build-only 0 build-only-3.log
cargo test --test toyos-build -- nvme_disk_boots 0 nvme_disk_boots-3.log

Not verified in a guest. The Role::Slot record reaching a running update's grant: no guest row launches update, and a row for it would have to install an image. The plumbing is a field copy at each hop. The refusal logic is host-tested above, and claimed_slots and the block-service branch each pass the same volume. No guest test exercises the type refusal either; inspection settles the inline check, as review agreed. No metal test was run, because nothing here depends on hardware.

Risks

  • The kernel's hold_source and the kernel-claim half of the volume issue remain until step 10, as ruled.
  • issues/the-boot-volumes-server-holds-a-claim-that-writes.md describes the boot file server's partition as "the running slot's volume" and "the volume the loader reads the kernel from". On the image the build writes, that partition is the ESP, which holds the loader and log.guid, not the kernel. The issue's premise may need restating. Recorded here, not changed.

🤖 Generated with Claude Code

Japabu and others added 2 commits October 20, 2026 19:49
The owner decided (2026-10-20) that explicit grants alone govern access to
the partition the machine runs from. diskserver's `--running <guid>` flag
made it refuse every session to that partition `Held`, on top of the grant
its connector was minted with. Delete the flag, its parse, the `running`
field and argument through `Service` and `admitted`, the log line naming
the partition, and its unit-test assertions; the supervisor no longer reads
the inventory to tell the block service the loaded ROOT, so
`storage_endowment` now serves file servers only and loses its `program`
parameter.

Ordinary authorization is unchanged: a connection with no grant reaches
nothing, a grant admits only its unique GUID or kind, and a session whose
grant does not write answers every write `ReadOnly`. No grant the
supervisor mints writes the running ROOT: file servers get DATA by kind,
the log and boot volume by unique GUID; the slot reader lists ROOTs by
kind read-only; the updater's write grants name only the idle slot, which
`toyos_update::slots::grant` refuses to be the running one's.

The issue that asked for the refusal is now a rejected record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The slot table names a slot's ROOT by unique GUID alone, and the loader
read whatever partition carried that GUID. Block grants reach partitions
by type: the DATA file server's grant, which writes, admits every
TOYOS-DATA partition. With diskserver no longer refusing the running ROOT
(the previous commit), a ROOT typed DATA would be writable through that
grant. The loader now holds the located partition's GPT type to
TOYOS-ROOT before reading a byte of the slot, and refuses the slot by
name otherwise, handing the kernel the new refusal word `type`.

The decision is `toyos_update::policy::typed`, host-tested beside the
loader's other slot decisions; a mutation that accepts any type fails
the test.

The running slot's volume issue now closes with the deletion of the
kernel's USB storage path (the small-kernel track's step 10), after which
grants alone govern every partition, rather than with a new kernel hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit to ToyOSOrg/evidence that referenced this pull request Oct 20, 2026
…e policy tests EXIT=0; mutation typed-accepts-any EXIT=101 then restored; --ci host EXIT=1 (only build/llvm.rs SDKROOT test, #952/#953); --build-only EXIT=0; nvme_disk_boots EXIT=0

Paths masked.
Japabu added a commit to ToyOSOrg/evidence that referenced this pull request Oct 20, 2026
…7a6a578cd9b542

The previous commit message abbreviated it wrongly as 8587da6e7a.
@Japabu Japabu changed the title diskserver refuses no session for being the running ROOT diskserver refuses no session for being the running ROOT; the loader refuses a ROOT not typed TOYOS-ROOT Oct 20, 2026
@Japabu

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #955 at 8587da6e7

Architecture

The deletion does what owner answer C asks. diskserver no longer has a --running flag, running field, Refusal::Held arm in admitted, log line or header paragraph for it. The supervisor's storage_endowment serves only file servers now, which removes an inventory read for the block row. Refusal::Held remains in lib/blockring because diskserver still uses it for concurrent holders (system/diskserver/src/main.rs:325), so it is not dead code. is_block still has its is_storage use. I found no remaining reference to --running or the old slug outside the commit history. Production code shrinks: diskserver and supervisor −80/+32, loader and policy +30/−2, of which 9 lines are the test.

The loader check is in the right place and fail-closed. verify reads the type GUID from the GPT entry that toyos_gpt::locate resolved (CRC-checked, primary then backup). It does this right after locate and before signed_header or any slot read. A mismatch returns Refusal::Type("root"), which choose logs as REFUSED and hands on as the word type. A slot-table edit cannot bypass the check, because the table holds only the unique GUID. The type comes from the GPT, and no grant reaches the GPT: diskserver and kernel claims both serve partition spans only.

My own grant inventory. git grep 'Scope::\(Kind\|Unique\)' finds only the supervisor's four mints. The block port is filtered out of generic receives (system/supervisor/src/main.rs:2784). Who can write the running ROOT after this change:

  • DATA file server, Kind(TOYOS_DATA) writing: cannot, because the loader now guarantees the booted ROOT is typed TOYOS_ROOT.
  • Log, Unique(log.guid) writing: only if \toyos\log.guid on the ESP named ROOT. Writing the ESP already means replacing the loader, which is the recorded Secure Boot gap, so nothing new opens here.
  • Boot volume, Unique(Role::Boot): read-only.
  • slot_grant kinds: read-only.
  • Updater, writing Unique on the table and the idle slot's two partitions: slots::grant identifies the running slot from the loader's Role::Root and refuses Stray::Running, Type, OtherDisk and Duplicate.

So no grant broadens access to the running ROOT. The kernel-driven-disk path still refuses through hold_source, as ruled.

Findings

BLOCKER issues/the-running-slots-volume-is-claimable.md:29-35: the new exit does not close the defect described in the issue's second paragraph. slots::grant knows the running slot's volume only from the slot table, which the updater writes. A table pairing a decoy volume with the running ROOT as slot A, and the running volume with the idle ROOT as slot B, gets the running volume granted for writing on the next boot. Deleting the kernel's USB storage at step 10 does not change this. After step 10, slot_grant takes its block-service branch (system/supervisor/src/main.rs:1124-1127), and that branch has the same flaw today. It mints Unique(slot.boot) writing from the same table-trusting slots::grant. The exit's "as every partition on diskserver's disk already is" therefore describes the defect as if it were the remedy.

Consequence: the issue would read as resolved by step 10 while a holder can still widen its own grant to the running slot's volume. That contradicts "rights can be narrowed, never widened by a holder" and slots::grant's own contract ("else the holder of one grant could name its next one anywhere"). Owner answer C covers grants deciding access. It does not cover a grant holder choosing its next grant. The old exit closed this half by having the loader hand over the booted volume's GUID.

Fix: keep this half open with an exit that closes it. That exit need not be a kernel hold. For example, the loader names the booted slot volume in the inventory as it names ROOT, and slots::grant checks runs.boot against it. Alternatively, if this half is to be accepted, it goes to the owner. The same overstatement appears in 3fd6d46's message ("the updater's write grants name only the idle slot, which slots::grant refuses to be the running one's"). That statement holds for ROOT but not for the volume. Correcting the PR description belongs to the orchestrator.

SUGGESTION lib/update/src/policy.rs:97-106,160-166: typed is a public wrapper around one !=, called once. Its test and negative control show that != works. They do not cover the part that could actually be wrong, which is the loader passing the located entry's type and TOYOS_ROOT in the right place, and inspection settles that part. An inline if stated != Guid::TOYOS_ROOT { return Err(Refusal::Type("root")) } in verify plus the Refusal::Type variant would do the same job, with no function, test or doc paragraph. This is optional because it matches the existing admits pattern.

Evidence

I checked the round-2 logs in the evidence repository. update-test.log shows 5 passed. mutation.log shows the && false patch applied, the test failing with left: Ok(()), right: Err(Type("root")), exit 101, and the patch reverted and verified clean. nvme_disk_boots-2.log shows 1 passed. On this row the loader reads ROOT from diskserver's disk, so the run covers a correctly typed ROOT passing the check and diskserver booting without the flag. Reusing the round-1 diskserver test is valid, since round 2 touches no diskserver or supervisor code. I accept the build/llvm.rs SDKROOT red as independent: it is a host-environment test unrelated to these files and is filed in #952. Skipping a guest test for a mistyped ROOT is reasonable, because inspection establishes the loader wiring.

The rejected record issues/the-block-service-does-not-refuse-the-running-root.md is accurate. Its frontmatter (none/rejected) follows issues/README.md. It states answer C without claiming a reliance on rollback. The old slug has no remaining citations.

SEND BACK

`slots::grant` learned the running slot's volume from the slot table, which
the updater it grants to writes. A table pairing a decoy volume with the
running ROOT as one slot, and the booted volume with the idle ROOT as the
other, was granted the booted volume for writing on the next boot: a holder
widening its own next grant. The previous commits' message overstated this
as closed ("the updater's write grants name only the idle slot"); that held
for ROOT and not for the volume.

The loader now hands the kernel the chosen slot's volume GUID
(`KernelArgs::slot_volume_guid`, appended at offset 1488, so `LAYOUT`
changes), the kernel names it in the inventory as `Role::Slot` beside
`Role::Root`, and `slots::grant` takes it and refuses an idle slot naming
either the booted volume or the running ROOT, `Stray::Running`, judging the
running slot by the loader's word alone. Both supervisor branches, the
kernel-claim one and the block-service one, pass it; a boot whose inventory
names no slot volume grants nothing.

`Role::Boot`'s doc called it the running slot's volume; it is the
partition firmware loaded the loader from (the ESP), and the manifests'
comments on the boot file server said the same; both are corrected.

The ROOT type check is inlined in the loader's `verify` as review
suggested: `policy::typed` and its test go, `Refusal::Type` stays.

`issues/the-running-slots-volume-is-claimable.md` keeps only its
kernel-claim half, which closes with the kernel's USB storage at step 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit to ToyOSOrg/evidence that referenced this pull request Oct 20, 2026
…-update+toyos-abi unit EXIT=0; mutation grant-trusts-table EXIT=101 then restored; --ci host EXIT=0; --build-only EXIT=0; nvme_disk_boots EXIT=0
@Japabu Japabu changed the title diskserver refuses no session for being the running ROOT; the loader refuses a ROOT not typed TOYOS-ROOT diskserver refuses no session for being the running ROOT; the loader names and types the booted slot Oct 20, 2026
@Japabu

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up review of #955 at ce4f91ace (round from 8587da6e7)

Architecture

The previous blocker is resolved, and the fix has the right owner. The loader is the only component that knows which volume it read the kernel from. It now hands that volume over as KernelArgs.slot_volume_guid, and Chosen.volume is slot.boot, the GUID read_section read the kernel and cmdline from (loader/src/slot.rs:146-160). The kernel relays the fact and nothing more. gpt::init pushes (Role::Slot, …) when the GUID is nonzero, exactly as it does for Role::Root, so the kernel adds no policy. slots::grant no longer reads runs from the table. It refuses Stray::Running when either idle partition equals the loader-named volume or the loader-named ROOT (lib/update/src/slots.rs:308-312). That check runs before the listing checks, so a duplicate GUID cannot get around it. Both supervisor branches read Role::Slot beside Role::Root and pass the same volume. If the inventory names no slot volume, slot_grant returns an error, the caller logs "no slot to grant", and nothing is minted or claimed.

The ABI change is coherent:

  • The field is appended at offset 1488, and the size is 1504.
  • LAYOUT is derived from size_of::<KernelArgs>(), so a stale loader is refused by the existing layout check.
  • Role::Slot = 3 is added to both encode and decode, and the lib/inspect match is exhaustive.
  • Byte order matches the inventory: root_partition_guid and slot.boot are both raw GPT bytes, the same representation grant already compares against Listed.unique_guid.

Does anything else still trust the table? idle() still chooses the running slot by matching the loader-named ROOT against the table. That is sound, because both partitions of the idle slot are now checked against the loader's naming. system/update/src/install/mod.rs:61-64 reads the running slot's version from the table for policy::installable. That is the updater limiting itself on a table it can already write, not a grant decision, so it does not widen any authority. I found no other place that grants from table contents. The kernel-claim half remains open in issues/the-running-slots-volume-is-claimable.md, as ruled. Its rewritten paragraphs are accurate.

The inline ROOT type check matches the earlier suggestion, and policy::typed and its test are gone. The round adds +86/−56 lines. Of that, 23 are the new test, and the rest is the field's plumbing and the type-check simplification.

Findings

BLOCKER tests/images/{acpicase,jobcase,latencycase,metalcase,metaldevicecase,netcase}.toml (comment above [programs.fileserver]) and kernel/src/gpt.rs:4-8: these still give the false account of Role::Boot that this round corrects elsewhere.

  • The manifests. The round fixes "the running slot's volume" to "the boot volume" in images/system.toml and images/console.toml, which is what the PR body says it did for "the manifests' comments". The six test manifests carry the identical comment and still say "the running slot's volume". They now contradict the corrected Role::Boot doc.

  • The kernel header. kernel/src/gpt.rs's header is in a file this round changes, and it says the loader names "the ROOT it read, the running slot's volume and the log's … so the file servers for those roles find them".

    • After this change the loader does name the slot volume, but no file server serves it. It exists for slots::grant.
    • The boot file server's partition (Role::Boot) comes from firmware's boot_partition_guid, and the loader does not name it.

    A reader would conclude that the boot file server serves the kernel's volume, which is the misreading this round set out to remove.

  • Fix. Apply the same one-line comment fix to the six manifests. Reduce the header sentence to what is true. For example: the loader names the ROOT it read, the booted slot's volume and the log partition, and firmware names the boot partition. The kernel answers all four in the inventory. No test is needed.

SUGGESTION system/supervisor/src/main.rs:1090-1096: the reflow left the line /// it — and broken mid-sentence. Rewrap the paragraph.

Evidence

I checked the round-3 logs in ToyOSOrg/evidence pr-955 @ a2c751bd8. REVISION is ce4f91ace and exits.txt matches the PR table.

  • Unit tests. r3-unit.log shows 51 + 53 passed, including a_table_cannot_name_the_booted_volume_idle.
  • Negative control. The mutation restores exactly the old runs = table.slot(idle.other()) check. The new test then fails with left: Ok((B, Slot { boot: [1;16], root: [4;16], .. })), which is the decoy table granting the booted volume. The run exits 101. The patch is reverted and cmp confirms the file is clean. This test catches precisely the defect the previous review found, and the existing bent([1;16],[4;16]) case still covers the plain case.
  • Host checks and build. ci-host-3.log shows 86 steps, all green. build-only exits 0.
  • nvme_disk_boots passed. The kernel checks LAYOUT before it reads anything else, so the pass shows that the rebuilt loader and kernel agree on the 1504-byte KernelArgs, and that a correctly typed ROOT passes the check.
  • What no guest test covers. No guest test exercises Role::Slot reaching update's grant. I accept that. The path is a field copy at each hop, plus an exhaustive enum and encode/decode added in one place. Inspection establishes it, and a guest row would have to install an image.
  • Comment-only fixes. The required finding above changes comments only, so the evidence needs no rerun.

SEND BACK

The test images' comments on the file servers called the boot role "the
running slot's volume", as the shipping manifests did before the previous
commit; it is the ESP. kernel/src/gpt.rs's header named three loader
partitions; it names four now that Role::Slot is one, and no file server
serves the slot volume. The supervisor's slot_grant doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit to ToyOSOrg/evidence that referenced this pull request Oct 20, 2026
@Japabu

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Re-review blocker addressed in b157714. The commit changes only comments.

  • Test-image comments: I corrected the "running slot's volume" comment above [programs.fileserver] in eight tests/images/*.toml files, not six. proctreecase.toml and testcases.toml carried the same comment. git grep "running slot's volume" -- images tests kernel system sdk now finds nothing.
  • kernel/src/gpt.rs header: it now names the loader's four partitions: ROOT, the booted slot's volume, the log, and the ESP firmware loaded the loader from (Role::Boot). It also says which consumer reads which partition: the log and boot file servers, and the supervisor for the running slot. No file server serves the slot volume.
  • Supervisor doc comment: I rewrapped the broken slot_grant doc comment, as you suggested.

Compile check: cargo run -- --build-only exits 0 at the new head (log). I reran nothing else, because nothing a test reads changed. The PR body is updated to match.

@Japabu

Japabu commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: the round-3 review's comment-only blocker is resolved at b157714 (checked the diff ce4f91a..b157714: eight test manifests, kernel/src/gpt.rs header, supervisor doc rewrap; comments only, --build-only exit 0). Round-3 evidence at ce4f91a stands. Reviewed: LAND. Queues after #954's required-check transition.

@Japabu
Japabu marked this pull request as ready for review October 20, 2026 19:33
@Japabu
Japabu enabled auto-merge October 20, 2026 19:33
@Japabu Japabu closed this Oct 20, 2026
auto-merge was automatically disabled October 20, 2026 19:35

Pull request was closed

@Japabu Japabu reopened this Oct 20, 2026
@Japabu
Japabu added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 4b8684f Oct 20, 2026
4 checks passed
@Japabu
Japabu deleted the wt/toyos-no-running branch October 20, 2026 20:08
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