Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ and the gates that held them go.
- The nine workflows become three — `pr`, `nightly`, `publish`; then
`a5b25a75^:src/mergehealth.rs` goes, and the ABI-lands-alone
rule moves into the review prompt.

**Ruled** (owner, 2026-10-04), on #629, which builds the content-addressed
toolchain item, **"Design pass, then re-cut"**: "A short design pass against
today's main (it would also ease the one-shared-fork-branch ordering
problem), roasted, then rebuilt as a fresh PR; #629 is closed in favour of
it."
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ anything more is built on it.
`issues/kernel/the-supervisors-claim-of-a-pci-function-the-t14-lacks-holds-interrupts-off-for-3-8-ms.md`
need.

**Ruled** (owner, 2026-10-04), on when these steps start, **"Both in
parallel"**: "Start the trace diary's first steps now, beside latency step 1
(interrupts on in system calls). Step 1 is already measured by existing T14
rows. The tail delays are only worked on once the diary shows their cause."
The step he names is step 2 of
`issues/kernel/toyos-beats-linuxs-latency-on-the-t14.md`,
`issues/kernel/syscall-preemption-is-incidental.md`; that mapping is the
orchestrator's, not his.

Behind that judgement, and not before it: interrupt enter and exit records
with a noise reader, the profiling sample, and the censuses moved onto the
ring.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# A driver that reflashes its device escapes its isolation

A userland driver holds its device's registers, and through them many
devices take new firmware (NVMe's Firmware Image Download and Commit, for
one). Nothing in ToyOS refuses that or checks what a device runs. The IOMMU
confines a device by the requester ID its requests carry, so it confines a
reflashed one only as far as that ID is the device's own: ACS Source
Validation at a port refuses another bus's ID from below it, but nothing
stands between a root-complex-integrated endpoint, or one function of a
multi-function device, and an ID beside it. And the firmware outlives the
driver: the next holder, and the next boot's firmware before any IOMMU is
on, drive what the last holder wrote. That reach is the IOMMU design's
reading, not a measurement.

**Ruled** (owner, 2026-10-04, "Refuse external, record reflash"):
"External-port devices can't be claimed by userland drivers for now;
reflashing is a recorded weakness with an exit (firmware verification) to
design later."

Owner: the orchestrator, under `issues/kernel/the-iommu-refuses-nothing-yet.md`.

**Exit**: a design for verifying a device's firmware is put to the owner
and ruled, and built; a device whose firmware fails that verification is
refused its claim by name, and a test or T14 row reads the refusal and its
negative control.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
status: open
kind: defect
opened: 2026-10-04
---

# A unit in scalable or abort mode loses translation for its root-table switch

A unit firmware leaves translating with a root table in scalable or abort-DMA
mode (`RTADDR_REG.TTM` not `00`) and `CAP.ESRTPS` clear may not have its root
table pointer set while translation is on (VT-d Rev. 4.1 §6.6). Handing such a
unit over to this kernel's root table therefore turns translation off for that
one switch, and for that moment every function behind the unit reaches all of
memory. `issues/kernel/a-unit-left-translating-passes-dma-untranslated-while-programmed.md`
removes the gap where the switch may stay translating; on these units it may
not.

Evidence: the specification alone. No machine here has such a unit: every unit of the
T14 reports `CAP` bit 63 (`ESRTPS`), `ECAP` bit 43 (`SMTS`) and bit 52
(`ADMS`) clear.

**Ruled** (owner, 2026-10-04, "Accept and file"): "Allow the one-moment gap on
those machines, log it, and file it as a known weakness with a fix (older
protection registers) as its exit."

Owner: the orchestrator, under `issues/kernel/the-iommu-refuses-nothing-yet.md`.

**Exit**: on such a unit that reports `CAP.PLMR` (bit 5) and `CAP.PHMR`
(bit 6), `PMEN`'s protected memory regions (§11.4.8.1) cover all of memory
before translation goes off for the switch and are released only once it is
back on; a host test reads that order, and is red with the `PMEN` step
removed. It is a host test because no machine reaches the path: QEMU's unit
always reports `ESRTPS` and implements `PMEN_REG` as read-only zero, and the
T14's units report `ESRTPS` clear but support neither mode.

