kernel: a refusal names its rule, and two bounds stop failing open

An AMD Ryzen booted to a working compositor with no USB and no storage,
and the log said only "register refused". A tree-wide audit of every
compile-time ceiling followed: 235 of them, 139 on quantities the machine
or a file decides rather than us, 5 documented anywhere, 171 silent when
reached. docs/fixed-bounds-audit.md has the inventory.

Errno attribution. The errno space was split between the kernel and the
envelope, free to drift; it is now one list in system/abi.zig, restated on
both sides, with a comptime check in library/device/driver where the two
halves are visible. device_register's six refusals and device_claim's three
are distinct codes, so a bus driver can say which rule stopped it, and
BadParent splits into NoSuchParent and NotYourParent. pci-bus reconciles
found against registered instead of counting refused functions as found.

Idempotency ordering. The child cap was checked before the identity match,
so a restarted bus was refused its own devices — the supervision restart the
system leans on ratcheted toward a degraded machine. A re-registration
consumes no slot and is now admitted first.

IOMMU fail-closed. confineDevice returned success for a device id past the
confinement table, leaving the device outside every domain while the caller
believed it confined — unreachable only while ids stop at 64, which both the
inventory move and a hardware-reported domain count would change. It refuses
now, and the coupling to the broker's device cap is a comptime assert rather
than a sentence in a comment.

PCI apertures. The bridge's MMIO apertures are derived from the holes in the
firmware memory map, and the derivation copied sub-4 GiB entries into a
fixed [64] array and skipped the rest. A skipped region is not merely lost:
the gap finder concludes it is free, so a real machine's 60-200 entry map
yields an aperture over live RAM, and containment then admits a child BAR
covering kernel memory. Rewritten to walk the map in place, with the hole
finder extracted as a pure function and driven by a synthetic 100-entry map
in a new test case. Both new tests were verified to fail on the old code.

parameters.zig gains the rationale it was missing and loses a stale sentence
pointing at the wrong file; vdso.md documents the errno space, including
EPEER, which had no written meaning anywhere.

docs/os-development/bounds.md is how a ceiling is declared from here.
docs/bounds-track-plan.md is the plan to remove the ones we invented.

