Skip to content

A receive reports what each handle names, and a typed handle is made of a received one only where the kernel said so - #1239

Merged
Japabu merged 4 commits into
mainfrom
handles3-received-kind
Nov 12, 2026
Merged

Japabu merged 4 commits into
mainfrom
handles3-received-kind

Conversation

@Japabu

@Japabu Japabu commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

SYS_MESSAGE_RECV answered handle numbers and a count. What a received handle named was only the sender's word, a typed call on a handle of the wrong kind ends the caller with a handle fault, and SYS_FSTAT tells apart only a region and a connection. So any peer that could send a process a handle could end that process. Reading the code shows the reach: toyos::launch's process handle, and every bell a client takes from its server (audio, net streams and sockets, the link driver, the disk session).

Closes issues/a-received-handle-has-no-knowable-type.md.

Resulting behaviour

  • The kernel reports each handle's kind. A receive now writes [ReceivedEntry { handle, kind }]. The kind is an ObjectKind word read during the same hold that installs the handle, so the receiver needs no second call and has no window. ObjectKind becomes a contract: a word is never renumbered or reused, and a new kind takes the next word. The entry carries the kind and no rights, because a missing right already answers PermissionDenied and ends nobody.
  • The SDK has one checked way to a typed handle. A batch yields ReceivedHandle. ReceivedHandle::typed::<T>() is the only way to get a typed wrapper from it, and only for a T: OfKind, a type that any object of its kinds serves. If the kernel did not report one of T::KINDS, it refuses with WrongKind and closes the handle. A kind word this SDK does not know is also refused. A claim's class types (PciDev, AcpiDev, FramebufferDev, UartDev) are not OfKind, because a claim's kind does not say its class: a received claim can become only a Device.
  • The unchecked constructors are gone. Connection::received, Namespace::received, and From<OwnedHandle> for Pipe, Waker and Bell are deleted. FromHandle::from_handle is now unsafe. Its contract is that the object takes every call of the type, which for a class type means a claim of that class. An endowment is its parent's word, a minted claim the kernel's, and a duplicate of a typed handle its holder's. SysCap's claims take a ClaimType, Device or a class type, whose class the claiming call names. std's adopt_namespace is unsafe for the same reason.
  • Bare handles stay bare. A received handle still converts to a bare OwnedHandle to send on, endow or map. SharedMemory::lent keeps SYS_FSTAT, which also says whether a region is RAM and how long it is. Paged::received keeps its own probe.
  • Every receive site is converted: the supervisor (launch extras, place and job, session place, handed-over connections), session, logkeeper, diskserver and blockserve, hda, netstack, the link driver, the audio and net clients, logkeeper-api, osbench and the guest tests. The supervisor now refuses by name a launch whose extras, place or job are not a connector, a process and a job. Before, it left both checks to the kernel's get_sent.
  • Manual. architecture/authority.md states the rule: a received handle arrives with its kind, and a receiver takes it as a kind only where the kernel reported that kind.

Checks

The owner's testing policy applies: only the unit and host tests of the touched crates, clippy on the touched packages, and a whole metal boot for an object-surface change.

All checks below ran at 34cb4c918 plus this change's tree, which is the pushed commit's tree. The rebase onto b56693152 brought only issues/ edits. Each log is linked in the evidence below. Two edits came after the image build and before the commit: a reflowed message_recv doc comment, which the ABI checks compiled, and messages_are_whole reading only got.handles entries.