A unit that reports either bit clear has no fix under this exit: with either
clear, that region's base and limit are read-only (§11.4.8.1), so `PMEN`
cannot cover all of memory; the same section plans the protected memory
registers for deprecation, pointing new software at abort-DMA mode. Such a
unit keeps the gap, logged, and this file stays open for it once the `PMEN`
exit is met.

**Exit for a unit with `PLMR` or `PHMR` clear**: on such a unit no function
behind it reaches memory while translation is off for the switch; a host test
reads that, and is red with the fix removed.
59 changes: 59 additions & 0 deletions issues/kernel/the-iommu-refuses-nothing-yet.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,62 @@ What the harness cannot measure:
- **Cost.** The 2× bar is answerable only on hardware, in a same-session A/B;
so are cache-snooping walks, real access-control enforcement, and mid-DMA
function reset on a real device.

**Ruled** (owner, 2026-10-04), on the threat model, **"Refuse external,
record reflash"**: "External-port devices can't be claimed by userland
drivers for now; reflashing is a recorded weakness with an exit (firmware
verification) to design later." The weakness is
`issues/kernel/a-driver-that-reflashes-its-device-escapes-its-isolation.md`.
On the T14 the external ports are the Thunderbolt root ports `00:07.0` and
`00:07.2`, under units 1 and 2 (the orchestrator's reading of that machine's
`iommu:` lines).

**Ruled** (owner, 2026-10-04), on the default-deny root and the hand-over of
a unit firmware left translating:

- **"Display and USB only"**: "Allowed only for display and USB controllers
whose reserved region belongs to them alone; that memory is mapped into the
driver's own isolated space. Everything else with reserved memory is
refused." Reserved memory is a DMAR RMRR; on the T14 the one RMRR,
`0x9c000000..0xa07fffff`, names the iGPU `00:02.0` alone.
- **"Accept and file"**: "Allow the one-moment gap on those machines, log it,
and file it as a known weakness with a fix (older protection registers) as
its exit." The gap, as put to him: a unit firmware left translating in
scalable or abort-DMA mode, with `CAP.ESRTPS` clear, may not have its root
table switched under translation (VT-d Rev. 4.1 §6.6), so translation goes
off for that one switch. The older protection registers are `PMEN`'s
protected memory regions (§11.4.8.1). The weakness is
`issues/kernel/a-unit-in-scalable-or-abort-mode-loses-translation-for-its-root-table-switch.md`.
- **"Apply it at hand-over"**, on reserved memory: "From the hand-over on,
only display and USB controllers keep access to their reserved region; any
other device's access is refused. A device that firmware was still using
then faults, which is logged and the device is stopped; the machine keeps
running."
- **"Allow it"**, on the T14 row in which a test program claims the iGPU and
its reserved region is mapped into its domain: "One test boot with a blank
panel, only in that row; normal boots are unaffected."
- On the row that aims the T14's undriven NVMe `04:00.0` at memory nobody
gave it, asked whether to allow a single read-only Identify on its own
boot, he answered: "You can do with the t14 what you want." The grant goes
beyond the question asked.

Owner: the orchestrator.

**Exit**: a test or a T14 row reads each ruled refusal, each with its
negative control:

- the isolation-scope rule: a non-singleton scope is refused by name for a
function behind a PCIe switch, and admitted for a root-complex-integrated
function;
- a userland claim of a function below an external port (on the T14,
`00:07.0` or `00:07.2`) is refused by name;
- a claim of a function an RMRR names is refused unless it is a display or USB
controller that RMRR names alone, whose region is then mapped into its
driver's domain (the T14's iGPU row);
- from the hand-over on, a device that is neither a display nor a USB
controller faults on its own RMRR region, is logged and stopped, and the
machine keeps running; a display or USB controller whose RMRR also names
another device faults on that region the same way; and a display or USB
controller its RMRR names alone keeps that region. Each case has its own
negative control;
- a unit with the hand-over gap logs it.
22 changes: 15 additions & 7 deletions issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ free list (`mm/slub.c:2228,2290`), and `CONFIG_RANDOM_KMALLOC_CACHES` spreads
each size over 16 caches chosen by call site and a per-boot seed
(`include/linux/slab.h:340-341,398-401`).

