init: a protocol you were not granted does not exist

The registry consults the open rows it has been parsing since P2, so
reaching a contract now takes a grant as well as a binding. A caller
without one is answered exactly as it would be for a name nobody ever
bound: same status, same empty reply, same absent capability, byte for
byte, and no log line on either path — klog_read is ungated, so a line on
one and not the other would be the oracle the design set out to remove.
Refusal and absence being one answer is what lets a supervisor later
narrow, fake or park a child's namespace without the child learning what
it was denied.

The manifest gains a third permission for a shape the plan did not
foresee: attestation is one hop, but the driver tree is three deep — the
PS/2 keyboard and mouse are spawned by ps2-bus, which the device manager
spawned — so no row could name them and PS/2 input would simply stop.
 lets a delegate vouch for what its children *reach*, never for
what they claim; the bind path is untouched, and the laundering deputy is
still refused.

The review found the receive side of a rule this track had already
written down. Every process holds a sendable handle to the registrar —
resolve installs one for anyone who asks — and ipc_reply_wait never asked
who owned the endpoint, so a stranger could dequeue there: take the
provider endpoints riding bind requests, and answer other clients' opens
in the registrar's name. Receiving is the owner's privilege, like binding
a signal or a timer; sending remains anyone's.

Suite 109/109.
This commit is contained in:
Daniel Samson
2026-08-01 04:44:15 +01:00
parent 1379b699f3
commit 7f3eb848f0
13 changed files with 670 additions and 24 deletions
+91 -6
View File
@@ -1,9 +1,17 @@
# /system/configuration/protocol.csv — who may claim, and who may reach, a name
# under /protocol (docs/os-development/protocol-namespace.md).
#
# init is the registrar: it serves /protocol, and every bind is checked against
# this file. It is AUTHORITATIVE — a name no row grants cannot be bound, and a
# missing file means nothing may be bound at all.
# init is the registrar: it serves /protocol, and every bind AND every open is
# checked against this file. It is AUTHORITATIVE — a name no row grants cannot be
# bound or reached, and a missing file means nothing may be bound or reached at
# all.
#
# A refused open is answered exactly as a name nobody bound is: -ENOENT, and no
# capability. That is not politeness, it is the model — the namespace IS the
# restriction, so what a process may not open simply does not exist for it, and
# there is no "permission denied" for it to tell apart from "no such contract".
# Which is why a missing row here shows up as a client retrying forever rather
# than as an error: check this file first, and `readdir /protocol` second.
#
# '#' starts a comment (whole-line or trailing); blank lines are ignored.
# Whitespace around a field is trimmed, so columns may be padded. Four
@@ -26,13 +34,21 @@
# confer; init's own path means this init; any other path means a
# task init spawned itself or one the kernel spawned. Task ids are
# monotonic and never reused, so an id cannot be borrowed.
# permission bind (provide this contract) | open (speak to it)
# permission bind (provide this contract) | open (speak to it) |
# supervise (stand in someone else's chain — see below)
# name the contract, relative to /protocol
#
# A trailing '*' on any field matches any tail — how a subtree is granted whole.
#
# NOTE: 'open' rows are parsed but not yet enforced; every open resolves today.
# The milestone that turns them into refusals is P3 (docs/security-track-plan.md).
# 'supervise' exists because attestation is one hop deep and the driver tree is
# three: the device manager starts the PS/2 bus, and the bus starts the keyboard
# and mouse drivers. Init never met the bus, so it cannot vouch for it by
# acquaintance — and it must not vouch for it by name, or the laundering deputy
# walks straight in. A 'supervise' row is the manifest saying it: a task running
# this binary, under this supervisor, may be the supervising task an 'open' row
# names, for this contract and no other. It grants the delegate nothing itself,
# and it is deliberately open-only — a delegate may vouch for what its children
# REACH, never for what they CLAIM, so every bind refusal is untouched by it.
#
# binary supervisor permission name
@@ -68,3 +84,72 @@
# another fixture did.
/test/*, kernel, bind, test/*
/test/*, /test/*, bind, test/*
# ============================================================================
# open — who may REACH each contract. One row per client per contract; a client
# with no row here simply finds the name absent, forever.
# ============================================================================
# --- init's own services ----------------------------------------------------
# fat reaches the block device behind the volume it mounts; the compositor
# reaches the scanout its driver announced, its own endpoint (the mouse-listener
# thread opens /protocol/display like any other client — threads share no
# handles), and the input stream that moves the cursor.
/system/services/fat, /system/services/init, open, block
/system/services/display, /system/services/init, open, scanout
/system/services/display, /system/services/init, open, display
/system/services/display, /system/services/init, open, input
/system/services/display-demo, /system/services/init, open, display
# --- the same two when the kernel test harness starts them directly ---------
/system/services/display, kernel, open, scanout
/system/services/display, kernel, open, display
/system/services/display, kernel, open, input
/system/services/display-demo, kernel, open, display
# --- the drivers, and the discovery service ---------------------------------
# Every driver says hello to the manager that started it — one row for the whole
# subtree, because that handshake is what being a driver means. The rest are per
# driver: the storage and HID class drivers talk to their controller, the HID
# drivers publish into the input stream, and the GPU driver announces its scanout
# to the compositor.
/system/drivers/*, /system/services/device-manager, open, device-manager
/system/services/discovery, /system/services/device-manager, open, device-manager
/system/drivers/usb-storage, /system/services/device-manager, open, usb-transfer
/system/drivers/usb-hid-keyboard, /system/services/device-manager, open, usb-transfer
/system/drivers/usb-hid-keyboard, /system/services/device-manager, open, input
/system/drivers/usb-hid-mouse, /system/services/device-manager, open, usb-transfer
/system/drivers/usb-hid-mouse, /system/services/device-manager, open, input
/system/drivers/virtio-gpu, /system/services/device-manager, open, display
# --- the PS/2 child drivers, one hop further down ---------------------------
# The keyboard and mouse drivers are started by the BUS driver, not by the
# device manager — the one three-deep chain in the tree. Init cannot vouch for
# the bus by acquaintance (it never started it), so the manifest authorizes it
# explicitly, and only for the two contracts its children need.
/system/drivers/ps2-bus, /system/services/device-manager, supervise, ps2-bus
/system/drivers/ps2-bus, /system/services/device-manager, supervise, input
/system/drivers/ps2-keyboard, /system/drivers/ps2-bus, open, ps2-bus
/system/drivers/ps2-keyboard, /system/drivers/ps2-bus, open, input
/system/drivers/ps2-mouse, /system/drivers/ps2-bus, open, ps2-bus
/system/drivers/ps2-mouse, /system/drivers/ps2-bus, open, input
# --- test fixtures ----------------------------------------------------------
# The /protocol/test subtree is theirs whole, the way the bind rows give it to
# them. Everything ABOVE that subtree is named one fixture at a time, so a
# fixture reaches a system contract only where a scenario needs it — which is
# what leaves the rest genuinely absent for the rest of them (the protocol-denied
# case asks for one it was not given, and is told there is no such thing).
/test/*, kernel, open, test/*
/test/*, /test/*, open, test/*
/test/*, kernel, open, device-manager
/test/*, /system/services/device-manager, open, device-manager
/test/system/services/input-source, kernel, open, input
/test/system/services/input-test, kernel, open, input
# The laundering-deputy probe (test/system/services/protocol-registry-test) runs
# a grandchild whose supervisor is a fixture nobody authorized — that is the
# point of it, and its bind must stay refused. It still has to report the verdict
# it got, so its reporting channel, and nothing else, is delegated.
/test/*, /test/*, supervise, test/verdict
Can't render this file because it contains an unexpected character in line 12 and column 15.