docs: device authority — the how, and the run that deletes the ceilings
device-authority.md is rewritten as an implementation design rather than a rival to device-manager.md. The what was already settled there in 2026-07: structure in the manager, authority in the kernel, and delegation as the step after hello. This is the how, plus the two decisions that paragraph leaves open. Decision 1: the manager claims, it is not granted. device-manager.md says "claims (or is granted)"; claiming wins because the manager runs before any driver exists and takes the seeded devices unopposed, leaving nothing unheld to race for. One new call, device_transfer(device_id, task_id), checks only that the caller holds the device — no names in the kernel, no attestation. The alternative put a binary path inside the kernel, and the kernel should hold only what cannot safely live in user space. The residual is stated rather than hidden: authority rests on the manager claiming first, which init.csv makes an operator-visible ordering rather than an attacker- controlled one, and the enforced version arrives with the spawn capability drivers.md already names as missing. Decision 2: the kernel stops holding inventory. It reads three things out of a descriptor — physical ranges, interrupt numbers, one PCI BDF — and stores the rest only so device_enumerate can hand it back. Devices with no resources leave the kernel entirely: a USB device conveys no mapping authority, so there is nothing to enforce. That is also the case which sidesteps containment, and therefore the reason a shared cap existed. Decision 3: no shared ceiling. The table becomes dynamic — it is built after heap.init, so nothing ever prevented it — and the two invented numbers go. A per-holder quota replaces them, because dynamic storage with no bound moves the ceiling to the kernel heap, which is shared and fatal rather than partial. A bound charged to whoever caused it is isolation. Two earlier drafts of this document are gone: one gave init the root grants, the other proposed extracting a firmware-framebuffer driver. Both were wrong and both are recorded as wrong in the run plan's settled list — the framebuffer is not a device, it is where pixels go until a real display driver announces itself. Run 2 is nine steps, ordered so the suite stays green throughout: build and prove the transfer mechanism, move the five claimants across one at a time, then the flag day, then the inventory, then the ceilings.
This commit is contained in:
@@ -19,11 +19,68 @@ next one starts.*
|
||||
| L5 | USB: interfaces from the descriptor, and the misattributed-endpoint bug | **done** — fix is by construction; no direct test, see open question 5 |
|
||||
| L6 | xHCI: a failed `allocateDevice` stops leaking an enabled slot | **done** — path forced and verified; no regression test, see open question 5 |
|
||||
|
||||
**Run complete.** L1 stopped (the step was wrong), L2–L6 landed. Suite 115 → 116.
|
||||
**Run 1 complete.** L1 stopped (the step was wrong), L2–L6 landed. Suite 115 → 116.
|
||||
Allowlist 278 → 269. Two steps ship without a permanent regression test, both because
|
||||
QEMU's USB devices are too small to reach the paths — see open question 5, which is the
|
||||
audit's own lesson recurring: the test rig is smaller than a real machine.
|
||||
|
||||
---
|
||||
|
||||
## Run 2 — device authority: delete the invented ceilings
|
||||
|
||||
*Design: [device-authority.md](os-development/device-authority.md), which is the **how**
|
||||
for the delegation step [device-manager.md](device-driver-development/device-manager.md)
|
||||
already settled. Read both before starting; the second is authoritative where they
|
||||
differ.*
|
||||
|
||||
The goal, in the project owner's words: **remove the maximum values we set arbitrarily,
|
||||
move the responsibility to the device manager, and keep in the kernel only the parts
|
||||
that cannot safely run in user space.**
|
||||
|
||||
| Step | What | State |
|
||||
|---|---|---|
|
||||
| D1 | `device_transfer(device_id, task_id)` — the holder gives a device away | not started |
|
||||
| D2 | Adversarial case: a process handed nothing is refused, on a held device and a free one | not started |
|
||||
| D3 | The manager claims the seeded devices at boot, before any driver is spawned | not started |
|
||||
| D4 | `usb-xhci-bus` receives its controller in the `hello` reply instead of claiming argv[1] | not started |
|
||||
| D5 | The other four claimants converted: `pci-bus`, `ps2-bus`, `virtio-gpu`, `acpi` | not started |
|
||||
| D6 | `device_claim` refuses a device the caller was not handed; the hole is closed | not started |
|
||||
| D7 | Zero-resource devices stop being kernel objects — inventory moves to the manager | not started |
|
||||
| D8 | **`maximum_children_per_parent` deleted** — the authorisation it stood in for exists | not started |
|
||||
| D9 | The device table becomes dynamic; **`maximum_devices` deleted**; per-holder quota declared | not started |
|
||||
|
||||
Ordering is load-bearing. D1–D2 build and prove the mechanism with nothing depending on
|
||||
it. D3–D5 move each claimant across one at a time, so the suite stays green throughout
|
||||
and a regression names the driver that caused it. D6 is the flag day. D7 must precede
|
||||
D9, because zero-resource children are the case that sidesteps containment and so the
|
||||
reason a shared cap was needed at all.
|
||||
|
||||
### Settled, so the run does not re-litigate them
|
||||
|
||||
- **The manager claims, it is not granted.** No binary names in the kernel; the rule is
|
||||
"you may give away what you hold". The residual — it rests on the manager claiming
|
||||
first — is stated in the design and is closed later by the spawn capability
|
||||
[drivers.md](device-driver-development/drivers.md) already names as missing.
|
||||
- **The framebuffer is not a device.** It is where pixels go, handed over by the loader,
|
||||
and the compositor uses it as the boot floor until a real display driver announces
|
||||
itself. Nothing in this run touches the display service or its GOP path.
|
||||
- **`maximum_endpoints_per_interface` and the wire structs stay.** Widening them is a
|
||||
protocol change, out of scope.
|
||||
- **A per-holder quota is not a retreat.** Dynamic storage with no bound moves the
|
||||
ceiling to the kernel heap, which is shared and fatal rather than partial. A bound
|
||||
charged to the task that caused it is isolation, and it is declared through
|
||||
[bounds.md](os-development/bounds.md) like anything else.
|
||||
|
||||
### Working rules
|
||||
|
||||
As Run 1, unchanged: work in `/Users/danielsamson/Gitea/daniel/danos` on
|
||||
`claude/bounds-track`; every step lands with a test that fails before the fix, verified
|
||||
by restoring the old behaviour; full suite green before each commit; never two suites at
|
||||
once (`pgrep -f qemu_test.py`); 60 GiB free; `git commit -F` with no `Co-Authored-By`;
|
||||
update this table before starting the next step. **If a step needs a decision that is
|
||||
not written down, stop it, add the question below, and move on** — Run 1's first step
|
||||
was wrong and stopping was the right call.
|
||||
|
||||
**Suite:** 115/115 at the start of the run.
|
||||
**Branch:** `claude/bounds-track`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user