**Exit**: a free-list link is stored encoded with a per-boot secret and
checked on every unlink, a size class's allocation order is drawn per boot,
and one size is spread over 16 caches by call site and a per-boot seed. Host
tests on the allocator: a corrupted link panics at the next unlink, and
storing it plain passes it and reds; two seeds give two allocation orders, and
a fixed order reds; 1000 call sites of one size use all 16 caches, and one
cache reds.
**Ruled** (owner, 2026-10-04, "Change the goal"): "Close the issue with the
allocator's first stage under that stronger, ToyOS-shaped goal instead of
copying Linux's SLUB features one by one." The goal, as put to him: no
allocator bookkeeping stored inside objects, every free checked, and data
kept apart from pointers. The first stage is the kernel's front of
`issues/kernel/toyos-has-its-own-allocator.md`, which owns this issue.

**Exit**: the kernel heap is that front, and its host tests hold the goal:
no free-list link or size is stored in memory an object occupies, so a write
past one object into a freed neighbour is followed by an allocation that
returns sound memory; a free of a pointer the heap did not hand out, and a
second free of one, each panic; and an allocation holding pointers never
shares a page with one holding only data. Each of those tests reds against
`dlmalloc`, or against the front with that property removed. The tests are
the orchestrator's reading of the goal, not his.
10 changes: 10 additions & 0 deletions issues/kernel/toyos-beats-linuxs-latency-on-the-t14.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ It samples: a window under 1 ms is seen only when a tick falls in it.
**Ruled** (owner, 2026-10-03): **"131 µs"**. ToyOS must beat Linux's worst
delay on the other seven CPUs, 131 µs, not cpu4's one-off 2.9 ms event.

**Ruled** (owner, 2026-10-04), on the order of the trace work, **"Both in
parallel"**: "Start the trace diary's first steps now, beside latency step 1
(interrupts on in system calls). Step 1 is already measured by existing T14
rows. The tail delays are only worked on once the diary shows their cause."
The step his text names is step 2 here,
`issues/kernel/syscall-preemption-is-incidental.md`, whose exit the
`mask_windows` row reads; the diary is
`issues/diagnostics/nothing-in-the-machine-can-read-the-trace-ring.md`. That
mapping is the orchestrator's, not his.

**Exit**: each step's exit is met, in the file the step names, and on the T14,
with every CPU spawning a program that exits at once, as under Linux's
131 µs reading, the longest lateness of a 1 kHz timer's interrupt on each CPU reads under 131 µs,
Expand Down
17 changes: 15 additions & 2 deletions issues/kernel/toyos-has-its-own-allocator.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,24 @@ scans the page bitmap and zeroes the whole 2 MiB page before it returns, so
every CPU that allocates from the heap meanwhile spins on its lock. How long
that takes on the T14 is unmeasured.

**Ruled** (owner, 2026-10-04, "Yes, that bar"): "Within 10% of mimalloc
for time and peak memory on a pinned benchmark set; faster than dlmalloc
everywhere. The benchmarks get a portable Rust runner so they run on ToyOS
itself."

The kernel's front closes
`issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md`, under the
goal the owner chose there (2026-10-04): no allocator bookkeeping inside
objects, every free checked, data apart from pointers.

Owner: the orchestrator.

