diff --git a/docs/src/architecture/authority.md b/docs/src/architecture/authority.md index 12a9273e2ab..7a2bbb8a884 100644 --- a/docs/src/architecture/authority.md +++ b/docs/src/architecture/authority.md @@ -49,6 +49,11 @@ - A connection carries bounded, whole messages, each holding bytes and handles together, so a message and the authority it passes arrive atomically. +- A received handle arrives with the kind of object it names, as the kernel + read it when it delivered the handle. What the sender's protocol says the + handle is remains the sender's word: a receiver takes it as a kind only where + the kernel reported that kind, and refuses any other, including a kind it + does not know. - Notifications coalesce, and polling for readiness does not consume one; signalling and consuming each need their own right. - Pipes remain for real byte streams; bulk data stays in shared memory. diff --git a/issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md b/issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md new file mode 100644 index 00000000000..dd304927fc9 --- /dev/null +++ b/issues/a-connection-call-on-a-bare-handle-of-another-kind-ends-its-caller.md @@ -0,0 +1,30 @@ +--- +status: open +kind: finding +opened: 2026-11-11 +--- + +# A connection call on a bare handle of another kind ends its caller + +The SDK's connection calls take any `AsHandle`: `FrameRx::pump` and `recv`, +and `ipc::send`, `signal` and their `try_` forms (`sdk/toyos/src/ipc.rs`). The +message syscalls resolve that handle with `HandleTable::get`, which ends the +caller for a handle of another kind. So a received handle that a process keeps +bare (`OwnedHandle::from(received)`) and then uses as a connection can end it, +which `ReceivedHandle::typed` exists to prevent. No receive site does this +today: every connection a peer sends is made a `Connection` through `typed` +before any connection call. Found in review of #1239, by reading. + +**Exit**: the SDK's connection calls take a `Connection` rather than any +`AsHandle`, so a bare handle cannot reach a message syscall. + +- **Owed on metal:** the receive that reports each handle's kind (#1239), + end to end. Its guest binaries are shared-boot members, which no guest row + runs. + - Rows: the `testcases`, `testcases-watchdog` and `shared-debug` boots of + main, whole, and the `proctreecase` boot (`process_tree`). + - 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. diff --git a/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md b/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md index 1e05f0901fc..be28dada464 100644 --- a/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md +++ b/issues/a-driver-that-lends-its-device-a-handle-a-peer-sent-is-ended-when-it-is-no-region.md @@ -23,8 +23,7 @@ soundserver sent with its acceptance, unchecked (`offered` and pipe end or a connection there ends hda rather than being refused. A disk driver is not reached, because `Region::lent` refuses what `SYS_FSTAT` does not answer as a region before the session lends it (`Sessions::open`, -`lib/blockserve/src/sessions.rs`). A region's kind is one `SYS_FSTAT` settles, -so this is not `issues/a-received-handle-has-no-knowable-type.md`'s case. +`lib/blockserve/src/sessions.rs`). The kernel owns the fix rather than each lender: what a holder lends is a peer's by `lend`'s contract, and the kernel already answers a peer's handle of diff --git a/issues/a-received-handle-has-no-knowable-type.md b/issues/a-received-handle-has-no-knowable-type.md deleted file mode 100644 index 93d117e91d1..00000000000 --- a/issues/a-received-handle-has-no-knowable-type.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -status: open -kind: defect -severity: [security] -opened: 2026-08-10 ---- - -# A process cannot ask what most handles it received are, and using one wrongly is fatal - -`SYS_MESSAGE_RECV` installs a message's handles in the receiver's table and -answers only *how many*. `SYS_FSTAT` answers what any handle is, needing no right, and -settles two kinds: only a region of memory the kernel allocated answers -`SharedMemory`, which is how `SharedMemory::lent` refuses every other handle -before it maps one (`sdk/toyos/src/shm.rs`), and only a `Connection` answers -`Socket`. It settles no other. A `Process`, a `Namespace`, a `Connector`, an -`Inbox` and a `SysCap` all answer `Unknown`, and an `Acceptor` answers `Pipe`, -as a pipe end does (`fstat` in `kernel/src/object/ops.rs`). - -Every typed use of a handle resolves through `HandleTable::get::`, and -`HandleError::WrongType` ends the caller with a handle fault everywhere but -`HandleTable::get_sent` (`kernel/src/object/handle.rs`). That is right for a -handle the process made or was endowed, and wrong for one a peer sent: a -correct program cannot know. - -So: **any process that receives one of those kinds over a connection and then -uses it typed can be ended by whoever sent it.** The sender needs nothing but -`Rights::TRANSFER`, which every handle it made carries. - -## Where it is reachable today - -- `toyos::launch` answers a started launch with the `Process` handle the - supervisor sent (`sdk/toyos/src/launch.rs`), and its caller uses it typed. - -A generic use is not reached: a read or a write a handle cannot do answers -`PermissionDenied`, and a watch `NotSupported`, never `WrongType`, which is how -netstack's piped ends are refused (`system/netstack/src/pipes.rs`). The -supervisor hands what a launch's client sent to `SYS_SPAWN` and -`SYS_NAMESPACE_BUILD`, which answer a handle of the wrong kind -`InvalidArgument`. - -Nothing in the tree is hostile today, so nothing fails. The property the -architecture claims — that a process cannot be harmed by what it was not given — -does not hold across a transfer. - -**Exit**: a process that receives a handle of one of those kinds refuses one of -another kind by name before it uses it typed, and a guest test that sends one -in its place sees it refused while the receiver lives. - -## Three ways out, and none is free - -1. **Report the kind.** `SYS_MESSAGE_RECV` writes `(RawHandle, kind)` pairs - instead of bare handles. One ABI shape change, in-tree only: no fork calls - `message_recv` or the SDK's receive. The receiver then refuses by name and - the fail-fast policy is untouched. -2. **A syscall** answering the kind of a handle. Cheaper to write and - worse: it is a second place to ask, and it makes "what is this" a round trip - rather than part of the answer that produced it. -3. **`WrongType` stops being fatal.** Rejected: it is fatal for a reason, and - three quarters of its call sites really are a bug in the caller. - -(1) is the recommendation. It is not this review's to take — it is an ABI -change, and a new syscall is the owner's to approve. - -## Ruled not a merge blocker, 2026-08-10 - -Judged while clearing PR #22's blockers, with the reasoning written down because -the next reader will ask why a class this wide was left open. - -- **The fix is an ABI shape change and the ABI was the owner's to approve.** It - widens the receive's answer from `n` to `n` pairs (`SYS_HANDLE_RECV` then, - `SYS_MESSAGE_RECV` since), which is a syscall the owner approved changing - shape after the fact. - -So it stays open, and what it is waiting on is a decision rather than an -instrument. - -## Taken up as (1), 2026-11-11 - -Staged as its own change, after the one that gave both moves a rights word: -`SYS_MESSAGE_RECV` writes `(RawHandle, ObjectKind)`, read in the hold that -installs the handle, and the SDK makes a typed wrapper of a received handle -only through a checked conversion. Its review set three constraints: - -- `ObjectKind` becomes a contract and not a census detail: its values are - never renumbered, and the SDK refuses a kind it does not know. -- It does not replace `fstat` for a region: `SharedMemory::lent` also needs - whether it is RAM, and its size. -- The pair carries the kind alone. A missing right already answers - `PermissionDenied` and ends nobody, so reporting rights has no caller. - -**Its reach is wider than `toyos::launch`.** Both event sides are typed -(`sys_event_signal`, `sys_event_take`) and answer `Unknown`, and a client -takes bells from its server and uses them typed in `sdk/toyos/src/audio.rs`, -`sdk/toyos/src/net/stream.rs`, `sdk/toyos/src/net/socket.rs` and -`lib/link/src/driver.rs`. Read from the code, not exercised. - -`launcher_refusals` is deleted: `4c191469f` took it out. diff --git a/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md b/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md index 0d4ed9f053f..93c552b7e29 100644 --- a/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md +++ b/issues/the-kernel-is-small-interrupts-post-and-threads-wait.md @@ -411,9 +411,8 @@ By mechanisms eliminated: 3. What the loader named, independent of the others: `SYS_BOOT_SOURCES`, the inventory's partition records, the device-path parser. **Done** (#1210). -4. The TTY mark, with or after 2: `SYS_MARK_TTY`. It meets - `issues/a-received-handle-has-no-knowable-type.md`, which wants - `SYS_FSTAT` to answer more, so the two are designed together. +4. The TTY mark, with or after 2: `SYS_MARK_TTY`. A receive reports each + handle's kind and a TTY is no kind, so `SYS_FSTAT` still answers it. 5. The main thread's stack, after #1200: `SYS_STACK_INFO` and the kernel's two stack constants. diff --git a/kernel/src/message.rs b/kernel/src/message.rs index 406a53ad28b..a3eb6f47dc2 100644 --- a/kernel/src/message.rs +++ b/kernel/src/message.rs @@ -23,7 +23,7 @@ use alloc::vec::Vec; use kernel::message::{Head, Queue, Untaken}; use kernel::pipe::{End, Holders, Released}; use kernel::sched::task::WaitClass; -use toyos_abi::handle::RawHandle; +use toyos_abi::handle::ReceivedEntry; use toyos_abi::syscall::{MAX_MESSAGE_LEN, MAX_QUEUED_HANDLES, MAX_QUEUED_MESSAGES, MAX_TRANSFER_HANDLES}; use crate::frame_bytes::{self, Bounce, Fill, FrameBytes, Spares}; @@ -162,7 +162,8 @@ pub enum Unsent { /// lock let go. Spares past its batch stay listed for the next take. pub struct Received { pub head: Head, - pub installed: [RawHandle; MAX_TRANSFER_HANDLES], + /// Each handle and its object's kind, read as it was installed. + pub installed: [ReceivedEntry; MAX_TRANSFER_HANDLES], _spares: Spares, boost: bool, } @@ -293,13 +294,14 @@ impl QueueRef { if !table.has_room(head.handles) { return Err(Unclaimed::Room(head.handles)); } - let mut installed = [RawHandle(0); MAX_TRANSFER_HANDLES]; + let mut installed = [ReceivedEntry::NONE; MAX_TRANSFER_HANDLES]; let Some(Ok((message, handles))) = state.queue.pop(head.len, head.handles, |_| true) else { unreachable!("message: a front its reader holds left the queue or was refused"); }; assert_eq!(message, head, "message: the front moved under its lock"); for (slot, entry) in installed.iter_mut().zip(handles) { - *slot = table.install(entry).expect("the table had room"); + let kind = entry.object().object_kind() as u32; + *slot = ReceivedEntry { handle: table.install(entry).expect("the table had room"), kind }; } let spares = state.bytes.take(head.len, into); let boost = core::mem::take(&mut state.rt_boost_pending); diff --git a/kernel/src/object/mod.rs b/kernel/src/object/mod.rs index de99ee0807b..39e7e74f8ca 100644 --- a/kernel/src/object/mod.rs +++ b/kernel/src/object/mod.rs @@ -184,6 +184,13 @@ macro_rules! kobject { } } + /// The word a receive reports this object as. + pub fn object_kind(&self) -> toyos_abi::handle::ObjectKind { + match self { + $(Self::$variant(_) => toyos_abi::handle::ObjectKind::$variant,)+ + } + } + fn run_zero_handles(&self) { match self { $(Self::$variant(o) => o.on_zero_handles(),)+ diff --git a/kernel/src/syscall/dispatch.rs b/kernel/src/syscall/dispatch.rs index c1cb6eaef41..45ea8898b27 100644 --- a/kernel/src/syscall/dispatch.rs +++ b/kernel/src/syscall/dispatch.rs @@ -18,7 +18,7 @@ use toyos_abi::handle::{RawHandle, Rights}; use toyos_abi::syscall::debug_action as DA; use toyos_abi::syscall::*; -use super::{HANDLE_LEN, TRANSFER_ENTRY_LEN}; +use super::{RECEIVED_ENTRY_LEN, TRANSFER_ENTRY_LEN}; #[cfg(feature = "test-actuators")] use super::debug::{canary, debug_heap_alloc, ring0_timer_in_syscall, FATAL_HALT_NONCE, LOCK_ACROSS_SWITCH}; use super::device::{ @@ -612,7 +612,7 @@ fn incoming<'a>(ctx: &SyscallContext<'a>, args: &MessageRecv) -> Result u64 { @@ -627,10 +627,13 @@ fn recv(conn_h: RawHandle, into: &mut Incoming<'_>, nonblock: bool) -> u64 { } /// A message is taken, its bytes in the reader's buffers: answer its -/// handles' numbers and wake the sender, for its room. -fn delivered(rx: &QueueRef, into: &mut Incoming<'_>, head: kernel::message::Head, installed: &[RawHandle]) -> u64 { +/// handles' numbers and kinds and wake the sender, for its room. +fn delivered(rx: &QueueRef, into: &mut Incoming<'_>, head: kernel::message::Head, installed: &[ReceivedEntry]) -> u64 { let installed = &installed[..head.handles]; - let told = installed.iter().enumerate().try_for_each(|(i, h)| into.handles.write_at(i * HANDLE_LEN, &h.0.to_ne_bytes())); + let told = installed + .iter() + .enumerate() + .try_for_each(|(i, entry)| into.handles.write_at(i * RECEIVED_ENTRY_LEN, toyos_abi::usersafe::bytes(entry))); if let Err(faulted) = told { return undelivered(rx, installed, faulted); } @@ -640,9 +643,9 @@ fn delivered(rx: &QueueRef, into: &mut Incoming<'_>, head: kernel::message::Head /// A message taken whose reader's buffers faulted: its bytes are lost, and /// its handles, installed at numbers the reader was never told, are closed. -fn undelivered(rx: &QueueRef, installed: &[RawHandle], faulted: crate::user_ptr::Faulted) -> u64 { +fn undelivered(rx: &QueueRef, installed: &[ReceivedEntry], faulted: crate::user_ptr::Faulted) -> u64 { process::with_handles(|handles| { - for &h in installed { + for ReceivedEntry { handle: h, .. } in installed.iter().copied() { // A sibling may have closed the number since, or reused it for a // handle of its own, which this then closes: the race is the // misbehaving process's own, and stays inside it. diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 0d9c5a81275..49a62176653 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -15,13 +15,13 @@ mod paged; mod proc; pub(crate) mod vm; -use toyos_abi::handle::RawHandle; +use toyos_abi::handle::ReceivedEntry; use toyos_abi::syscall::SyscallError; use crate::process; -/// Byte width of one [`RawHandle`] on the wire. -const HANDLE_LEN: usize = core::mem::size_of::(); +/// Byte width of one handle a receive answers. +const RECEIVED_ENTRY_LEN: usize = core::mem::size_of::(); /// Byte width of one handle a send moves. const TRANSFER_ENTRY_LEN: usize = core::mem::size_of::(); diff --git a/lib/blockserve/src/link.rs b/lib/blockserve/src/link.rs index e01ccb4a80a..3199ef99a9d 100644 --- a/lib/blockserve/src/link.rs +++ b/lib/blockserve/src/link.rs @@ -156,22 +156,21 @@ impl Link { RxStep::Malformed => "a frame this protocol does not make", RxStep::Frame { msg_type: disk::MSG_SESSION, payload_len, .. } => { let session = disk::Session::decode(self.rx.payload(payload_len)); - let handles = self.rx.take_handles::<2>(); + let handles = self.rx.take_handles::<2>().map(|[conn, region]| (conn.typed::(), region)); match (session, handles) { - (Some(session), Some([conn, region])) => { - let conn = Connection::received(conn); - handed.push(Handed::Session { session, conn, region }); + (Some(session), Some((Ok(conn), region))) => { + handed.push(Handed::Session { session, conn, region: region.into() }); continue; } + (_, Some((Err(_), _))) => "a session whose connection is no connection", (_, Some(_)) => "a session frame this protocol does not make", (_, None) => "a session without its two handles", } } RxStep::Frame { msg_type: msg_type @ (disk::MSG_DESCRIBE | disk::MSG_DESIGNATE), payload_len, .. } => { let payload = self.rx.payload(payload_len).to_vec(); - match self.rx.take_handles::<1>() { - Some([conn]) => { - let conn = Connection::received(conn); + match self.rx.take_handles::<1>().map(|[conn]| conn.typed::()) { + Some(Ok(conn)) => { let asked = match msg_type { disk::MSG_DESCRIBE => disk::disk(&payload).map(|disk| Handed::Describe { disk, conn }), _ => disk::Designation::decode(&payload).map(|order| Handed::Designate { order, conn }), @@ -184,6 +183,7 @@ impl Link { None => "a description or designation frame this protocol does not make", } } + Some(Err(_)) => "a description or designation whose connection is no connection", None => "a description or designation without the client's connection", } } diff --git a/lib/blockserve/src/region.rs b/lib/blockserve/src/region.rs index 67d7f89fbf3..7897a3c110e 100644 --- a/lib/blockserve/src/region.rs +++ b/lib/blockserve/src/region.rs @@ -27,7 +27,7 @@ impl Region { /// A region a client sent, refused unless it is memory the kernel /// allocated and exactly a session long. - pub fn lent(handle: toyos::OwnedHandle) -> Result { + pub fn lent(handle: impl Into) -> Result { Ok(Self { memory: SharedMemory::lent(handle, SESSION_BYTES)? }) } diff --git a/lib/link/src/driver.rs b/lib/link/src/driver.rs index 979e4704d51..353c9d8384f 100644 --- a/lib/link/src/driver.rs +++ b/lib/link/src/driver.rs @@ -263,7 +263,10 @@ impl<'a> Session<'a> { RxStep::Eof => Some(Ended::HungUp("its process ended before it answered")), RxStep::Frame { msg_type: wire::MSG_OPENED, payload_len: 0, handles: wire::OPENED_HANDLES } => { let [ring, rung] = self.rx.take_handles::<{ wire::OPENED_HANDLES }>().expect("counted above"); - self.bells = Some((Waker::from(ring), Bell::from(rung))); + let (Ok(ring), Ok(rung)) = (ring.typed::(), rung.typed::()) else { + panic!("{}: the stack opened this link with handles that are not its bells", self.label); + }; + self.bells = Some((ring, rung)); // Its work is done: the bells carry the link from here. self.conn = None; say!("{}: the link is open", self.label); diff --git a/lib/link/src/region.rs b/lib/link/src/region.rs index 6aa5c818e0e..8425afbc288 100644 --- a/lib/link/src/region.rs +++ b/lib/link/src/region.rs @@ -26,7 +26,7 @@ impl Region { /// A region a driver sent, refused unless it is memory the kernel /// allocated and exactly a region long. - pub fn lent(handle: toyos::OwnedHandle) -> Result { + pub fn lent(handle: impl Into) -> Result { Ok(Self { memory: SharedMemory::lent(handle, REGION_BYTES)? }) } diff --git a/lib/logkeeper-api/src/lib.rs b/lib/logkeeper-api/src/lib.rs index 044753cc52d..b13cbc963e9 100644 --- a/lib/logkeeper-api/src/lib.rs +++ b/lib/logkeeper-api/src/lib.rs @@ -32,7 +32,7 @@ fn answer(conn: &Connection) -> Result { let boot_so_far: u64 = toyos::ipc::decode_payload(rx.payload(frame.payload_len)) .map_err(|e| format!("logkeeper's answer is not one `u64`: {e:?}"))?; let [pipe] = rx.take_handles::<1>().ok_or("logkeeper's answer carried no pipe")?; - let pipe = Pipe::from(pipe); + let pipe = pipe.typed::().map_err(|why| format!("logkeeper's answer carried no pipe: {why}"))?; Ok(Served { pipe, boot_so_far }) } diff --git a/lib/session/src/lib.rs b/lib/session/src/lib.rs index 6bb4684914d..500af22e567 100644 --- a/lib/session/src/lib.rs +++ b/lib/session/src/lib.rs @@ -299,9 +299,13 @@ pub fn open( return Err(format!("the supervisor refused a session: {why}")); } let [launcher, controls] = rx.take_handles::<2>().ok_or("the session came without its handles")?; + let namespace = |handle: toyos::ipc::ReceivedHandle| { + handle.typed::().map_err(|why| format!("the session's handles are not namespaces: {why}")) + }; + let (launcher, controls) = (namespace(launcher)?, namespace(controls)?); asks += 1; - match toyos::namespace::Namespace::received(controls).open(CONTROL) { - Ok(control) => return Ok((toyos::namespace::Namespace::received(launcher), control)), + match controls.open(CONTROL) { + Ok(control) => return Ok((launcher, control)), Err(toyos_abi::syscall::SyscallError::Gone) if began.elapsed() < ENDED_UNREAD => {} Err(toyos_abi::syscall::SyscallError::Gone) => { return Err(format!( diff --git a/sdk/abi/src/handle.rs b/sdk/abi/src/handle.rs index 835c3710bfe..7e6776a1481 100644 --- a/sdk/abi/src/handle.rs +++ b/sdk/abi/src/handle.rs @@ -68,8 +68,13 @@ impl RawHandle { /// any table issues. pub const HANDLE_INVALID: RawHandle = RawHandle(u32::MAX); -/// Every kind of kernel object a handle can name, as the word +/// Every kind of kernel object a handle can name: the word a receive reports +/// beside each handle it installs ([`ReceivedEntry`]) and /// [`CENSUS_KIND`](crate::syscall::debug_action::CENSUS_KIND) takes. +/// +/// **A kind's word is ABI: it is never renumbered and never given to another +/// kind.** A new kind takes the next word, a kind that goes leaves its word +/// unused for good, and a receiver refuses a word it does not know. #[repr(u32)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum ObjectKind { @@ -372,6 +377,31 @@ impl TransferEntry { const _: () = assert!(Rights::from_bits(TransferEntry::UNCHANGED).is_none()); +crate::user_safe! { + /// One handle a message carried, as [`message_recv`] answers it: where + /// it is in the receiver's table and what it names, read in the hold + /// that installed it. + /// + /// [`message_recv`]: crate::syscall::message_recv + #[derive(Clone, Copy, PartialEq, Eq, Debug)] + pub struct ReceivedEntry { + pub handle: RawHandle, + /// An [`ObjectKind`] word. + pub kind: u32, + } +} + +impl ReceivedEntry { + /// No handle, of no kind: what a receive's buffer holds before the kernel + /// writes it. + pub const NONE: Self = Self { handle: HANDLE_INVALID, kind: u32::MAX }; + + /// What the handle names; `None` for a word this ABI does not know. + pub const fn kind(self) -> Option { + ObjectKind::from_word(self.kind) + } +} + /// A rights word with a bit no right uses. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct NotRights; diff --git a/sdk/abi/src/syscall.rs b/sdk/abi/src/syscall.rs index b08621aba98..7a4c1730887 100644 --- a/sdk/abi/src/syscall.rs +++ b/sdk/abi/src/syscall.rs @@ -610,7 +610,7 @@ pub const MAX_LABELS_LEN: usize = 4096; pub const MAX_SPAWN_LABELS_LEN: usize = MAX_LABELS_LEN - SELF_LABEL.len() - QUIT_LABEL.len(); use crate::end::End; -use crate::handle::{Rights, TransferEntry}; +use crate::handle::{ReceivedEntry, Rights, TransferEntry}; use crate::pci::{DmaGrant, DmaMapping}; use crate::{Pid, RawHandle, Tid, HANDLE_INVALID}; @@ -2151,6 +2151,7 @@ crate::user_safe! { pub head_len: u64, pub bytes: u64, pub len: u64, + /// `[ReceivedEntry]`. pub handles: u64, pub cap: u64, /// [`MESSAGE_NONBLOCK`] or none; any other bit is `InvalidArgument`. @@ -2159,7 +2160,7 @@ crate::user_safe! { } impl MessageRecv { - fn new(head: &mut [u8], bytes: &mut [u8], handles: &mut [RawHandle], nonblock: bool) -> Self { + fn new(head: &mut [u8], bytes: &mut [u8], handles: &mut [ReceivedEntry], nonblock: bool) -> Self { Self { head: head.as_mut_ptr() as u64, head_len: head.len() as u64, @@ -2194,9 +2195,10 @@ impl Received { const _: () = assert!(Received::encode(MAX_MESSAGE_LEN, MAX_TRANSFER_HANDLES) < u64::MAX - 255); /// Take `conn`'s next message whole: its first bytes into `head`, the rest -/// into `bytes`, and its handles, each installed in this process's table, -/// into `handles`. Two places, so a protocol's own header and the payload -/// behind it each land where the caller keeps them, with no copy between. +/// into `bytes`, and its handles, each installed in this process's table and +/// beside it the kind of object it names, into `handles`. Two places, so a +/// protocol's own header and the payload behind it each land where the caller +/// keeps them, with no copy between. /// /// Waits for one unless `nonblock`, which answers `WouldBlock`. Once the peer /// has gone and nothing is queued, `Gone`. **A message is never cut**: one @@ -2208,7 +2210,7 @@ pub fn message_recv( conn: RawHandle, head: &mut [u8], bytes: &mut [u8], - handles: &mut [RawHandle], + handles: &mut [ReceivedEntry], nonblock: bool, ) -> Result { let args = MessageRecv::new(head, bytes, handles, nonblock); diff --git a/sdk/libc/src/posix_io.rs b/sdk/libc/src/posix_io.rs index 155de077588..7a96cba49d3 100644 --- a/sdk/libc/src/posix_io.rs +++ b/sdk/libc/src/posix_io.rs @@ -119,7 +119,7 @@ pub(crate) fn open_handle(path: &[u8], flags: i32, _mode: u32) -> Result<(RawHan return syscall::null(null_rights(how.read, how.write)).map(|h| (h, toyos::fd::Kind::Handle)).map_err(crate::errcode::of); } let fd = files::open(At::Cwd, path, how).map_err(crate::errcode::of_files)?; - let exported = files::export(fd).map(|h| (h.into_raw(), toyos::fd::Kind::File)).map_err(crate::errcode::of_files); + let exported = files::export(fd).map(|h| (h.into_owned().into_raw(), toyos::fd::Kind::File)).map_err(crate::errcode::of_files); // SAFETY: this function's descriptor, after the server exported its description. let _ = unsafe { toyos::fd::close(fd) }; exported diff --git a/sdk/std/os/fs.rs b/sdk/std/os/fs.rs index 6bb5438346a..b930b0e66a6 100644 --- a/sdk/std/os/fs.rs +++ b/sdk/std/os/fs.rs @@ -70,8 +70,14 @@ pub fn symlink, Q: AsRef>(original: P, link: Q) -> io::Resu /// For the one process no parent endows a namespace: the supervisor builds the machine's /// directory capabilities itself. Refused, and the handle closed, when this /// process already resolves through one. +/// +/// # Safety +/// `namespace` names a namespace: the kernel ends a process that opens a name +/// through a handle to anything else. #[stable(feature = "toyos_ext", since = "1.0.0")] -pub fn adopt_namespace(namespace: crate::os::toyos::io::OwnedHandle) -> io::Result<()> { - toyos::endow::adopt_namespace(toyos::endow::FromHandle::from_handle(namespace.into_inner())) +pub unsafe fn adopt_namespace(namespace: crate::os::toyos::io::OwnedHandle) -> io::Result<()> { + // SAFETY: the caller's. + let namespace = unsafe { toyos::endow::FromHandle::from_handle(namespace.into_inner()) }; + toyos::endow::adopt_namespace(namespace) .map_err(|_| io::const_error!(io::ErrorKind::AlreadyExists, "this process already has a namespace")) } diff --git a/sdk/toyos/src/audio.rs b/sdk/toyos/src/audio.rs index e902ada00ae..47ba2f7edfc 100644 --- a/sdk/toyos/src/audio.rs +++ b/sdk/toyos/src/audio.rs @@ -226,6 +226,8 @@ pub enum AudioError { /// with it. Handles travel in the frame that names them, so a short /// batch is a protocol violation and never something to wait for. MissingHandles, + /// The signal soundserver sent is no bell. + Signal(ipc::WrongKind), /// The slot ring soundserver sent is refused ([`SharedMemory::lent`]). Lent(crate::shm::Lent), Ipc(IpcError), @@ -273,7 +275,7 @@ impl AudioStream { }; let [shm, signal] = rx.take_handles::().ok_or(AudioError::MissingHandles)?; - let signal = crate::wake::Bell::from(signal); + let signal = signal.typed::().map_err(AudioError::Signal)?; let slot_count = resp.slot_count as u32; let shm_size = AudioSlotHeader::SIZE + slot_count as usize * resp.client_period_bytes as usize; diff --git a/sdk/toyos/src/endow.rs b/sdk/toyos/src/endow.rs index 9adb0a82aa0..e2da55d6793 100644 --- a/sdk/toyos/src/endow.rs +++ b/sdk/toyos/src/endow.rs @@ -14,7 +14,7 @@ use core::cell::UnsafeCell; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicU32, AtomicU8, Ordering}; -use toyos_abi::handle::HANDLE_INVALID; +use toyos_abi::handle::{ObjectKind, HANDLE_INVALID}; use toyos_abi::syscall::{ self, DeviceRequest, DeviceType, SyscallError, MAX_ENDOWMENTS, MAX_LABELS_LEN, }; @@ -43,42 +43,101 @@ pub enum EndowError { Refused(SyscallError), } -/// A typed wrapper over a handle it owns, as whoever gave the handle says it -/// is: the kernel refuses each call on a handle of another type, so the type -/// is trusted only as far as the giver's protocol says. +/// A typed wrapper over a handle it owns. pub trait FromHandle { - fn from_handle(handle: OwnedHandle) -> Self; + /// # Safety + /// Every call of this type is one the object `handle` names can take: + /// the kernel ends a process that makes one on any other. For an + /// [`OfKind`] type, that is an object of one of its kinds. For a claim's + /// class type ([`ClaimType`]), it is a claim on a device of that class. + /// Of a handle a peer sent, only the kernel says which kind it is: + /// [`ReceivedHandle::typed`](crate::ipc::ReceivedHandle::typed). + unsafe fn from_handle(handle: OwnedHandle) -> Self; } +/// A typed wrapper whose every call any object of one of [`Self::KINDS`] +/// takes, so the kernel's word on a received handle's kind is enough to make +/// one ([`ReceivedHandle::typed`](crate::ipc::ReceivedHandle::typed)). A +/// claim's class types are not: a claim's kind does not say its class. +pub trait OfKind: FromHandle { + const KINDS: &'static [ObjectKind]; +} + +/// A type the system capability mints a device claim as: [`Device`], or a +/// class's type over it. The SDK does not pair the type with the class a call +/// claims: a caller that names a class type keeps it to the class it asks for. +pub trait ClaimType: FromHandle {} + +/// Whether `kinds` names `kind`. +pub(crate) const fn names(kinds: &[ObjectKind], kind: ObjectKind) -> bool { + let mut i = 0; + while i < kinds.len() { + if kinds[i] as u32 == kind as u32 { + return true; + } + i += 1; + } + false +} + +/// [`FromHandle`] and [`OfKind`] for each type, made by its constructor. macro_rules! from_handle { - ($($ty:ty => $make:expr),+ $(,)?) => { - $(impl FromHandle for $ty { - fn from_handle(handle: OwnedHandle) -> Self { - let make: fn(OwnedHandle) -> Self = $make; + ($($ty:ty: $($kind:ident)|+ => $make:expr),+ $(,)?) => { + $(impl $crate::endow::FromHandle for $ty { + unsafe fn from_handle(handle: $crate::OwnedHandle) -> Self { + let make: fn($crate::OwnedHandle) -> Self = $make; make(handle) } + } + + impl $crate::endow::OfKind for $ty { + const KINDS: &'static [toyos_abi::handle::ObjectKind] = &[$(toyos_abi::handle::ObjectKind::$kind),+]; })+ }; } +pub(crate) use from_handle; from_handle! { - OwnedHandle => |h| h, - Acceptor => Acceptor, - Connector => Connector, - Namespace => Namespace, - SysCap => SysCap, - Job => Job, - Device => Device, - crate::Pipe => crate::Pipe, - crate::wake::Waker => crate::wake::Waker::from, - crate::wake::Bell => crate::wake::Bell::from, - crate::ipc::Connection => crate::ipc::Connection, - crate::FramebufferDev => |h| crate::FramebufferDev(Device(h)), - crate::PciDev => |h| crate::PciDev(Device(h)), - crate::AcpiDev => |h| crate::AcpiDev(Device(h)), - crate::UartDev => |h| crate::UartDev(Device(h)), + Acceptor: Acceptor => Acceptor, + Connector: Connector => Connector, + Namespace: Namespace => Namespace, + SysCap: SysCap => SysCap, + Job: Job => Job, + Process: Process => Process, + Device: Device => Device, + crate::Pipe: PipeRead | PipeWrite => crate::Pipe, + crate::ipc::Connection: Connection => crate::ipc::Connection, +} + +/// Any handle, untyped. +impl FromHandle for OwnedHandle { + unsafe fn from_handle(handle: OwnedHandle) -> Self { + handle + } } +impl OfKind for OwnedHandle { + const KINDS: &'static [ObjectKind] = &ObjectKind::ALL; +} + +impl ClaimType for Device {} + +/// The class types over a claim: [`FromHandle`] and [`ClaimType`], never +/// [`OfKind`]. +macro_rules! class_of_claim { + ($($ty:ty),+ $(,)?) => { + $(impl FromHandle for $ty { + unsafe fn from_handle(handle: OwnedHandle) -> Self { + Self(Device(handle)) + } + } + + impl ClaimType for $ty {})+ + }; +} + +class_of_claim!(crate::FramebufferDev, crate::PciDev, crate::AcpiDev, crate::UartDev); + /// This process's endowment table, parsed once. pub struct Endowments { labels: [u8; MAX_LABELS_LEN], @@ -107,7 +166,10 @@ impl Endowments { } // The swap is what makes this the only caller to see the handle, and // the kernel put it in this process's table at spawn. - Some(T::from_handle(OwnedHandle(RawHandle(raw)))) + let handle = OwnedHandle(RawHandle(raw)); + // SAFETY: what this process was endowed is its parent's to say, which + // made it and can end it as it likes. + Some(unsafe { T::from_handle(handle) }) } /// Whether `label` names something this process still holds. diff --git a/sdk/toyos/src/fd.rs b/sdk/toyos/src/fd.rs index cd5e053ddb6..d0494f66059 100644 --- a/sdk/toyos/src/fd.rs +++ b/sdk/toyos/src/fd.rs @@ -471,13 +471,20 @@ mod owner { } continue; } + /// A descriptor endowed as a file's or a socket's export, which + /// is a connection's end. + fn exported(handle: OwnedHandle) -> crate::ipc::Connection { + // SAFETY: what this process was endowed is its parent's to say, + // as every endowment's kind is. + unsafe { ::from_handle(handle) } + } let object = match kind { Kind::Handle => Object::handle(handle), - Kind::File => match crate::files::adopt_exported(handle) { + Kind::File => match crate::files::adopt_exported(exported(handle)) { Ok(object) => object, Err(_) => { refused += 1; continue; } }, - Kind::Exported => match crate::net::adopt_exported(handle) { + Kind::Exported => match crate::net::adopt_exported(exported(handle)) { Some(object) => object, None => { refused += 1; diff --git a/sdk/toyos/src/files.rs b/sdk/toyos/src/files.rs index 3e1d6c5139e..c0bdd0637e7 100644 --- a/sdk/toyos/src/files.rs +++ b/sdk/toyos/src/files.rs @@ -314,8 +314,9 @@ pub fn stream(fd: Fd) -> Result { /// Export a served description for another process, sharing its access mode /// and offset. The returned connection becomes an `fd::file` endowment. -pub fn export(fd: Fd) -> Result { - fd::with(fd, export_held).map_err(Error::Fd)? +pub fn export(fd: Fd) -> Result { + // The owner made it of the file server's answer through `typed`. + fd::with(fd, export_held).map_err(Error::Fd)?.map(crate::ipc::Connection) } /// The object remains held across the owner ABI call, including if its @@ -327,8 +328,8 @@ pub(crate) fn export_held(object: fd::Lent<'_>) -> Result { } #[cfg(all(feature = "fd-table", not(test)))] -pub(crate) fn adopt_exported(handle: OwnedHandle) -> Result { - owner::adopt_exported(handle) +pub(crate) fn adopt_exported(connection: crate::ipc::Connection) -> Result { + owner::adopt_exported(connection) } pub fn mkdir(at: At, path: &[u8]) -> Result<(), Error> { diff --git a/sdk/toyos/src/files/owner.rs b/sdk/toyos/src/files/owner.rs index 0d75e62f571..63f92746ca7 100644 --- a/sdk/toyos/src/files/owner.rs +++ b/sdk/toyos/src/files/owner.rs @@ -373,8 +373,8 @@ fn object(held: Held) -> Object { /// Take up the one description exported for an endowed file descriptor. /// This constructs an object without entering the descriptor table again. -pub(super) fn adopt_exported(handle: crate::OwnedHandle) -> Result { - let mut dir = Dir::adopt(crate::ipc::Connection::received(handle)).map_err(Error::Served)?; +pub(super) fn adopt_exported(connection: crate::ipc::Connection) -> Result { + let mut dir = Dir::adopt(connection).map_err(Error::Served)?; let fid = Fid { id: 1, generation: dir.generation() }; dir.fstat(fid).map_err(Error::Served)?; Ok(object(Held::Served(Served { diff --git a/sdk/toyos/src/fs.rs b/sdk/toyos/src/fs.rs index f01f880c483..9a62c454c8a 100644 --- a/sdk/toyos/src/fs.rs +++ b/sdk/toyos/src/fs.rs @@ -794,7 +794,7 @@ impl Dir { /// The one handle a reply that answers with one carries; an error carries /// none, and any other count ends the connection. - fn one(&mut self, msg_type: u32, reply: Reply, carried: crate::ipc::Batch) -> Result { + fn one(&mut self, msg_type: u32, reply: Reply, carried: crate::ipc::Batch) -> Result { let n = carried.len(); match (answer(msg_type, reply), carried.exact::<1>()) { (Ok(_), Some([handle])) => Ok(handle), @@ -945,7 +945,7 @@ impl Dir { let paths = Paths { first: path, second: &[] }; let (reply, mut carried) = self.path_call_with(op, Request { flags, ..Request::new() }, [base, Base::Root], paths, 0)?; let fid = Fid { id: reply.value, generation: self.generation }; - let holder = carried.next(); + let holder = carried.next().map(crate::OwnedHandle::from); if !carried.is_empty() { self.close(fid); self.conn = None; @@ -1030,7 +1030,7 @@ impl Dir { } let request = Request { len: fids.len() as u64, span: (fids.len() * 8) as u64, ..Request::new() }; let (msg_type, reply, carried) = self.call_with(EXPORT, request)?; - self.one(msg_type, reply, carried).map(Connection::received) + self.one(msg_type, reply, carried)?.typed().map_err(|_| Error::Malformed) } /// What `path` names from `base`: the link itself where `follow` is @@ -1101,7 +1101,7 @@ impl Dir { let mut request = Request::new(); request.fid = self.held(fid)?; let (msg_type, reply, carried) = self.call_with(STREAM, request)?; - self.one(msg_type, reply, carried).map(Pipe) + self.one(msg_type, reply, carried)?.typed().map_err(|_| Error::Malformed) } /// The bytes of the file `fid` opened, as [`IMAGE`] answers them. @@ -1109,7 +1109,7 @@ impl Dir { let mut request = Request::new(); request.fid = self.held(fid)?; let (msg_type, reply, carried) = self.call_with(IMAGE, request)?; - self.one(msg_type, reply, carried) + self.one(msg_type, reply, carried).map(crate::OwnedHandle::from) } /// The entries of the directory `dir` opened, from `cursor` (0 at its diff --git a/sdk/toyos/src/ipc.rs b/sdk/toyos/src/ipc.rs index 59d91bd7794..db1f40a0b81 100644 --- a/sdk/toyos/src/ipc.rs +++ b/sdk/toyos/src/ipc.rs @@ -5,9 +5,10 @@ //! (`SYS_MESSAGE_RECV`): a receiver never sees part of a frame, and a //! frame's handles arrive with it and with no other. -use toyos_abi::handle::TransferEntry; +use toyos_abi::handle::{ObjectKind, ReceivedEntry, TransferEntry}; use toyos_abi::RawHandle; use toyos_abi::syscall::{self, Received, SyscallError}; +use crate::endow::OfKind; use crate::{AsHandle, BorrowedHandle, Moved, OwnedHandle}; /// A type that may cross an IPC boundary as a payload. @@ -126,7 +127,7 @@ pub enum TrySendError { /// An IPC connection. A server gets one from [`crate::port::Acceptor::accept`], /// a client from [`crate::endow::service`], and a server one end a peer -/// handed it from [`Self::received`]. +/// handed it from [`ReceivedHandle::typed`]. pub struct Connection(pub(crate) OwnedHandle); impl AsHandle for Connection { @@ -134,13 +135,6 @@ impl AsHandle for Connection { } impl Connection { - /// A connection end a peer sent, consumed. Its type is the sender's - /// word: the kernel refuses each connection call on a handle of another - /// type, so trust the sender only as far as its protocol says. - pub fn received(handle: OwnedHandle) -> Self { - Self(handle) - } - /// This end as a handle, to send it on in a frame ([`Self::send_with`]). pub fn into_owned(self) -> OwnedHandle { self.0 @@ -516,7 +510,7 @@ impl FrameRx { /// The `N` handles the last frame carried, now the caller's; `None`, with /// them closed, when it carried another number. - pub fn take_handles(&mut self) -> Option<[OwnedHandle; N]> { + pub fn take_handles(&mut self) -> Option<[ReceivedHandle; N]> { self.take_batch().exact::() } @@ -550,7 +544,7 @@ fn receive( payload: &mut [u8], nonblock: bool, ) -> Result<(Frame, Batch), Refused> { - let mut handles = [toyos_abi::HANDLE_INVALID; syscall::MAX_TRANSFER_HANDLES]; + let mut handles = [ReceivedEntry::NONE; syscall::MAX_TRANSFER_HANDLES]; let got = syscall::message_recv(conn.raw(), msg_type, payload, &mut handles, nonblock); taken(got, *msg_type, handles) } @@ -559,7 +553,7 @@ fn receive( fn taken( got: Result, msg_type: [u8; TYPE_LEN], - handles: [RawHandle; syscall::MAX_TRANSFER_HANDLES], + handles: [ReceivedEntry; syscall::MAX_TRANSFER_HANDLES], ) -> Result<(Frame, Batch), Refused> { let got = got.map_err(Refused::Kernel)?; // The kernel installed each number in this process's table for this @@ -587,13 +581,13 @@ fn step(taken: Result) -> RxStep { /// The handles one frame carried, owned, handed out one at a time: what /// nobody takes is closed when this drops. pub struct Batch { - handles: [RawHandle; syscall::MAX_TRANSFER_HANDLES], + handles: [ReceivedEntry; syscall::MAX_TRANSFER_HANDLES], next: usize, len: usize, } impl Batch { - const EMPTY: Batch = Batch { handles: [toyos_abi::HANDLE_INVALID; syscall::MAX_TRANSFER_HANDLES], next: 0, len: 0 }; + const EMPTY: Batch = Batch { handles: [ReceivedEntry::NONE; syscall::MAX_TRANSFER_HANDLES], next: 0, len: 0 }; /// The handles not yet taken. pub fn len(&self) -> usize { @@ -605,7 +599,7 @@ impl Batch { } /// Exactly `N` handles; `None`, with them closed, for any other number. - pub fn exact(mut self) -> Option<[OwnedHandle; N]> { + pub fn exact(mut self) -> Option<[ReceivedHandle; N]> { const { assert!(N <= syscall::MAX_TRANSFER_HANDLES) }; if self.len() != N { return None; @@ -615,12 +609,12 @@ impl Batch { } impl Iterator for Batch { - type Item = OwnedHandle; + type Item = ReceivedHandle; - fn next(&mut self) -> Option { - let raw = *self.handles[..self.len].get(self.next)?; + fn next(&mut self) -> Option { + let entry = *self.handles[..self.len].get(self.next)?; self.next += 1; - Some(OwnedHandle(raw)) + Some(ReceivedHandle { handle: OwnedHandle(entry.handle), kind: entry.kind }) } } @@ -630,6 +624,67 @@ impl Drop for Batch { } } +/// A handle a frame carried, with the kind of object the kernel said it +/// names as it installed it. What the sender's protocol says it is is only +/// the sender's word, and the kernel ends a process that uses a handle as a +/// kind it is not: a typed handle is made of this by [`Self::typed`] alone. +#[derive(Debug)] +pub struct ReceivedHandle { + handle: OwnedHandle, + kind: u32, +} + +impl ReceivedHandle { + /// The handle as a `T`, refused and closed unless the kernel said it + /// names one of `T`'s kinds. + pub fn typed(self) -> Result { + admit(T::KINDS, self.kind)?; + // SAFETY: the kernel answered one of `T`'s kinds for this handle, and + // any object of those takes every call of `T`'s. + Ok(unsafe { T::from_handle(self.handle) }) + } +} + +/// The bare handle, to send on, endow or map: nothing typed is made of it. +impl From for OwnedHandle { + fn from(received: ReceivedHandle) -> Self { + received.handle + } +} + +impl AsHandle for ReceivedHandle { + fn as_handle(&self) -> BorrowedHandle<'_> { + self.handle.as_handle() + } +} + +/// A received handle names another kind of object than its protocol says. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct WrongKind { + pub wanted: &'static [ObjectKind], + /// `None` for a kind this SDK does not know. + pub got: Option, +} + +impl core::fmt::Display for WrongKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.got { + Some(got) => write!(f, "a received handle names a {got:?}, not one of {:?}", self.wanted), + None => write!(f, "a received handle names a kind this SDK does not know, not one of {:?}", self.wanted), + } + } +} + +impl core::error::Error for WrongKind {} + +/// Whether a handle the kernel reported as `word` is one of `wanted`. +fn admit(wanted: &'static [ObjectKind], word: u32) -> Result<(), WrongKind> { + match ObjectKind::from_word(word) { + Some(kind) if crate::endow::names(wanted, kind) => Ok(()), + got => Err(WrongKind { wanted, got }), + } +} + /// A payload's bytes, as a frame carries them. pub(crate) fn as_bytes(val: &T) -> &[u8] { unsafe { core::slice::from_raw_parts(val as *const T as *const u8, core::mem::size_of::()) } @@ -714,7 +769,7 @@ mod tests { /// is no frame, and the type is read from the front. #[test] fn a_message_shorter_than_a_type_is_malformed() { - let none = [toyos_abi::HANDLE_INVALID; syscall::MAX_TRANSFER_HANDLES]; + let none = [ReceivedEntry::NONE; syscall::MAX_TRANSFER_HANDLES]; let seven = 7u32.to_ne_bytes(); assert!(matches!(taken(Ok(Received { len: TYPE_LEN - 1, handles: 0 }), seven, none), Err(Refused::Malformed))); let (frame, _) = taken(Ok(Received { len: TYPE_LEN, handles: 0 }), seven, none).ok().unwrap(); @@ -726,7 +781,7 @@ mod tests { /// The kernel's refusals reach a server as the step it acts on. #[test] fn a_receive_refused_is_the_step_a_server_acts_on() { - let none = [toyos_abi::HANDLE_INVALID; syscall::MAX_TRANSFER_HANDLES]; + let none = [ReceivedEntry::NONE; syscall::MAX_TRANSFER_HANDLES]; for (e, idle, eof) in [ (SyscallError::WouldBlock, true, false), (SyscallError::Gone, false, true), @@ -744,7 +799,7 @@ mod tests { /// kernel fault. #[test] fn a_receive_refused_is_the_error_a_client_acts_on() { - let none = [toyos_abi::HANDLE_INVALID; syscall::MAX_TRANSFER_HANDLES]; + let none = [ReceivedEntry::NONE; syscall::MAX_TRANSFER_HANDLES]; let blocking = |got| taken(got, [0; TYPE_LEN], none).map(|(frame, _)| frame).map_err(Refused::blocking); assert!(matches!(blocking(Err(SyscallError::InvalidArgument)), Err(IpcError::Malformed))); assert!(matches!(blocking(Ok(Received { len: TYPE_LEN - 1, handles: 0 })), Err(IpcError::Malformed))); @@ -755,6 +810,28 @@ mod tests { )); } + /// A received handle becomes a typed one only as a kind its type names: + /// another kind, and a word this SDK does not know, are refused by name. + #[test] + fn a_received_handle_is_typed_only_as_the_kind_the_kernel_reported() { + use crate::wake::{Bell, Waker}; + assert_eq!(admit(Bell::KINDS, ObjectKind::EventWait as u32), Ok(())); + assert_eq!( + admit(Bell::KINDS, ObjectKind::EventSignal as u32), + Err(WrongKind { wanted: Bell::KINDS, got: Some(ObjectKind::EventSignal) }) + ); + assert_eq!(admit(Waker::KINDS, ObjectKind::EventSignal as u32), Ok(())); + assert_eq!(admit(crate::Pipe::KINDS, ObjectKind::PipeRead as u32), Ok(())); + assert_eq!(admit(crate::Pipe::KINDS, ObjectKind::PipeWrite as u32), Ok(())); + assert_eq!( + admit(crate::process::Process::KINDS, ObjectKind::PipeRead as u32), + Err(WrongKind { wanted: &[ObjectKind::Process], got: Some(ObjectKind::PipeRead) }) + ); + let unknown = ObjectKind::ALL.len() as u32; + assert_eq!(admit(Bell::KINDS, unknown), Err(WrongKind { wanted: Bell::KINDS, got: None })); + assert_eq!(admit(Bell::KINDS, ReceivedEntry::NONE.kind), Err(WrongKind { wanted: Bell::KINDS, got: None })); + } + /// The kernel's refusal leaves every handle at its own number, and the /// batch closes each of them, once. #[test] diff --git a/sdk/toyos/src/launch.rs b/sdk/toyos/src/launch.rs index 194c768f9aa..5e9978a5507 100644 --- a/sdk/toyos/src/launch.rs +++ b/sdk/toyos/src/launch.rs @@ -34,6 +34,7 @@ use alloc::vec::Vec; use crate::fd::Kind; use crate::ipc::{Connection, IpcError}; +use crate::process::Process; use crate::OwnedHandle; /// The label a holder is endowed its launcher under, and the one name in that @@ -357,7 +358,7 @@ pub enum LaunchError { /// What a launch answered. pub enum Outcome { /// The handle the caller now holds for the child. - Started(OwnedHandle), + Started(Process), /// Not permitted ([`MSG_NOT_PERMITTED`]). NotPermitted, /// Declared, and it did not start. @@ -387,7 +388,7 @@ pub fn launch(conn: &Connection, request: Launch<'_>) -> Result match rx.take_handles::<1>() { - Some([process]) => Ok(Outcome::Started(process)), + Some([process]) => process.typed().map(Outcome::Started).map_err(|_| LaunchError::Sent(IpcError::Malformed)), None => Err(LaunchError::Sent(IpcError::Malformed)), }, MSG_NOT_PERMITTED => Ok(Outcome::NotPermitted), diff --git a/sdk/toyos/src/lib.rs b/sdk/toyos/src/lib.rs index 46a02cc0ffa..5f6803b4ca4 100644 --- a/sdk/toyos/src/lib.rs +++ b/sdk/toyos/src/lib.rs @@ -298,8 +298,6 @@ impl Pipe { self.0.into_raw() } - /// Take ownership of a pipe end that arrived over a connection. - /// /// # Safety /// `raw` must be a live pipe-end handle this process owns and nothing else /// answers for. @@ -312,12 +310,6 @@ impl AsHandle for Pipe { fn as_handle(&self) -> BorrowedHandle<'_> { self.0.as_handle() } } -/// A pipe end a peer sent: its type is the sender's word, as a connection's -/// is ([`Connection::received`]). -impl From for Pipe { - fn from(handle: OwnedHandle) -> Self { Self(handle) } -} - /// Each typed handle, given up as the handle it owns: to send, endow or /// provide it. macro_rules! owned_from { diff --git a/sdk/toyos/src/namespace.rs b/sdk/toyos/src/namespace.rs index bf63297a90e..25f75eb2fc4 100644 --- a/sdk/toyos/src/namespace.rs +++ b/sdk/toyos/src/namespace.rs @@ -54,13 +54,6 @@ impl Namespace { self.0.try_clone().map(Self) } - /// A namespace a peer sent, consumed. Its type is the sender's word: the - /// kernel refuses each namespace call on a handle of another type, as it - /// does a connection's ([`Connection::received`]). - pub fn received(handle: OwnedHandle) -> Self { - Self(handle) - } - /// # Safety /// `raw` must be a live namespace handle this process owns and nothing else /// answers for. diff --git a/sdk/toyos/src/net.rs b/sdk/toyos/src/net.rs index c49ab8e9e9a..5b862fbd399 100644 --- a/sdk/toyos/src/net.rs +++ b/sdk/toyos/src/net.rs @@ -48,7 +48,7 @@ impl Drop for Exporter { } } -use crate::ipc::{Frame, FrameRx, IpcError, IpcPayload}; +use crate::ipc::{Frame, FrameRx, IpcError, IpcPayload, ReceivedHandle}; use crate::ipc_payload; use crate::{Connection, OwnedHandle}; @@ -323,8 +323,7 @@ const MAX_ADOPT: usize = { /// is read without waiting: netstack queued it before it answered the export, /// so it is there or never sent. `None`, with everything closed, where the /// process takes up no holder of it. -pub fn adopt_exported(end: OwnedHandle) -> Option { - let connection = Connection::received(end); +pub fn adopt_exported(connection: Connection) -> Option { let mut frames: FrameRx = FrameRx::new(); let first = match frames.pump(&connection) { crate::ipc::RxStep::Frame { msg_type, payload_len, .. } => { @@ -411,7 +410,7 @@ impl PendingResponse { /// The answer, the `N` handles it hands over, and the connection, which /// is then the caller's. - pub fn answer_with_handles(self) -> Result<(Resp, [OwnedHandle; N], Connection), NetError> { + pub fn answer_with_handles(self) -> Result<(Resp, [ReceivedHandle; N], Connection), NetError> { const { assert!(core::mem::size_of::() <= MAX_ANSWER && core::mem::size_of::() <= MAX_ANSWER) }; let mut rx = FrameRx::::new(); let frame = answer(self.conn(), &mut rx)?; diff --git a/sdk/toyos/src/net/ends.rs b/sdk/toyos/src/net/ends.rs index ab56cb7d4cb..b0dc5710e8b 100644 --- a/sdk/toyos/src/net/ends.rs +++ b/sdk/toyos/src/net/ends.rs @@ -10,6 +10,8 @@ use toyos_abi::handle::Rights; use toyos_abi::syscall; use toyos_abi::RawHandle; +use crate::endow::OfKind; +use crate::ipc::ReceivedHandle; use crate::{AsHandle, BorrowedHandle, OwnedHandle}; use super::NetError; @@ -29,6 +31,12 @@ pub(super) fn narrowed(handle: OwnedHandle, rights: Rights) -> Result>(handle: ReceivedHandle, rights: Rights) -> Result { + narrowed(handle.typed::().map_err(|_| NetError::Io)?.into(), rights) +} + /// A handle an end keeps, lent for a call. /// /// # Safety diff --git a/sdk/toyos/src/net/socket.rs b/sdk/toyos/src/net/socket.rs index 065028e54d4..25ef689cd32 100644 --- a/sdk/toyos/src/net/socket.rs +++ b/sdk/toyos/src/net/socket.rs @@ -36,11 +36,11 @@ use toyos_abi::handle::Rights; use toyos_abi::syscall; use toyos_abi::RawHandle; -use crate::ipc::{self, Connection, FrameRx}; +use crate::ipc::{self, Connection, FrameRx, ReceivedHandle}; use crate::sync::Lock; use crate::{AsHandle, BorrowedHandle, OwnedHandle}; -use super::ends::{close_kept, kept, narrowed, Ends, HELD_BELL, HELD_CONNECTION}; +use super::ends::{close_kept, kept, narrowed, narrowed_as, Ends, HELD_BELL, HELD_CONNECTION}; use super::stream::{self, Wait}; use super::{answer, hangup, BoundResponse, MAX_ANSWER, MsgType, NetError, NetstackConn, TcpListenRequest, TcpOptions, UdpBindRequest}; @@ -325,7 +325,7 @@ pub fn bind(addr: [u8; 4], port: u16) -> Result { /// hands over the bell and makes the connection this process's holder. Refused /// [`NetError::ResourceExhausted`] before anything is made where the process holds /// [`MAX_SOCKETS`]. -fn open(kind: Kind, addr: [u8; 4], ask: impl FnOnce(NetstackConn) -> Result<(BoundResponse, [OwnedHandle; 1], Connection), NetError>) -> Result { +fn open(kind: Kind, addr: [u8; 4], ask: impl FnOnce(NetstackConn) -> Result<(BoundResponse, [ReceivedHandle; 1], Connection), NetError>) -> Result { let slot = TABLE.lock().reserve().ok_or(NetError::ResourceExhausted)?; let made = NetstackConn::connect().and_then(ask).and_then(|(bound, [bell], connection)| { let (descriptor, connection, bell) = take_up(kind, connection.into_owned(), bell)?; @@ -347,13 +347,13 @@ fn open(kind: Kind, addr: [u8; 4], ask: impl FnOnce(NetstackConn) -> Result<(Bou /// A holder's connection and bell, narrowed to what a `kind` socket's holder uses, and a copy of /// the bell for the descriptor: the descriptor's, the connection, the bell. Refused, every handle /// is closed. -fn take_up(kind: Kind, connection: OwnedHandle, bell: OwnedHandle) -> Result<(OwnedHandle, BorrowedHandle<'static>, BorrowedHandle<'static>), NetError> { +fn take_up(kind: Kind, connection: OwnedHandle, bell: ReceivedHandle) -> Result<(OwnedHandle, BorrowedHandle<'static>, BorrowedHandle<'static>), NetError> { let rights = match kind { Kind::Listener => HELD_CONNECTION.union(Rights::TRANSFER), Kind::Datagram => HELD_CONNECTION, }; let connection = narrowed(connection, rights); - let bell = narrowed(bell, HELD_BELL); + let bell = narrowed_as::(bell, HELD_BELL); let refused = match (connection, bell) { // SAFETY: narrowed here for the end being made, which keeps them until its `free`. (Ok(connection), Ok(bell)) => match unsafe { kept(bell) }.try_clone_to_owned() { diff --git a/sdk/toyos/src/net/stream.rs b/sdk/toyos/src/net/stream.rs index 7a8880aa05d..d4fe4fa68f5 100644 --- a/sdk/toyos/src/net/stream.rs +++ b/sdk/toyos/src/net/stream.rs @@ -49,14 +49,15 @@ use toyos_abi::handle::Rights; use toyos_abi::syscall::{self, SyscallError}; use toyos_abi::RawHandle; -use super::ends::{close_kept, kept, narrowed, Ends, HELD_BELL, HELD_CONNECTION, HELD_RUNG}; +use super::ends::{close_kept, kept, narrowed, narrowed_as, Ends, HELD_BELL, HELD_CONNECTION, HELD_RUNG}; -use crate::ipc::{self, Connection, FrameRx}; +use crate::ipc::{self, Connection, FrameRx, ReceivedHandle}; use crate::shm::{Bytes, SharedMemory}; use crate::sync::Lock; use crate::transport::session::{self, Bells, Doorbell, Holder, HungUp, Layout, Mode, Polled, Receiving, Refused as Answer, Sending, Took, Waited}; use crate::transport::stream::Place; use crate::transport::Span; +use crate::wake::{Bell, Waker}; use crate::{AsHandle, BorrowedHandle, OwnedHandle}; use super::{answer, hangup, MsgType, NetError, NetstackConn, TcpAcceptResponse, TcpConnectRequest, TcpConnectResponse}; @@ -770,7 +771,8 @@ pub(super) fn accept(listener: BorrowedHandle<'_>, drain: super::socket::Drain<' let frame = answer(&listener, &mut rx)?; let accepted: TcpAcceptResponse = ipc::decode_payload(rx.payload(frame.payload_len)).map_err(hangup)?; let [connection, receive, send, rung] = rx.take_handles::<{ BELLS + 1 }>().ok_or(NetError::Protocol(frame.msg_type))?; - Ok((accepted, [receive, send, rung], Connection::received(connection))) + let connection = connection.typed().map_err(|_| NetError::Protocol(frame.msg_type))?; + Ok((accepted, [receive, send, rung], connection)) })?; let answer = opened.answer; Ok(Accepted { @@ -823,7 +825,7 @@ impl Answered for TcpAcceptResponse { fn open( nodelay: bool, peer: Endpoint, - ask: impl FnOnce(OwnedHandle) -> Result<(A, [OwnedHandle; BELLS], Connection), NetError>, + ask: impl FnOnce(OwnedHandle) -> Result<(A, [ReceivedHandle; BELLS], Connection), NetError>, ) -> Result, NetError> { let slot = TABLE.lock().reserve().ok_or(NetError::ResourceExhausted)?; let made = (|| { @@ -895,10 +897,11 @@ pub(super) fn hold_connection(connection: OwnedHandle) -> Result<(OwnedHandle, R /// A holder's connection, its bells and its region mapped at `base`, taken up /// as [`Held`]. Refused, the region is unmapped and every handle closed. -fn take_up(connection: OwnedHandle, bells: [OwnedHandle; BELLS], region: OwnedHandle, base: *mut u8) -> Result { +fn take_up(connection: OwnedHandle, bells: [ReceivedHandle; BELLS], region: OwnedHandle, base: *mut u8) -> Result { let connection = hold_connection(connection); let [receive, send, rung] = bells; - let (receive, send, rung) = (narrowed(receive, HELD_BELL), narrowed(send, HELD_BELL), narrowed(rung, HELD_RUNG)); + let (receive, send, rung) = + (narrowed_as::(receive, HELD_BELL), narrowed_as::(send, HELD_BELL), narrowed_as::(rung, HELD_RUNG)); let region = narrowed(region, HELD_REGION); match (connection, receive, send, rung, region) { (Ok((descriptor, connection)), Ok(receive_bell), Ok(send_bell), Ok(rung), Ok(region)) => { @@ -969,7 +972,9 @@ pub(super) fn ask_export(connection: BorrowedHandle<'_>, rung: Option match rx.take_handles::<1>() { - Some([end]) if frame.payload_len == 0 => Ok(end), + Some([end]) if frame.payload_len == 0 => { + end.typed::().map(Connection::into_owned).map_err(|_| NetError::Protocol(EXPORTED)) + } _ => Err(NetError::Protocol(EXPORTED)), }, REFUSED => { @@ -1023,7 +1028,7 @@ pub(super) fn adopted(connection: Connection, first: Option<(&[u8], ipc::Batch)> } /// An [`ADOPT`] frame's holder, payload, this end and handles. -fn adoption((payload, handles): (&[u8], ipc::Batch)) -> Option<(Holder, Adopt, Endpoint, [OwnedHandle; BELLS + 1])> { +fn adoption((payload, handles): (&[u8], ipc::Batch)) -> Option<(Holder, Adopt, Endpoint, [ReceivedHandle; BELLS + 1])> { let adopt: Adopt = ipc::decode_payload(payload).ok()?; let handles = handles.exact::<{ BELLS + 1 }>()?; let local = local_end(adopt.local_addr, adopt.local_port)?; diff --git a/sdk/toyos/src/process.rs b/sdk/toyos/src/process.rs index 7902a894a6a..96f294f6c21 100644 --- a/sdk/toyos/src/process.rs +++ b/sdk/toyos/src/process.rs @@ -10,7 +10,6 @@ pub use toyos_abi::end::{End, Fault}; use toyos_abi::handle::Rights; use toyos_abi::syscall::{self, ProcessStats, SyscallError}; -use crate::endow::FromHandle; use crate::shm::{Immutable, SharedMemory}; use crate::{AsHandle, OwnedHandle, RawHandle}; @@ -95,12 +94,6 @@ impl AsHandle for Process { } } -impl FromHandle for Process { - fn from_handle(handle: OwnedHandle) -> Self { - Self(handle) - } -} - /// A program's bytes in a memory object this process holds, which a spawn /// lays out and the kernel pages the child from (`SpawnMap::object`). pub struct Image { diff --git a/sdk/toyos/src/quit.rs b/sdk/toyos/src/quit.rs index 2ccc129a469..e5dcccb21ab 100644 --- a/sdk/toyos/src/quit.rs +++ b/sdk/toyos/src/quit.rs @@ -11,7 +11,7 @@ pub use toyos_abi::quit::Reason; use toyos_abi::syscall::{self, SyscallError}; -use crate::endow::{FromHandle, Once}; +use crate::endow::Once; use crate::{AsHandle, OwnedHandle, RawHandle}; /// This process's notice. @@ -52,11 +52,7 @@ impl AsHandle for Notice { } } -impl FromHandle for Notice { - fn from_handle(handle: OwnedHandle) -> Self { - Self(handle) - } -} +crate::endow::from_handle! { Notice: Notice => Notice } /// This process's notice, from the kernel under `QUIT_LABEL`. Borrowed, as /// [`crate::endow::this_process`] is. diff --git a/sdk/toyos/src/shm.rs b/sdk/toyos/src/shm.rs index bcf9ed4deb4..bb26b0e9775 100644 --- a/sdk/toyos/src/shm.rs +++ b/sdk/toyos/src/shm.rs @@ -96,7 +96,8 @@ impl SharedMemory { /// it is memory it allocated, `bytes` rounded up to whole pages, as /// [`Self::create`] makes it. The handle is consumed either way; one /// lent without `WRITE` is the kernel's `PermissionDenied`. - pub fn lent(handle: OwnedHandle, bytes: usize) -> Result { + pub fn lent(handle: impl Into, bytes: usize) -> Result { + let handle = handle.into(); let stat = syscall::fstat(handle.raw()).map_err(Lent::Kernel)?; judge(&stat, bytes)?; let ptr = map(&handle, VmProt::ReadWrite).map_err(Lent::Kernel)?; @@ -371,7 +372,8 @@ impl ReadOnly { } /// [`SharedMemory::lent`], mapped read-only, whatever the handle allows. - pub fn lent(handle: OwnedHandle, bytes: usize) -> Result { + pub fn lent(handle: impl Into, bytes: usize) -> Result { + let handle = handle.into(); let stat = syscall::fstat(handle.raw()).map_err(Lent::Kernel)?; judge(&stat, bytes)?; let ptr = map(&handle, VmProt::Read).map_err(Lent::Kernel)?; diff --git a/sdk/toyos/src/spawn.rs b/sdk/toyos/src/spawn.rs index f3cfd6a6f1d..92fb9e72284 100644 --- a/sdk/toyos/src/spawn.rs +++ b/sdk/toyos/src/spawn.rs @@ -375,7 +375,7 @@ impl Independent<'_> { job, }; match launch::launch(&conn, request) { - Ok(Outcome::Started(handle)) => Ok(Process(handle)), + Ok(Outcome::Started(process)) => Ok(process), Ok(Outcome::NotPermitted) => { Err(Refused::Permission("no row declares the program, or the launcher's row may not start it")) } diff --git a/sdk/toyos/src/surface.rs b/sdk/toyos/src/surface.rs index f4ade8f110b..1236a151bdb 100644 --- a/sdk/toyos/src/surface.rs +++ b/sdk/toyos/src/surface.rs @@ -301,8 +301,8 @@ impl Host { return Some(Notice::Dropped { client, why: "its foreground line grew past what a surface holds" }); } match how { - LINE_REPLACE => peer.line = line.collect(), - LINE_ADD => peer.line.extend(line), + LINE_REPLACE => peer.line = line.map(OwnedHandle::from).collect(), + LINE_ADD => peer.line.extend(line.map(OwnedHandle::from)), _ => { drop(line.collect::>()); self.close(i); diff --git a/sdk/toyos/src/syscap.rs b/sdk/toyos/src/syscap.rs index 4af7fcb578f..dee2d6940cb 100644 --- a/sdk/toyos/src/syscap.rs +++ b/sdk/toyos/src/syscap.rs @@ -11,7 +11,7 @@ use toyos_abi::handle::Rights; use toyos_abi::inventory::{RawRecord, Record, Undecodable}; use toyos_abi::syscall::{self, DeviceRequest, DeviceType, SyscallError}; -use crate::endow::FromHandle; +use crate::endow::ClaimType; use crate::{AsHandle, OwnedHandle, RawHandle}; pub struct SysCap(pub(crate) OwnedHandle); @@ -27,7 +27,7 @@ impl SysCap { /// `NotFound` is a machine with no such device, which is a fact the supervisor logs /// and endows nothing for — not a failure. `AlreadyExists` is another /// process holding the class, which is a different fact and stays loud. - pub fn claim(&self, class: DeviceType) -> Result { + pub fn claim(&self, class: DeviceType) -> Result { self.mint(DeviceRequest::Class(class)) } @@ -39,7 +39,7 @@ impl SysCap { /// [`DeviceType::PciFunction`] with no function named: a class is the whole /// of what the call above asks for, and this one cannot be asked without /// an arrival. - pub fn claim_function( + pub fn claim_function( &self, arrival: toyos_abi::inventory::Arrival, ) -> Result { @@ -49,7 +49,7 @@ impl SysCap { /// [`Self::claim_function`], its claim granted `windows` at the mint: the /// firmware windows the signed image's `drives` row names. `NotSupported` /// where the function has none of one. - pub fn claim_function_granted( + pub fn claim_function_granted( &self, arrival: toyos_abi::inventory::Arrival, windows: toyos_abi::pci::FirmwareWindows, @@ -62,7 +62,7 @@ impl SysCap { /// /// `NotFound` is a set that is not one function this machine can hand out /// whole, and `PermissionDenied` one the kernel drives itself. - pub fn claim_isa( + pub fn claim_isa( &self, set: toyos_abi::syscall::IsaId, ) -> Result { @@ -84,10 +84,14 @@ impl SysCap { syscall::job_floor(self.0.raw(), job.as_handle().raw(), bytes) } - fn mint(&self, request: DeviceRequest) -> Result { + fn mint(&self, request: DeviceRequest) -> Result { // The kernel installed this handle in this process's table for this // call and no other, so nothing else answers for it. - Ok(T::from_handle(OwnedHandle(syscall::device_claim(self.0.raw(), request)?))) + let claim = OwnedHandle(syscall::device_claim(self.0.raw(), request)?); + // SAFETY: the kernel made a claim of the class `request` names. + // That `T` is that class's type, or `Device`, is the caller's to + // keep: the bound does not hold it. + Ok(unsafe { T::from_handle(claim) }) } /// A second handle to this capability, carrying the same rights. diff --git a/sdk/toyos/src/wake.rs b/sdk/toyos/src/wake.rs index ce4078ddd18..0596be6e1f9 100644 --- a/sdk/toyos/src/wake.rs +++ b/sdk/toyos/src/wake.rs @@ -116,17 +116,9 @@ impl AsHandle for Bell { } } -/// A side a peer sent: its type is the sender's word, as a connection's is. -impl From for Waker { - fn from(handle: OwnedHandle) -> Self { - Self(handle) - } -} - -impl From for Bell { - fn from(handle: OwnedHandle) -> Self { - Self(handle) - } +crate::endow::from_handle! { + Waker: EventSignal => Waker, + Bell: EventWait => Bell, } impl From for OwnedHandle { diff --git a/system/diskserver/src/main.rs b/system/diskserver/src/main.rs index 19c48e72714..6ffbdf84653 100644 --- a/system/diskserver/src/main.rs +++ b/system/diskserver/src/main.rs @@ -532,7 +532,7 @@ impl Service { let (Some(guid), Some([region]), wire::MSG_OPEN) = (guid, handles, msg_type) else { return refuse(&p.conn, Refusal::Malformed); }; - self.waiting.push(Waiting { conn: p.conn, badge, ask: Ask::Open { guid, region } }); + self.waiting.push(Waiting { conn: p.conn, badge, ask: Ask::Open { guid, region: region.into() } }); } /// Answer every waiting question the drivers have now answered, or whose diff --git a/system/diskserver/src/session.rs b/system/diskserver/src/session.rs index 114506ce7e8..a2187e53bc0 100644 --- a/system/diskserver/src/session.rs +++ b/system/diskserver/src/session.rs @@ -399,7 +399,9 @@ fn handshake( wire::MSG_OPENED => { let [ring, answered] = rx.take_handles::<{ wire::OPENED_HANDLES }>().ok_or(Error::Protocol)?; let opened = Opened::decode(rx.payload(frame.payload_len)).ok_or(Error::Protocol)?; - Ok((Bells { ring: Waker::from(ring), answered: Bell::from(answered) }, opened)) + let ring = ring.typed::().map_err(|_| Error::Protocol)?; + let answered = answered.typed::().map_err(|_| Error::Protocol)?; + Ok((Bells { ring, answered }, opened)) } wire::MSG_REFUSED => Err(Refusal::decode(rx.payload(frame.payload_len)).map_or(Error::Protocol, Error::Refused)), _ => Err(Error::Protocol), diff --git a/system/hda/src/main.rs b/system/hda/src/main.rs index 6ee7022632b..00d16ddc5d2 100644 --- a/system/hda/src/main.rs +++ b/system/hda/src/main.rs @@ -445,7 +445,7 @@ fn offered(control: &Connection, offer: &Offer) -> Result let [ring] = rx .take_handles::<{ output::ACCEPTED_HANDLES }>() .ok_or_else(|| refused("its acceptance carried no ring".into()))?; - Ok(ring) + Ok(ring.into()) } Some(ToDriver::Refused) => Err(Refusal::Declined), _ => Err(refused(format!("it answered the offer with frame {}", frame.msg_type))), diff --git a/system/logkeeper/src/main.rs b/system/logkeeper/src/main.rs index 72197c9a77f..d9595fe4acc 100644 --- a/system/logkeeper/src/main.rs +++ b/system/logkeeper/src/main.rs @@ -400,7 +400,10 @@ impl Log { let Some([ring, alive]) = self.supervisor_rx.take_handles::<2>() else { panic!("logkeeper: the supervisor registered {name:?} with no ring"); }; - self.take(registration.tag, registration.pid, ring, Bell::from(alive)); + let Ok(alive) = alive.typed::() else { + panic!("logkeeper: the supervisor registered {name:?} with a bell that is none"); + }; + self.take(registration.tag, registration.pid, ring.into(), alive); } // **The flush runs before the next frame is read**: the supervisor sends // RESUME when the stop it flushed for was refused, which can be diff --git a/system/netstack/src/serve.rs b/system/netstack/src/serve.rs index 46a395b4c01..07069002dcc 100644 --- a/system/netstack/src/serve.rs +++ b/system/netstack/src/serve.rs @@ -213,8 +213,8 @@ type Lent = (Region, [OwnedHandle; BELLS], Rc); /// connection being `connection`: the session the node is lent, the bells the /// answer hands over, and the holders. Refused by name, nothing is /// mapped, and the connection is handed back with the refusal's word. -fn lend(connection: Connection, region: toyos::OwnedHandle) -> Result { - let mapping = match Region::judged(region) { +fn lend(connection: Connection, region: ipc::ReceivedHandle) -> Result { + let mapping = match Region::judged(region.into()) { Ok(mapping) => mapping, Err(why) => { say!("netstack: refusing a stream's region — {why}"); diff --git a/system/session/src/main.rs b/system/session/src/main.rs index a84a9f33e77..e4c444ea245 100644 --- a/system/session/src/main.rs +++ b/system/session/src/main.rs @@ -153,7 +153,7 @@ fn from_supervisor( wire::MSG_STARTED => { let started = Started::decode(payload).expect("session: the supervisor's start is one it writes"); let [handle] = batch.exact::<1>().expect("session: a start carries its app"); - let process: Process = toyos::endow::FromHandle::from_handle(handle); + let process: Process = handle.typed().expect("session: a start's handle is its app's process"); apps.push((started.instance, process)); owner.started(started.instance, started.name) } diff --git a/system/sshserver/src/spawn.rs b/system/sshserver/src/spawn.rs index 45ec39fa5c5..5cf83747fd9 100644 --- a/system/sshserver/src/spawn.rs +++ b/system/sshserver/src/spawn.rs @@ -69,7 +69,8 @@ impl Ender { let quit = toyos::AsHandle::as_handle(child) .try_clone_narrowed(toyos_abi::Rights::MANAGE) .map_err(|e| format!("no handle to end it was had ({e:?})"))?; - Ok(Self(toyos::process::Process::from_handle(quit))) + // SAFETY: a duplicate of the child's own process handle. + Ok(Self(unsafe { toyos::process::Process::from_handle(quit) })) } /// Ask it to quit with a hang-up, whether or not a thread waits for it. diff --git a/system/supervisor/src/main.rs b/system/supervisor/src/main.rs index 3ae16311b51..1ff6175a728 100644 --- a/system/supervisor/src/main.rs +++ b/system/supervisor/src/main.rs @@ -554,7 +554,8 @@ fn main() { } builder.finish().expect("supervisor: no namespace for its own files") }; - if let Err(e) = std::os::toyos::fs::adopt_namespace(build().into()) { + // SAFETY: the namespace built just above. + if let Err(e) = unsafe { std::os::toyos::fs::adopt_namespace(build().into()) } { panic!("supervisor: std would not take its namespace ({e}), and the supervisor is endowed none"); } Box::leak(Box::new(build())) @@ -1917,7 +1918,7 @@ impl<'a> Supervisor<'a> { if matches!(&service.source, Binary::Swapped(running) if running.digest == request.digest) { return refuse(name, Refusal::AlreadyRuns(request.digest)); } - let swapped_in = match Swapped::copied(lent, &request) { + let swapped_in = match Swapped::copied(lent.into(), &request) { Ok(swapped_in) => swapped_in, Err(why) => return refuse(name, why), }; @@ -2410,17 +2411,25 @@ impl Supervisor<'_> { // Past every refusal that does not know which handle is which: the // descriptors, the extras, the place if it names one, and the job. let mut carried = carried; - let fds: Vec = carried.by_ref().take(request.fd_count()).collect(); + let fds: Vec = carried.by_ref().take(request.fd_count()).map(Into::into).collect(); // `SYS_NAMESPACE_BUILD` copies a connector into the namespace and // leaves the caller's handle, and the supervisor's copy of a client's - // connector has no life beyond this launch. **Not a claim about the - // type** — a client sends what it likes, and everything below treats a - // wrong one as a refused launch rather than as the supervisor's own bug. - let extras: Vec<(String, Connector)> = - names.into_iter().zip(carried.by_ref()).map(|(name, handle)| (name.to_string(), Connector::from_handle(handle))).collect(); - let mut rest: Vec = carried.collect(); - let job = toyos::job::Job::from_handle(rest.pop().expect("a launch's batch ends with its job")); - let place = rest.pop(); + // connector has no life beyond this launch. + let extras: Result, ipc::WrongKind> = + names.into_iter().zip(carried.by_ref()).map(|(name, handle)| Ok((name.to_string(), handle.typed()?))).collect(); + let mut rest: Vec = carried.collect(); + let job = rest.pop().expect("a launch's batch ends with its job").typed::(); + let place = rest.pop().map(ipc::ReceivedHandle::typed::).transpose(); + // A client sends what it likes: what its frame calls a connector, a + // place or a job and the kernel does not is a refused launch. + let (extras, job, place) = match (extras, job, place) { + (Ok(extras), Ok(job), Ok(place)) => (extras, job, place), + (Err(why), ..) | (_, Err(why), _) | (.., Err(why)) => { + say!("supervisor: launcher: refused a launch whose handles are not what its frame says: {why}"); + let _ = conn.try_signal(launch::MSG_REFUSED); + return; + } + }; // std joins a relative `current_dir` onto the supervisor's own cwd, so passing one // on would start the child in the supervisor's `/` — the default this field removes. @@ -3018,9 +3027,11 @@ fn translated(claim: toyos::Device, function: &Pci) -> Result Ok(claim), Ok(false) => Err(Missing::Untranslated(None)), diff --git a/system/supervisor/src/sessions.rs b/system/supervisor/src/sessions.rs index 060e9099ba2..cc653f17f31 100644 --- a/system/supervisor/src/sessions.rs +++ b/system/supervisor/src/sessions.rs @@ -55,7 +55,7 @@ pub(crate) struct Login { pub link: Link, rx: LaunchRx, /// The owner's handle to itself, which places a detached app under it. - place: Option, + place: Option, /// The session's launcher port, which every launcher in it is minted on. /// Held here alone, so the session's end closes it and a launch connected /// to it reads its end. @@ -450,8 +450,9 @@ impl Supervisor<'_> { Ok(_) => say!("supervisor: launcher: refusing a handed-over launch: the table is full"), Err(why) => say!("supervisor: launcher: a launch session {} handed over is dropped: {why}", session.share()), }, - wire::MSG_PLACE => match batch.exact::<1>() { - Some([handle]) => self.logins.open[at].place = Some(handle), + wire::MSG_PLACE => match batch.exact::<1>().map(|[handle]| handle.typed()) { + Some(Ok(place)) => self.logins.open[at].place = Some(place), + Some(Err(why)) => say!("supervisor: session {}'s owner sent a place that is none: {why}", session.share()), None => say!("supervisor: session {}'s owner sent no place", session.share()), }, wire::MSG_WAITING => { @@ -475,7 +476,7 @@ impl Supervisor<'_> { /// `session`, which the badge must name. fn handed_over(&self, session: Session, badge: &[u8], batch: ipc::Batch) -> Result { let [handle] = batch.exact::<1>().ok_or("it carried no single connection")?; - let conn = Connection::received(handle); + let conn = handle.typed::().map_err(|why| why.to_string())?; let Some(Authority { row, session: named }) = Authority::decode(badge) else { return Err(format!("its badge is not one this supervisor writes: {badge:?}")); }; diff --git a/tests/guest/osbench/src/sys/toyos.rs b/tests/guest/osbench/src/sys/toyos.rs index a43d1a41204..1475b942fb4 100644 --- a/tests/guest/osbench/src/sys/toyos.rs +++ b/tests/guest/osbench/src/sys/toyos.rs @@ -137,7 +137,7 @@ pub mod ipc { use std::os::toyos::process::CommandExt; use std::process::{Child, Command, Stdio}; - use toyos::endow::{Endowments, FromHandle}; + use toyos::endow::Endowments; use toyos::ipc::{Connection, MAX_FRAME_LEN}; use toyos::port::{self, Acceptor}; use toyos::{OwnedHandle, Pipe}; @@ -199,8 +199,7 @@ pub mod ipc { pub fn recv_with(&self, buf: &mut [u8]) -> io::Result { let (_, handles) = self.0.recv_into(buf).map_err(|e| failed("receive", e))?; let [handle] = handles.exact::<1>().ok_or_else(|| io::Error::other("no handle came"))?; - // The peer sends only pipe ends. - Ok(Pipe::from_handle(handle)) + handle.typed::().map_err(io::Error::other) } } diff --git a/tests/guest/rust-tests/src/bin/messages_are_whole.rs b/tests/guest/rust-tests/src/bin/messages_are_whole.rs index 94748985eb0..c42589d611c 100644 --- a/tests/guest/rust-tests/src/bin/messages_are_whole.rs +++ b/tests/guest/rust-tests/src/bin/messages_are_whole.rs @@ -13,16 +13,17 @@ //! - Plain reads and writes of a connection are refused. //! - A receive after the peer has gone takes what it sent, then answers //! `Gone`; a send to a peer gone answers `Gone`. +//! - Each handle arrives beside the kind of object it names, and a receiver +//! that asks for another kind is refused by name and lives. -use toyos::ipc::Connection; +use toyos::ipc::{Connection, FrameRx, WrongKind}; use toyos::poller::OTHER_END_GONE; use toyos::{namespace, pipe_pair, port, AsHandle, OwnedHandle, Pipe}; use toyos_abi::syscall::{ self, Received, SyscallError, MAX_MESSAGE_LEN, MAX_QUEUED_HANDLES, MAX_QUEUED_MESSAGES, MAX_TRANSFER_HANDLES, PIPE_CAPACITY, }; -use toyos_abi::handle::TransferEntry; -use toyos_abi::RawHandle; +use toyos_abi::handle::{ObjectKind, ReceivedEntry, TransferEntry}; /// A failure ceiling only: an answer that never comes is the defect. const CEILING_NANOS: u64 = 10_000_000_000; @@ -44,6 +45,8 @@ fn main() { bytes_alone_are_refused(pair()); println!("messages_are_whole: plain reads and writes were refused"); gone_after_what_was_sent(pair()); + each_handle_arrives_with_its_kind(pair()); + println!("messages_are_whole: each handle arrived with its kind, and another kind was refused by name"); println!("messages_are_whole: ok"); } @@ -65,11 +68,11 @@ fn send(conn: &Connection, bytes: &[u8], handles: Vec, nonblock: bo /// The next message, its bytes and its handles, without waiting. fn recv(conn: &Connection) -> Result<(Vec, Vec), SyscallError> { let mut bytes = vec![0u8; MAX_MESSAGE_LEN]; - let mut handles = [RawHandle(0); MAX_TRANSFER_HANDLES]; + let mut handles = [ReceivedEntry::NONE; MAX_TRANSFER_HANDLES]; let got = syscall::message_recv(conn.as_handle().raw(), &mut [], &mut bytes, &mut handles, true)?; bytes.truncate(got.len); // SAFETY: the kernel installed these numbers in this process for this call. - Ok((bytes, handles[..got.handles].iter().map(|&h| unsafe { OwnedHandle::from_raw(h) }).collect())) + Ok((bytes, handles[..got.handles].iter().map(|h| unsafe { OwnedHandle::from_raw(h.handle) }).collect())) } /// A pipe end to send, and the end that sees it go. @@ -111,7 +114,7 @@ fn a_short_buffer_leaves_it_queued((client, server): (Connection, Connection)) { let (h2, _) = token(); send(&client, &[7; 8], vec![h1, h2], false).expect("a message of 8 bytes and two handles"); let raw = server.as_handle().raw(); - let mut handles = [RawHandle(0); MAX_TRANSFER_HANDLES]; + let mut handles = [ReceivedEntry::NONE; MAX_TRANSFER_HANDLES]; assert_eq!( syscall::message_recv(raw, &mut [], &mut [0; 7], &mut handles, true), Err(SyscallError::InvalidArgument), @@ -131,7 +134,7 @@ fn a_short_buffer_leaves_it_queued((client, server): (Connection, Connection)) { let got = syscall::message_recv(raw, &mut [], &mut bytes, &mut handles, true).expect("the message, whole"); assert_eq!((got, bytes), (Received { len: 8, handles: 2 }, [7; 8]), "the refused receives took part of it"); // SAFETY: installed for this call. - handles[..2].iter().for_each(|&h| drop(unsafe { OwnedHandle::from_raw(h) })); + handles[..2].iter().for_each(|h| drop(unsafe { OwnedHandle::from_raw(h.handle) })); // Its first bytes fill the head, and the rest the body, in order. send(&client, &[1, 2, 3, 4, 5], vec![], false).expect("a message of five bytes"); @@ -187,3 +190,45 @@ fn gone_after_what_was_sent((client, server): (Connection, Connection)) { assert_eq!(recv(&server).err(), Some(SyscallError::Gone), "after the last message, the leaving"); assert_eq!(send(&server, b"x", vec![], false), Err(SyscallError::Gone), "a send to a peer gone"); } + +fn each_handle_arrives_with_its_kind((client, server): (Connection, Connection)) { + let (read, write) = pipe_pair().expect("a pipe"); + let (waker, bell) = toyos::wake::pair().expect("an event"); + let region = toyos::shm::SharedMemory::create(1).expect("a region"); + let (acceptor, connector) = port::create().expect("a port"); + let sent = vec![ + (OwnedHandle::from(read), ObjectKind::PipeRead), + (OwnedHandle::from(write), ObjectKind::PipeWrite), + (OwnedHandle::from(waker), ObjectKind::EventSignal), + (OwnedHandle::from(bell), ObjectKind::EventWait), + (region.share().expect("a second handle to the region"), ObjectKind::SharedMem), + (OwnedHandle::from(acceptor), ObjectKind::Acceptor), + (OwnedHandle::from(connector), ObjectKind::Connector), + (OwnedHandle::from(toyos::job::own().expect("this process's job")), ObjectKind::Job), + ]; + let (handles, kinds): (Vec<_>, Vec<_>) = sent.into_iter().unzip(); + send(&client, &[], handles, false).expect("a message of eight handles"); + let mut entries = [ReceivedEntry::NONE; MAX_TRANSFER_HANDLES]; + let got = syscall::message_recv(server.as_handle().raw(), &mut [], &mut [], &mut entries, true).expect("the message"); + assert_eq!(got.handles, kinds.len(), "the message came with other handles than it carried"); + let entries = &entries[..got.handles]; + let reported: Vec<_> = entries.iter().map(|entry| entry.kind()).collect(); + assert_eq!(reported, kinds.into_iter().map(Some).collect::>(), "a handle arrived as another kind than it names"); + // SAFETY: installed for this call. + entries.iter().for_each(|entry| drop(unsafe { OwnedHandle::from_raw(entry.handle) })); + + // A pipe end where a process is asked for, then one taken as what it is. + let (_read, write) = pipe_pair().expect("a pipe"); + let (_also, second) = pipe_pair().expect("a second pipe"); + client.signal_with(1, [OwnedHandle::from(write), OwnedHandle::from(second)]).expect("a frame of two pipe ends"); + let mut rx = FrameRx::<0>::new(); + rx.recv(&server).expect("the frame"); + let [first, second] = rx.take_handles::<2>().expect("its two handles"); + assert_eq!( + first.typed::().err(), + Some(WrongKind { wanted: &[ObjectKind::Process], got: Some(ObjectKind::PipeWrite) }), + "a pipe end taken as a process" + ); + let pipe = second.typed::().expect("a pipe end taken as one"); + assert_eq!(pipe.write(b"x"), Ok(1), "the pipe end taken is not the one sent"); +} diff --git a/tests/guest/rust-tests/src/bin/moved_rights.rs b/tests/guest/rust-tests/src/bin/moved_rights.rs index 4cb6839f45e..1401aac48db 100644 --- a/tests/guest/rust-tests/src/bin/moved_rights.rs +++ b/tests/guest/rust-tests/src/bin/moved_rights.rs @@ -21,7 +21,7 @@ mod image; use toyos::ipc::Connection; use toyos::{namespace, port, AsHandle}; use toyos_abi::end::End; -use toyos_abi::handle::{Rights, TransferEntry}; +use toyos_abi::handle::{ReceivedEntry, Rights, TransferEntry}; use toyos_abi::syscall::{self, SpawnArgs, SpawnEndow, SyscallError, MAX_TRANSFER_HANDLES}; use toyos_abi::RawHandle; @@ -82,11 +82,11 @@ impl Pair { /// The one handle of the next message. fn take(&self) -> RawHandle { - let mut handles = [RawHandle(0); MAX_TRANSFER_HANDLES]; + let mut handles = [ReceivedEntry::NONE; MAX_TRANSFER_HANDLES]; let got = syscall::message_recv(self.server.as_handle().raw(), &mut [], &mut [0; 1], &mut handles, true) .expect("the message just sent"); assert_eq!(got.handles, 1, "the message carried {} handles", got.handles); - handles[0] + handles[0].handle } /// `h` moved through the connection as `entry` asks: its number after. diff --git a/tests/guest/rust-tests/src/bin/mutual_kill.rs b/tests/guest/rust-tests/src/bin/mutual_kill.rs index e45a71820f9..5e9d433cea6 100644 --- a/tests/guest/rust-tests/src/bin/mutual_kill.rs +++ b/tests/guest/rust-tests/src/bin/mutual_kill.rs @@ -15,7 +15,6 @@ use std::sync::atomic::{AtomicU32, Ordering}; use toyos::ipc::Connection; use toyos::process::Process; use toyos::shm::SharedMemory; -use toyos::endow::FromHandle; use toyos::{endow, namespace, port, OwnedHandle}; use toyos_abi::syscall::{self, SVC_LABEL}; use toyos_abi::RawHandle; @@ -125,7 +124,7 @@ fn killer() -> ! { assert_eq!(frame.msg_type, ARM, "an unexpected frame"); let [word_handle, victim] = rx.take_handles::<2>().expect("the word and the victim"); let go = SharedMemory::lent(word_handle, WORD_BYTES).expect("map the word"); - let victim = Process::from_handle(victim); + let victim = victim.typed::().expect("the victim is a process"); let mut out = std::io::stdout(); out.write_all(b"armed\n").expect("say armed"); diff --git a/tests/guest/rust-tests/src/bin/process_tree.rs b/tests/guest/rust-tests/src/bin/process_tree.rs index 17983471645..1a927416f54 100644 --- a/tests/guest/rust-tests/src/bin/process_tree.rs +++ b/tests/guest/rust-tests/src/bin/process_tree.rs @@ -33,7 +33,7 @@ use std::io::{BufRead, BufReader}; use std::os::toyos::process::CommandExt; use std::process::{Child, ChildStdin, Command, Stdio}; -use toyos::endow::{self, Endowments, FromHandle, SVC_LABEL, SYSCAP_LABEL}; +use toyos::endow::{self, Endowments, SVC_LABEL, SYSCAP_LABEL}; use toyos::ipc::Connection; use toyos::launch::{self, Launch, Outcome, Parent, LAUNCHER}; use toyos::process::Process; @@ -159,8 +159,9 @@ fn grow() -> Grown { let frame = rx.recv(&conn).expect("B's word that its subtree is grown"); assert_eq!(frame.msg_type, MSG_GROWN, "B said something else"); let [c, d, b_self, e] = rx.take_handles::<4>().expect("B sent four handles"); - let (c, d, e) = (Process::from_handle(c), Process::from_handle(d), Process::from_handle(e)); - Grown { b, conn, c, d, b_self, e, _held: held } + let process = |handle: toyos::ipc::ReceivedHandle| handle.typed::().expect("B sent a process"); + let (c, d, e) = (process(c), process(d), process(e)); + Grown { b, conn, c, d, b_self: b_self.into(), e, _held: held } } fn an_end_takes_its_subtree(end: Ending) { @@ -215,7 +216,7 @@ fn under_b_is_gone(grown: &Grown) { }; match launch::launch(&conn, request) { Ok(Outcome::Gone) => {} - Ok(Outcome::Started(child)) => panic!("a launch under B {when} started {child:?}"), + Ok(Outcome::Started(_)) => panic!("a launch under B {when} started"), Ok(_) => panic!("a launch under B {when} was answered as something other than gone"), Err(_) => panic!("the launcher did not answer a launch under B {when}"), } @@ -299,7 +300,7 @@ fn a_pipe_is_no_place() { }; match launch::launch(&conn, request) { Ok(Outcome::Refused) => {} - Ok(Outcome::Started(child)) => panic!("a launch whose place is a pipe started {child:?}"), + Ok(Outcome::Started(_)) => panic!("a launch whose place is a pipe started"), Ok(_) => panic!("a launch whose place is a pipe was answered as something other than refused"), Err(_) => panic!("the launcher did not answer a launch whose place is a pipe"), } diff --git a/tests/guest/rust-tests/src/bin/served_file_offset.rs b/tests/guest/rust-tests/src/bin/served_file_offset.rs index 0318d3c7b70..47cc427a080 100644 --- a/tests/guest/rust-tests/src/bin/served_file_offset.rs +++ b/tests/guest/rust-tests/src/bin/served_file_offset.rs @@ -129,7 +129,7 @@ fn refused_spawn_keeps_export(fd: i32) { // The kernel refuses after the SDK duplicated the descriptors and laid the program out. let refused = helper.spawn(Some(&image)).err(); assert_eq!(refused, Some(toyos::spawn::Refused::Syscall(toyos_abi::syscall::SyscallError::InvalidArgument))); - let mut dir = toyos::fs::Dir::adopt(toyos::ipc::Connection::received(exported)).expect("refusal kept the original export"); + let mut dir = toyos::fs::Dir::adopt(exported).expect("refusal kept the original export"); let fid = toyos::fs::Fid { id: 1, generation: dir.generation() }; assert_eq!(dir.seek(fid, toyos::fs::SEEK_CURRENT, 0).unwrap(), 4, "refusal kept the shared offset"); dir.close(fid); diff --git a/tests/guest/rust-tests/src/function.rs b/tests/guest/rust-tests/src/function.rs index 9bd17857a78..9d3b3461ddc 100644 --- a/tests/guest/rust-tests/src/function.rs +++ b/tests/guest/rust-tests/src/function.rs @@ -1,7 +1,7 @@ //! The PCI function a job claims, found in the inventory by what identifies //! its card: a claim names a function by its arrival. -use toyos::endow::FromHandle; +use toyos::endow::ClaimType; use toyos::syscap::SysCap; use toyos_abi::inventory::{Pci, RawRecord, Record}; use toyos_abi::syscall::{PciId, SyscallError}; @@ -22,6 +22,6 @@ pub fn of(cap: &SysCap, id: PciId) -> Option { /// The claim on the one function of card `id`: `NotFound` where the machine /// has none. -pub fn claim(cap: &SysCap, id: PciId) -> Result { +pub fn claim(cap: &SysCap, id: PciId) -> Result { of(cap, id).map_or(Err(SyscallError::NotFound), |pci| cap.claim_function(pci.arrival)) } diff --git a/tests/guest/test-runner/src/main.rs b/tests/guest/test-runner/src/main.rs index 427a2d590b7..904e8148fe2 100644 --- a/tests/guest/test-runner/src/main.rs +++ b/tests/guest/test-runner/src/main.rs @@ -314,7 +314,8 @@ fn run_one(name: &str, args: &[&str], cap: Option<&SysCap>) -> Ran { // machine with no console nothing anywhere says why. The list ends // here instead, by handing the machine back. match toyos::AsHandle::as_handle(&child).try_clone_to_owned() { - Ok(watch) => *JOB.lock().unwrap_or_else(PoisonError::into_inner) = Some(Process::from_handle(watch)), + // SAFETY: a duplicate of the child's own process handle. + Ok(watch) => *JOB.lock().unwrap_or_else(PoisonError::into_inner) = Some(unsafe { Process::from_handle(watch) }), Err(e) => give_the_machine_back(&format!( "job {name:?} started and its handle would not duplicate ({e:?}), so the \ deadline has nothing to end it with"