Suite 114 -> 115.
This commit is contained in:
Daniel Samson
2026-08-08 11:09:54 +01:00
parent 7db5fba884
commit a86559648e
25 changed files with 1520 additions and 168 deletions
+20 -6
View File
@@ -44,6 +44,10 @@ var ecam_physical: u64 = 0;
var start_bus: u64 = 0;
var bus_count: u64 = 0;
var manager_handle: ipc.Handle = 0;
/// Functions this scan discovered but could not publish. Counted so the end of the
/// scan can reconcile "found" against "registered" — a scan that silently returns a
/// subset of the machine is the failure this driver is most able to hide.
var refused: u32 = 0;
/// One aligned 32-bit read from a function's configuration space.
fn configRead(bus: u64, dev: u64, function: u64, offset: u64) u32 {
@@ -74,10 +78,10 @@ fn configWrite16(bus: u64, dev: u64, function: u64, offset: u64, value: u16) voi
/// Claim the bridge, map the ECAM, hello the manager, then scan.
fn initialise(endpoint: ipc.Handle) bool {
_ = endpoint;
if (!device.claim(bridge_id)) {
std.log.info("unable to claim bridge device {d}", .{bridge_id});
device.claim(bridge_id) catch |e| {
std.log.info("unable to claim bridge device {d}: {s}", .{ bridge_id, @errorName(e) });
return false;
}
};
const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = logging.write("/system/drivers/pci-bus: out of memory\n");
return false;
@@ -141,7 +145,13 @@ fn scan() void {
}
}
}
std.log.info("{d} functions found", .{found});
// Reconcile: "found" alone reads as success even when most of the machine was
// refused. If the two disagree, say so at a level that survives a scrollback.
if (refused == 0) {
std.log.info("{d} functions found, all registered", .{found});
} else {
std.log.warn("{d} functions found, {d} REFUSED — {d} registered", .{ found, refused, found - refused });
}
}
/// Register one function under the bridge and report it to the manager. The
@@ -225,8 +235,12 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
// subsystem are read. Every discovered function is logged, matched or not.
logFunction(bus, dev, function, class_triple, descriptor.vendor, descriptor.device, descriptor.subsystem);
const registered = device.register(bridge_id, &descriptor) orelse {
std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function });
// Name the rule that refused. `ParentFull` and `TableFull` are different ceilings
// with different fixes, and `NotContained` is not a ceiling at all — it means the
// BAR escaped the bridge's own window (docs/fixed-bounds-audit.md).
const registered = device.register(bridge_id, &descriptor) catch |e| {
refused += 1;
std.log.warn("register refused for {d}:{d}.{d}: {s}", .{ bus, dev, function, @errorName(e) });
return;
};
// The registered device id is the packet's target — the manager's object
+15 -11
View File
@@ -114,10 +114,10 @@ pub fn main() void {
_ = logging.write("/system/drivers/ps2-bus: found PS/2 controller\n");
_ = logging.write("/system/drivers/ps2-bus: initializing controller\n");
if (!device.claim(controller_device_descriptor.id)) {
_ = logging.write("/system/drivers/ps2-bus: unable to claim controller \n");
device.claim(controller_device_descriptor.id) catch |e| {
std.log.warn("unable to claim controller: {s}", .{@errorName(e)});
return;
}
};
const controller = ps2.Controller.init(controller_device_descriptor) orelse {
_ = logging.write("/system/drivers/ps2-bus: controller is missing its IO ports\n");
@@ -255,14 +255,18 @@ pub fn main() void {
if (port_device_types[@intFromEnum(ps2.Port.two)] != null) {
if (ps2.findMouseDescriptor(buffer)) |descriptor| {
if (findInterruptResourceIndex(descriptor)) |auxiliary_index| {
if (device.claim(descriptor.id) and device.irqBind(descriptor.id, auxiliary_index, endpoint)) {
maybe_auxiliary_interrupt = .{
.device_id = descriptor.id,
.interrupt_index = auxiliary_index,
.gsi = descriptor.resources[auxiliary_index].start,
};
} else {
_ = logging.write("/system/drivers/ps2-bus: auxiliary irq_bind failed\n");
if (device.claim(descriptor.id)) |_| {
if (device.irqBind(descriptor.id, auxiliary_index, endpoint)) {
maybe_auxiliary_interrupt = .{
.device_id = descriptor.id,
.interrupt_index = auxiliary_index,
.gsi = descriptor.resources[auxiliary_index].start,
};
} else {
_ = logging.write("/system/drivers/ps2-bus: auxiliary irq_bind failed\n");
}
} else |e| {
std.log.warn("auxiliary claim failed: {s}", .{@errorName(e)});
}
}
}
+5 -5
View File
@@ -173,10 +173,10 @@ fn initialise(endpoint: ipc.Handle) bool {
if (!channel.bindPatiently("usb-transfer", endpoint))
_ = logging.write("/system/drivers/usb-xhci-bus: /protocol/usb-transfer is another controller's; serving mine unnamed\n");
if (!device.claim(controller_id)) {
std.log.info("unable to claim controller device {d}", .{controller_id});
device.claim(controller_id) catch |e| {
std.log.warn("unable to claim controller device {d}: {s}", .{ controller_id, @errorName(e) });
return false;
}
};
// Fetch our own descriptor back for the controller's resources.
const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch {
@@ -522,8 +522,8 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
const hid_text = std.fmt.bufPrint(&hid_buffer, "P{d}I{d}", .{ port, interface.number }) catch "";
descriptor.hid_len = hid_text.len;
@memcpy(descriptor.hid[0..hid_text.len], hid_text);
const registered = device.register(controller_id, &descriptor) orelse {
std.log.info("register refused for port {d} interface {d}", .{ port, interface.number });
const registered = device.register(controller_id, &descriptor) catch |e| {
std.log.warn("register refused for port {d} interface {d}: {s}", .{ port, interface.number, @errorName(e) });
return null;
};
+3 -3
View File
@@ -200,10 +200,10 @@ fn testPixel(index: u32) u32 {
fn initialise(endpoint: ipc.Handle) bool {
_ = endpoint;
if (!device.claim(device_id)) {
std.log.info("unable to claim device {d}", .{device_id});
device.claim(device_id) catch |e| {
std.log.info("unable to claim device {d}: {s}", .{ device_id, @errorName(e) });
return false;
}
};
var descriptors: [64]device.DeviceDescriptor = undefined;
const total = device.enumerate(&descriptors);