**Exit**: no heap growth allocates or zeroes a 2 MiB page inside the kernel
heap's lock; the kernel allocates through ToyOS's allocator, and `dlmalloc`,
`libc`, `windows-sys` and `windows-link` are gone from `kernel/Cargo.toml` and
`kernel/Cargo.lock`, with `build::tests::the_kernel_resolves_no_libc_for_either_target`
(`src/build.rs`) deleted in the same pull request; and ToyOS's std allocates
through the std front, measured against `dlmalloc` before it is swapped in.
(`src/build.rs`) deleted in the same pull request; ToyOS's std allocates
through the std front, measured against `dlmalloc` before it is swapped in;
and the ruled bar holds, read by the benchmarks' portable Rust runner, which
runs on ToyOS: the allocator within 10% of mimalloc for time and peak memory
on the pinned benchmark set, and faster than `dlmalloc` everywhere.
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,23 @@ writes its own, and the battery comes first (his direction of `0ee814f5a`).
- **"Extracts only"**: the repository holds small decoded extracts of the
T14's ACPI tables; the whole tables stay out of the tree, read only by a
check run outside it.
- **"Local only; decode the 186 bytes"** (2026-10-04): "Full tables stay out
of the repo (a copy you hold); the 186-byte fixture on main is replaced by
decoded values, as 'Extracts only' says." The T14's root-bridge list is
`t14_root_bridge` in `toyos-acpi/tests/common/mod.rs`.
- **"full clean room write with the spec"**: the AML interpreter is written
from the ACPI specification.

**Ruled** (owner, 2026-10-04), on the interpreter:

- **"Like Windows, not Linux"**, on `_OSI`: "Yes to every published Windows
version string, no to 'Linux' and 'FreeBSD', as Linux itself answers. The
T14 then runs the path it was tested on: Modern Standby, CPU performance
tables, 101-step backlight, thermal profiles, all devices present."
- **"Yes, one path"**, on power-off: "Power-off always goes through the ACPI
server; the kernel's power-off table reader is deleted. If the server is
broken, power-off fails loudly in every test."

The orchestrator's reading of the clean-room ruling, not his: uACPI and
ACPICA are run only as black-box oracles, and whoever writes the interpreter
never reads their source.
Expand All @@ -67,3 +81,9 @@ power-off path (`SYS_SHUTDOWN`), and the boot's log records the press and that
stop, and each EC query number once with its count: a T14 row reads them there.
A second T14 row kills the server and reads `SCI_EN` clear in `PM1_CNT`
afterwards, the kernel having written `ACPI_DISABLE` to `SMI_CMD`.

