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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user