check why exit
cargo test -p toyos-abi -p toyos-blockserve -p toyos-link -p toyos-session the ABI and the touched host libraries 0
cargo test -p toyos -p logkeeper-api the SDK, including a_received_handle_is_typed_only_as_the_kind_the_kernel_reported, and logkeeper-api 0
cargo test --manifest-path system/{diskserver,hda,logkeeper,netstack,session,sshserver}/Cargo.toml the touched programs with host tests 0 each
cargo clippy in CI's shapes: the touched host members --all-targets, toyos --all-targets, toyos --lib --features fd-table, toyos-abi with undocumented_unsafe_blocks, and the x86-64 kernel with default features and with boot-actuators,test-actuators lint on what changed 0 each
cargo run -- --build-only the kernel, std and every ToyOS-only receiver (supervisor, osbench's ToyOS half, the guest tests) compile 0, run before two later edits; the metal staging rebuilt every guest binary and image at the pushed commit

The supervisor's host tests are in its nested lifecycle crate, which this change does not touch. osbench builds only for x86-64 ToyOS and Linux. I did not run the kernel's host library (kernel/pure), the models or aarch64: the change touches neither kernel/pure nor any arch code.

Negative control. With admit mutated to accept any kind this SDK knows, the SDK test reds: left: Ok(()) right: Err(WrongKind { wanted: [EventWait], got: Some(EventSignal) }), exit 101.

Guest. The receive's guest claim lives in messages_are_whole. Its new arm sends eight handles of eight kinds, checks each kind reported beside its handle, and checks that a pipe end taken as a process is refused by name while the receiver lives. process_tree covers launch's typed Process. Both are shared-boot members on metal, not QEMU rows, so no guest row runs them. With the benches down they are owed on metal (below).

QEMU rows that reach the changed receivers, one batch on the guest runner, run 43049964255 at ad7d762c2: 4 passed, 4 total.

At the current head 451269586, rebased onto #1246: guest run 43108314485 covers it at that head. The rows are session_lifecycle, a_launch_gives_each_unsent_stdio_slot_a_null, netstack_holders_exported_to_children (round 1's ask_export and the socket adopt_exported), hda_plays_to_the_controller_that_reaches_an_output, and libc_children (spawn_served_stdout: files::export and the file adopt_exported). The result follows when it arrives.
session_lifecycle (a desktop session's typed namespaces, its owner's place and its started apps' processes), a_launch_gives_each_unsent_stdio_slot_a_null (the supervisor's typed extras and job), netstack_holders_exported_to_children (exported-end adoption, stream bells), and hda_plays_to_the_controller_that_reaches_an_output (the audio bell, hda's ring).

Evidence (ToyOSOrg/evidence pr-1239 at 9448c5dc): index, host checks (summary), negative control, build, metal staging and request, guest rows.

Owed on metal (the benches are down; metal is no longer a landing condition). This is recorded in issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md:

  • Rows: the testcases, testcases-watchdog and shared-debug boots of main, whole, and the proctreecase boot.
  • Bench: the T14.
  • Main must show: messages_are_whole: each handle arrived with its kind, and another kind was refused by name, then messages_are_whole: ok. mutual_kill, moved_rights and served_file_offset pass. process_tree passes, with a_pipe_is_no_place refused by the supervisor by name.

The images staged at ad7d762c2 for those boots are deleted, and the request stays as the record of what was asked.

Review round 1

  • Blocker fixed (d8090d44c): the class types left typed, by type. KINDS moved from FromHandle to OfKind, which typed requires and the four class types do not implement. ClaimType marks what SysCap mints and replaces the const assert. The FromHandle Safety text now states the per-type contract.
  • Rebased onto 6d0cb3ae9. The deleted record stays deleted. served_file_offset's conflict is resolved by main's spawn lines and by files::export returning a Connection. That one change is in the rebased commit because the resolution needs it: the descriptor owner already types that end through typed, so the test needs no unsafe.
  • Suggestions taken: ReceivedHandle::kind (no caller) is gone. ask_export types netstack's end. Both adopt_exporteds take a Connection and are safe, with the one unsafe where fd takes an endowed export, whose kind is the parent's word. The connection calls that take any AsHandle are recorded in issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md.
  • ObjectKind numbering (the owner's coordinator's decision): kind words are ABI and never renumbered, and A child's handles are a list its parent names: labels and namespaces leave the kernel #1252 retires word 8 instead of shifting the rest. ObjectKind's definition now also says that a retired word stays unused. Whichever of this PR and A child's handles are a list its parent names: labels and namespaces leave the kernel #1252 lands second keeps FromHandle::from_handle unsafe.
  • Every server admits its clients through one table, toyos::admission #1242: its admission::Client::take_handles must return [ReceivedHandle; N], and its callers must use typed. Its agent does that, whichever of the two lands second.
  • Checks for this round are compile checks, as the review asked: cargo test -p toyos --lib ipc:: and clippy in CI's shapes (toyos --all-targets, toyos --lib --features fd-table, toyos-abi), each exit 0. On the guest side, a staging of one shared member compiled std, libc, the testcases image's programs and every guest binary at d8090d44c, exit 2 (staged). That image builds no compositor. The other callers of the changed bounds compile by inspection: the compositor, console, uart and acpiserver call endow::device, whose FromHandle bound is unchanged, and metalprobe's cap.claim yields FramebufferDev, which is a ClaimType. See summary and guest compile.
  • Runtime path: one changed: ask_export gained a refusal of an end that is no connection. Its accepting path holds by inspection. netstack makes that end in Exports::connect, where it also makes an accepted stream's end, and accept already types that one through typed::<Connection>.

Review round 2 (15fa1e54d)

  • Comments only. The supervisor's translated now gives the claim's class: a claim minted for function, a PCI function. SysCap::mint's SAFETY comment and ClaimType's doc now say that pairing a class type with the class a call claims is the caller's to keep, because the bound does not hold it.
  • Suggestion not taken here: ClaimType carrying its class, so that a mismatched T is refused at compile time. The calls are sound as they are, and that would widen this PR's surface past the record it closes.
  • The round-1 text above is corrected: one runtime path did change, and the guest compile did not build every program.

Rebase onto #1246 (06fcb81ea)

  • Conflict: undelivered in kernel/src/syscall/ipc.rs. A thread carries its process's handles, and a lookup takes no lock but the table's #1246 moved its lookup from process::with_process_data(|data| … data.handles …) to process::with_handles(|handles| …), under the handle table's own lock. This PR changed its parameter to &[ReceivedEntry] and its loop to close each entry's .handle.
  • Resolution: both changes hold. The function takes &[ReceivedEntry] and closes each through process::with_handles. Every other site this PR changes in the kernel auto-merged: recv_now installs under the receiver's handle-table lock, as A thread carries its process's handles, and a lookup takes no lock but the table's #1246 now describes it, and reads each kind in that hold.
  • Checks at 451269586: the kernel's host tests and its clippy shapes (host library, x86-64 default and with boot-actuators,test-actuators, AArch64), each exit 0 (summary). Guest: run 43108314485 at 451269586, with the same five rows. Run 43092098455 at 15fa1e54d passed 5 of 5 (log) and is superseded by the rebase.

Unresolved

  • issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md stays open. A region has no typed wrapper to refuse at, and that record's exit is the kernel's.
  • No fork calls message_recv or the SDK's receive. I searched every fork clone and ~/.cargo/git/checkouts/, so no fork pin moves.

🤖 Generated with Claude Code

@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #1239 at ad7d762c2

Architecture

The ownership is right. The kernel knows a handle's kind, so the receive's own answer reports it. The kernel reads the word in the hold that installs the handle. The exhaustive kobject! arm (kernel/src/object/mod.rs:188) maps kernel variants to words, so a kernel kind with no ABI word fails to compile. ReceivedEntry.kind is a u32 and not an enum, so a word from a newer kernel arrives as data and cannot cause undefined behaviour. The entry carries the kind and no rights, as decided.

Kernel object types stay separate from protocol types. The ABI reports kernel kinds only, so a Connection from typed is still just a connection, and which protocol it speaks remains the peer's word. The SDK has one checked conversion, ReceivedHandle::typed. The unchecked constructors are deleted or made unsafe. This is the smallest shape that meets the record's exit. Net size is +437/−307, of which −97 is the closed record. Test growth is one host test and one guest arm.

I traced every receive site:

  • Typed uses go through typed: audio, net streams and sockets, link driver, diskserver, logkeeper, logkeeper-api, session, the supervisor's launch, place and sessions, fs, launch, osbench and the tests.
  • Bare uses go to SharedMemory::lent, Region::judged, Paged::received or Image::held, which probe with fstat, or the handle is relayed or endowed.
  • The compositor's and terminal's foreground line (sdk/toyos/src/surface.rs:304, :386) keeps bare handles and calls process_quit. That call is safe today only through rights, not kind: get checks MANAGE before the type (kernel/src/object/handle.rs), and only Process handles carry MANAGE.

Findings

BLOCKER

  1. sdk/toyos/src/endow.rs:95-98, endow.rs:51-55, sdk/toyos/src/ipc.rs:645 — typed admits a device claim of any class as PciDev, AcpiDev, FramebufferDev or UartDev.
    • Problem: each of the four declares KINDS = [Device], so ReceivedHandle::typed::<PciDev>() checks only that the handle is some device claim.
    • Consequence: the kernel ends a process that makes a PCI or ACPI call on a claim of another class: HandleError::WrongType leads to handle_fault (kernel/src/syscall/device.rs:68, :188-201). Claims carry TRANSFER (kernel/src/object/ops.rs:33-35), so a peer can send one. For four public types, the PR's one checked conversion, and the FromHandle Safety text ("the kernel ends a process that makes this type's calls on a handle to anything else"), are therefore false. A peer could still end a receiver with the wrong handle through a safe call, which is the defect the record closes.
    • Fix: no receive site types a claim today, and the fix is type-level. Either keep the class-typed wrappers out of typed, for example with a bound that only kind-exact types implement while Endowments::take and SysCap::mint keep FromHandle, or let a received claim become only a Device.
    • Evidence needed: a compile check suffices. No runtime path changes, so the staged metal stays valid evidence.

Required for landing (mechanics and coordination, not code defects)

Evidence

  • Host checks: they ran at 34cb4c918 plus the change's tree. git diff --name-only 34cb4c918 b56693152 touches only issues/, so the rebase claim holds. The scope is sufficient under the owner's policy.
  • Negative control: the admit mutant reds the SDK test, so it exposes the SDK's decision. On the kernel side, the kind mapping is fixed by type. The wire layout (entry stride, word value) is checked only by the new messages_are_whole arm on metal. That is adequate.
  • Guest run 43049964255: success at headSha ad7d762c2, 4 passed of 4. I verified this.
  • Metal must show:
    • testcases, testcases-watchdog and shared-debug green as whole boots, with messages_are_whole: each handle arrived with its kind, and another kind was refused by name, then messages_are_whole: ok, and mutual_kill, moved_rights and served_file_offset passing;
    • proctreecase with process_tree passing. That covers launch's typed Process, and a_pipe_is_no_place refused, now by the supervisor's own refusal by name.

SUGGESTION

  • sdk/toyos/src/ipc.rs:639 — ReceivedHandle::kind() has no caller, and WrongKind::got already carries the kind. Delete it.
  • sdk/toyos/src/net/stream.rs:975 — ask_export returns netstack's end bare, and it is later adopted through unsafe adopt_exported, whose SAFETY comment says "its parent's to say". The parent never checked netstack's word. Dir::export (fs.rs:1033) types the same kind of handle, so end.typed::<Connection>() here would make the chain checked.
  • sdk/toyos/src/files/owner.rs:377 — adopt_exported is safe and wraps an unchecked handle as a Connection, while net::adopt_exported became unsafe for the same precondition. Make the two consistent.
  • sdk/toyos/src/files.rs:317 — files::export could return Connection, which removes the new unsafe in served_file_offset.
  • Generic connection calls on any AsHandle (FrameRx::{pump, recv}, ipc::{send, signal, try_*}) still end the caller on a received bare handle of the wrong kind, because the message syscalls use get. No receive site does this, and the PR does not claim otherwise. Narrow these calls later or note them in a record.

Verdict

SEND BACK for blocker 1. Once it is fixed and the branch is rebased: LAND pending the metal results above, with the landing order against #1252 decided.

Japabu added a commit that referenced this pull request Nov 11, 2026
… is known

`PciDev`, `AcpiDev`, `FramebufferDev` and `UartDev` declared `KINDS =
[Device]`, so `ReceivedHandle::typed` made any device claim one of them, and
a claim of another class then ended the receiver at its first class call.
A claim's kind does not say its class.

`FromHandle` keeps only the unsafe constructor. Its Safety text is now what
the kernel holds a type's calls to: the object takes every call of the type,
which for a class type means a claim of that class. `KINDS` moves to
`OfKind`, which only types that any object of their kinds serves implement,
and `typed` requires it. The four class types implement `FromHandle` and
`ClaimType`, a marker for what `SysCap` mints. It replaces the const assert
that a minted type is a device's, so `SysCap::claim` and the guest tests'
`function::claim` take the class from the call that names it. A received
claim can become only a `Device`.

From the review's suggestions:
- `ReceivedHandle::kind`, which had no caller, is gone.
- `ask_export` types netstack's exported end before it hands it on.
- Both `adopt_exported`s take a `Connection` and are safe. The one `unsafe` is
  where `fd` takes an endowed export, whose kind is the parent's word.

`ObjectKind`'s definition now says that a kind which goes leaves its word
unused for good. A new record names the connection calls that still take any
`AsHandle`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu force-pushed the handles3-received-kind branch from ad7d762 to d8090d4 Compare November 11, 2026 17:57
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2 of #1239 at d8090d44c

Architecture

The split fixes round 1's blocker by type. typed now requires OfKind. Only types that any object of their kinds can serve implement it: the nine in from_handle!, plus OwnedHandle, Notice, Waker and Bell. The four class types get FromHandle and ClaimType from class_of_claim! and never get OfKind. Their fields are pub(crate), and the orphan rule stops a downstream crate from adding OfKind to an SDK type.

I traced every typed call. Each T is Connection, Pipe, Process, Job, Namespace, Connector, Bell, Waker or narrowed_as's T: OfKind, so no received handle becomes a class type without a class check. The remaining unsafe from_handle sites are:

  • endowments: the parent's word, including fd.rs:473;
  • SysCap::mint: the caller's class;
  • the supervisor's translated: a PCI function's claim;
  • two duplicates of a child's process handle;
  • std's adopt_namespace: the caller's word.

This is the simplest form that keeps every call site safe. Without ClaimType, SysCap would mint a Connection again, and that check was a const assert on the KINDS that has moved away. Without class types in FromHandle, every claimant would need an unsafe conversion from Device.

Round 1 adds +99/−52 across 12 files, of which 19 lines are the new record. The whole PR is +497/−321 against 6d0cb3ae9.

Suggestions taken, checked:

  • ReceivedHandle::kind is gone and had no caller. messages_are_whole:215 calls the ABI's ReceivedEntry::kind.
  • ask_export types netstack's end (stream.rs:976).
  • Both adopt_exporteds take a Connection. The one unsafe (fd.rs:473-477) is an endowment taken under an fd:<n>:file or fd:<n>:exported label. Its SAFETY comment matches Endowments::take's: an endowment's kind is the parent's word.
  • files::export returns a Connection. libc's posix_io.rs:122 adapts with into_owned.

The new record is accurate. Both message syscalls resolve the handle with get::<ConnectionEnd> (kernel/src/syscall/ipc.rs:478, :593), and get ends the caller on WrongType (kernel/src/object/handle.rs:54). FrameRx::{pump, recv} take &impl AsHandle, and the free send, signal and try_* functions take a BorrowedHandle of any kind. It is a finding with an exit, as issues/README.md requires.

Rebase: the closed record stays deleted, and nothing in the tree names it. served_file_offset takes main's spawn lines and needs no unsafe. The ObjectKind text is consistent with from_word. A retired word breaks the dense-index const assert at compile time until from_word allows a gap, which is the loud outcome we want.

Findings

BLOCKER

  1. sdk/toyos/src/syscap.rs:91 and system/supervisor/src/main.rs:3031: two SAFETY comments that make a class type still give the reasoning this round refuted.
    • Problem: the new contract (endow.rs:49-52) requires a claim of that class for a class type. translated says "a device claim, as either type holds", which is exactly the argument that a claim's kind decides its class type. mint says "the kernel made a device claim, which T is a type of". But T is the caller's own choice, made independently of class, so cap.claim::<PciDev>(DeviceType::Uart) compiles.

    • Consequence: both calls are sound today. translated's callers (:1373, :3206) pass only claims minted for function, a PCI function, and every production mint is Device. But these unsafe sites are what this PR makes auditable. A reader who copies "a device claim, as either type holds" would justify making a received Device into a PciDev.

    • Fix (comment only, nothing reruns):

      • At main.rs:3031, give the class, for example "minted for function, a PCI function".
      • At syscap.rs:91, say that the claim is of the class the caller asked for, and that pairing T with that class is the caller's job.
      • Say the same in ClaimType's doc (endow.rs:66-67). "which the minting call's class decides" reads as though the SDK enforced the pairing.

      Round 1 accepted that SysCap's safe calls leave this pairing to the caller, so only the text is required.

SUGGESTION

  • ClaimType could carry its class (const CLASS: Option<DeviceType>, None for Device). claim, claim_function and claim_isa could then refuse a mismatched T at compile time, and mint's SAFETY would rest on the kernel's word alone.

Evidence

  • Round 1's compile checks ran at d8090d44c with a clean tree (r1/summary.txt), each exit 0:

    • cargo test -p toyos --lib ipc::: 11 passed;
    • clippy on toyos --all-targets, on toyos --lib --features fd-table, and on toyos-abi with undocumented_unsafe_blocks.

    The guest staging (r1/guest-compile.log, exit 2, staged) compiled std, libc, rust-tests and the testcases kernel and ROOT. The PR text says "every program", but the log says that image "builds no compositor". The other callers of the changed bounds compile by inspection: the compositor, console, uart and acpiserver call endow::device, whose FromHandle bound is unchanged, and metalprobe's cap.claim yields FramebufferDev, which is ClaimType. That meets round 1's compile bar.

  • Staged metal at ad7d762c2: the claim that it stays valid holds. The PR text's reason, "this round changes no runtime path", is not exact: ask_export gains one refusal (stream.rs:976). On its accepting path, netstack makes the exported end in Exports::connect (system/netstack/src/serve.rs:155-158). It makes an accepted stream's end there too, and accept already passes that end through typed::<Connection> (stream.rs:774), so inspection establishes the accepting path. The PR text needs correcting, but nothing needs re-staging.

  • Merge: git merge-tree against current main 45935f4b9 is clean. Against A poll belongs to the handle it was made through, and a ring to its one submitter #1261, issues/parallel-tests-red-under-other-suites.md conflicts as modify/delete. Main deleted it in 9eba68cde, so whichever PR lands second keeps the deletion.

Landing conditions: metal, from the queued ad7d762c2 images

  • testcases, testcases-watchdog and shared-debug green as whole boots. They must show messages_are_whole: each handle arrived with its kind, and another kind was refused by name, then messages_are_whole: ok, and mutual_kill, moved_rights and served_file_offset passing.
  • proctreecase with process_tree passing: launch's typed Process, and a_pipe_is_no_place refused by the supervisor by name.

Verdict

SEND BACK for the comment-only blocker, which needs no rerun. After that fix: LAND pending the metal results above.

Japabu added a commit that referenced this pull request Nov 11, 2026
…aimed for

Two SAFETY comments still argued that a claim's kind decides its class type.
That is the reasoning round 1 refuted. They now give the class instead.
- The supervisor's `translated` makes a `PciDev` of a claim minted for its
  `function`, a PCI function.
- `SysCap::mint` makes `T` of a claim of the class the request names.

`SysCap::mint`'s comment, and `ClaimType`'s doc, now also say that pairing a
class type with the class a call claims is the caller's to keep: the bound
does not hold it. Comments only.

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

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: verified 15fa1e5 against follow-up r2. git diff d8090d44c 15fa1e54d changes only comment and doc lines (the supervisor's SAFETY comment, SysCap::mint's SAFETY comment and ClaimType's doc). The PR text is corrected. The images at ad7d762 stand. Lands on the T14 boots (testcases, shared-debug, proctreecase) with the r2 criteria: messages_are_whole kind lines, mutual_kill, moved_rights, served_file_offset and process_tree passing.

Japabu added a commit that referenced this pull request Nov 11, 2026
…le the benches are down

The benches are down, so metal is no longer a landing condition. What only
metal shows of this change is recorded as owed in
`issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md`:
the T14's `testcases`, `testcases-watchdog`, `shared-debug` and
`proctreecase` boots of main. They carry the shared-boot members
`messages_are_whole`, `mutual_kill`, `moved_rights`, `served_file_offset` and
`process_tree`, which no guest row runs. A record edit only.

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

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: landing under the benches-down rule. Review r2 text fixes verified earlier; head 5773d0d is 15fa1e5 plus one record (verified). Guest run 43092098455 at 15fa1e5: success. The PR's own shared-boot members (messages_are_whole, process_tree, …) are owed on metal and will also run on the shared-boot guest row (#1271) on main; a red there is fixed forward.

@Japabu
Japabu marked this pull request as ready for review November 11, 2026 21:44
@Japabu
Japabu added this pull request to the merge queue Nov 11, 2026
@Japabu
Japabu removed this pull request from the merge queue due to a manual request Nov 11, 2026
Japabu and others added 4 commits November 12, 2026 00:00
…of a received one only where the kernel said so

`SYS_MESSAGE_RECV` answered handle numbers and a count. What a handle named
was the sender's word, a typed lookup of the wrong kind ends its caller, and
`SYS_FSTAT` settles two kinds of twenty: so whoever could send a process a
handle could end it. Read from the code, the reach was `toyos::launch`'s
process handle and every bell a client takes from its server (audio, net
streams and sockets, the link driver, the disk session).

The receive now writes `[ReceivedEntry { handle, kind }]`, the kind an
`ObjectKind` word read in the hold that installs the handle, so there is no
second call to forget and no window. `ObjectKind` becomes a contract: a
word is never renumbered or reused. The pair carries the kind alone.

In the SDK a batch yields `ReceivedHandle`, and `ReceivedHandle::typed::<T>()`
is the one way to a typed wrapper: refused, and the handle closed, unless the
kernel reported one of `T::KINDS`, a word this SDK does not know included.
The unchecked constructors are gone (`Connection::received`,
`Namespace::received`, `From<OwnedHandle>` for `Pipe`, `Waker` and `Bell`),
and `FromHandle::from_handle` is `unsafe`, its contract the kind: an
endowment is its parent's word, a minted claim the kernel's, and a duplicate
of a typed handle its holder's. `SysCap::claim` checks at compile time that
it mints a device's type. std's `adopt_namespace` is `unsafe` for the same
reason. A received handle still becomes a bare `OwnedHandle` to send on,
endow or map: `SharedMemory::lent` keeps `SYS_FSTAT`, which also answers
whether a region is RAM and how long, and `Paged::received` its own probe.

The supervisor refuses by name a launch whose extras, place or job are not a
connector, a process and a job, and a session's place that is no process,
where it left both to the kernel's `get_sent`.

`messages_are_whole` gains the arm the record's exit asks for: eight handles
of eight kinds arrive each beside its kind, and a pipe end taken as a
process is refused by name while the receiver lives and takes the next as a
pipe.

Closes `issues/a-received-handle-has-no-knowable-type.md`.

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

`PciDev`, `AcpiDev`, `FramebufferDev` and `UartDev` declared `KINDS =
[Device]`, so `ReceivedHandle::typed` made any device claim one of them, and
a claim of another class then ended the receiver at its first class call.
A claim's kind does not say its class.

`FromHandle` keeps only the unsafe constructor. Its Safety text is now what
the kernel holds a type's calls to: the object takes every call of the type,
which for a class type means a claim of that class. `KINDS` moves to
`OfKind`, which only types that any object of their kinds serves implement,
and `typed` requires it. The four class types implement `FromHandle` and
`ClaimType`, a marker for what `SysCap` mints. It replaces the const assert
that a minted type is a device's, so `SysCap::claim` and the guest tests'
`function::claim` take the class from the call that names it. A received
claim can become only a `Device`.

From the review's suggestions:
- `ReceivedHandle::kind`, which had no caller, is gone.
- `ask_export` types netstack's exported end before it hands it on.
- Both `adopt_exported`s take a `Connection` and are safe. The one `unsafe` is
  where `fd` takes an endowed export, whose kind is the parent's word.

`ObjectKind`'s definition now says that a kind which goes leaves its word
unused for good. A new record names the connection calls that still take any
`AsHandle`.

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

Two SAFETY comments still argued that a claim's kind decides its class type.
That is the reasoning round 1 refuted. They now give the class instead.
- The supervisor's `translated` makes a `PciDev` of a claim minted for its
  `function`, a PCI function.
- `SysCap::mint` makes `T` of a claim of the class the request names.

`SysCap::mint`'s comment, and `ClaimType`'s doc, now also say that pairing a
class type with the class a call claims is the caller's to keep: the bound
does not hold it. Comments only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le the benches are down

The benches are down, so metal is no longer a landing condition. What only
metal shows of this change is recorded as owed in
`issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md`:
the T14's `testcases`, `testcases-watchdog`, `shared-debug` and
`proctreecase` boots of main. They carry the shared-boot members
`messages_are_whole`, `mutual_kill`, `moved_rights`, `served_file_offset` and
`process_tree`, which no guest row runs. A record edit only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu force-pushed the handles3-received-kind branch from 5773d0d to 4512695 Compare November 11, 2026 22:02
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Rebase review of #1239 at 451269586 (onto main 06fcb81ea)

Architecture

The design is unchanged from the LAND at 15fa1e54d. The rebase moves undelivered onto #1246's table-lock lookup and changes nothing else.

The resolution

Evidence (mine, at 451269586, in a fresh target dir)

  • cargo test -p kernel --lib --features sched-check: exit 0, 461 passed.
  • The 12 kernel shapes of build/clippy.rs (both --lib --tests shapes, one with $KERNEL_CONTROLS, plus six x86-64 and four AArch64 bare-target shapes): all exit 0, no warnings.
  • The SDK, which both main and A receive reports what each handle names, and a typed handle is made of a received one only where the kernel said so #1239 touched:
    • cargo clippy -p toyos --all-targets and cargo clippy -p toyos --lib --features fd-table, warnings denied: exit 0.
    • cargo test -p toyos: exit 0.
  • Guest 43108314485 is still queued, so I have not read it. The system crates compile only for ToyOS, so only that run's image build shows they compile at this head.

Findings

None.

LAND

@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: landing under the benches-down rule. Rebase review LAND at 4512695; guest run 43108314485 at that head: success (five rows). Owed on metal recorded; the PR's own shared-boot members will also run on the shared-boot guest row (#1271) on main.

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