**Stage: power-off through the server** (the orchestrator's placement of "Yes,
one path"). The ACPI server evaluates `\_S5` and powers the machine off.
Blocked on the interpreter's evaluation of `\_S5`. **Exit**: the kernel's `\_S5_` reader, `toyos-acpi/src/dsdt.rs`, and its caller
in `kernel/src/arch/x86_64/power.rs` are deleted, and a test that powers off
through a broken server is red.
6 changes: 0 additions & 6 deletions src/licence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,6 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[
"ours: OVMF's answer on a q35 guest, read off that boot's own loader log",
Terms::Spdx("MIT OR Apache-2.0"),
),
(
"toyos-acpi/fixtures/thinkpad-t14/root-bridge-0.bin",
"a734078ed9ca3971ce804fd9ecc97b7794f816058f9ae17e47e0d2bcb63af0f3",
"ours: the T14's answer, read off that boot's own loader log on the stick",
Terms::Spdx("MIT OR Apache-2.0"),
),
// A bcachefs volume upstream's own tools wrote, gzipped. The bytes inside
// it are this repository's test material; `NOTICE` carries the raw digest,
// the commands, and the fsck that called it clean.
Expand Down
11 changes: 0 additions & 11 deletions toyos-acpi/fixtures/thinkpad-t14/SOURCE

This file was deleted.

Binary file removed toyos-acpi/fixtures/thinkpad-t14/root-bridge-0.bin
Binary file not shown.
1 change: 0 additions & 1 deletion toyos-acpi/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//!
//! Input is firmware-supplied and untrusted: no path panics, the walk
//! terminates on every input, and every refusal is a [`ResourceError`].
//! `tests/corpus.rs` holds that claim over both firmwares' committed bytes.

use crate::Phys;
use toyos_abi::boot::RootBridgeWindow;
Expand Down
38 changes: 38 additions & 0 deletions toyos-acpi/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,41 @@ pub fn madt(entries: &[u8]) -> Vec<u8> {
body.extend_from_slice(entries);
sdt(b"APIC", 5, &body)
}

/// ACPI 6.5 Table 6.44's resource types.
pub const MEMORY: u8 = 0;
pub const IO: u8 = 1;
pub const BUS: u8 = 2;

/// One QWORD Address Space Descriptor (ACPI 6.5 §6.4.3.5.1), field by field,
/// its General and Type Specific Flags clear.
pub fn qword_descriptor(kind: u8, granularity: u64, min: u64, max: u64, translation: u64, length: u64) -> Vec<u8> {
let mut d = vec![0x8A, 0x2B, 0x00, kind, 0x00, 0x00];
for field in [granularity, min, max, translation, length] {
d.extend_from_slice(&field.to_le_bytes());
}
d
}

/// `descriptors`, then the End Tag over its checksum byte (ACPI 6.5 §6.4.2.9).
pub fn resource_list(descriptors: &[Vec<u8>]) -> Vec<u8> {
let mut bytes = descriptors.concat();
bytes.extend_from_slice(&[0x79, 0x00]);
bytes
}

/// What OVMF's firmware answered for its root bridge 0 through
/// `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL::Configuration`, as captured.
pub const OVMF_ROOT_BRIDGE: &[u8] = include_bytes!("../../fixtures/ovmf-pure-efi/root-bridge-0.bin");

/// What the ThinkPad T14's firmware answers for its root bridge 0 through
/// `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL::Configuration`, decoded: its I/O range,
/// two memory windows and its bus range.
pub fn t14_root_bridge() -> Vec<u8> {
resource_list(&[
qword_descriptor(IO, 0, 0x3000, 0x3fff, 0, 0x1000),
qword_descriptor(MEMORY, 0x20, 0xa200_0000, 0xbcff_ffff, 0, 0x1b00_0000),
qword_descriptor(MEMORY, 0x40, 0x40_0000_0000, 0x60_3dbf_ffff, 0, 0x20_3dc0_0000),
qword_descriptor(BUS, 0, 0, 0x79, 0, 0x7a),
])
}
8 changes: 2 additions & 6 deletions toyos-acpi/tests/corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

mod common;

use common::{declare_len, entry, madt, rsdp, sdt, xsdt, Machine};
use common::{declare_len, entry, madt, rsdp, sdt, t14_root_bridge, xsdt, Machine, OVMF_ROOT_BRIDGE};
use toyos_abi::boot::RootBridgeWindow;
use toyos_acpi::{
dsdt_address, ecam_base, find_table, hpet_base, iapc_boot_arch, madt_entries, memory_windows,
Expand Down Expand Up @@ -566,10 +566,6 @@ fn a_fadt_that_ends_before_arm_boot_arch_is_short() {

/// Where the two firmwares' descriptor lists sit for the sweep below.
const ROOT_BRIDGE_AT: u64 = 0x4_0000;
const ROOT_BRIDGES: &[(&str, &[u8])] = &[
("ovmf", include_bytes!("../fixtures/ovmf-pure-efi/root-bridge-0.bin")),
("thinkpad-t14", include_bytes!("../fixtures/thinkpad-t14/root-bridge-0.bin")),
];

/// **No panic and no unbounded walk, over every byte of both firmwares' real
/// descriptor lists.** Each byte takes each of its 255 other values in turn and
Expand All @@ -579,7 +575,7 @@ const ROOT_BRIDGES: &[(&str, &[u8])] = &[
fn no_single_byte_mutation_of_a_firmwares_descriptor_list_panics_or_runs_away() {
let mut mutations = 0u64;
let mut refused = 0u64;
for (which, original) in ROOT_BRIDGES {
for (which, original) in [("ovmf", OVMF_ROOT_BRIDGE.to_vec()), ("thinkpad-t14", t14_root_bridge())] {
for offset in 0..original.len() {
for value in 0..=255u8 {
if original[offset] == value {
Expand Down
Loading
Loading