diff --git a/issues/build/no-gate-decodes-kernel-elfs-instructions.md b/issues/build/no-gate-decodes-kernel-elfs-instructions.md new file mode 100644 index 00000000000..80f3d6547e2 --- /dev/null +++ b/issues/build/no-gate-decodes-kernel-elfs-instructions.md @@ -0,0 +1,33 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# No gate decodes `kernel.elf`'s instructions + +Five stages each owe a gate over `kernel.elf`'s instructions: + +- `issues/kernel/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md` +- `issues/kernel/indirect-branches-and-returns-run-without-thunks.md` +- `issues/kernel/kernel-functions-return-with-their-used-registers-intact.md` +- `issues/kernel/the-kernel-runs-without-indirect-branch-tracking.md` +- `issues/kernel/kernel-forward-copies-and-fills-are-one-rep-movsb-or-stosb-on-every-cpu.md` + +The five share one x86-64 decoder, in Rust in the tree and run by the host +gate, with no external disassembler: it lands before any of them, and each adds +its own check over it rather than a decoder of its own. It takes its functions +from `toyos_symbols::locate` over `toyos_elf` (`src/build.rs:1575-1598`), not +from a second ELF reader. `global_asm!` emits no `.type` or `.size`, so code +such as the AP trampoline (`kernel/src/arch/x86_64/smp.rs:396`), in `.text` +with a `jmp far [mem]` and a `retf`, is covered by no sized function. + +**Exit**: the decoder walks every function `kernel.elf`'s `.symtab` names, from +its address to its size, and a walk that decodes a byte it does not know or +ends anywhere but the function's end is refused; so is any byte of an +executable section that no walked function covers, apart from a region named by +symbol and decoded in its own mode, as the trampoline's 16-, 32- and 64-bit +code is. **Mutation**, each red over the shipped kernel: an instruction length +misread by one, a SIB byte dropped after a ModRM r/m of 100b; +`global_asm!("1: jmp rax")` in `.text`. **Oracle**: SDM Vol. 2's opcode maps, +Appendix A. diff --git a/issues/hardware/blockd-drives-nvme-apst.md b/issues/hardware/blockd-drives-nvme-apst.md new file mode 100644 index 00000000000..ab3ef5d75dd --- /dev/null +++ b/issues/hardware/blockd-drives-nvme-apst.md @@ -0,0 +1,18 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# blockd drives NVMe APST + +After blockd drives the T14's drive, the blockd step of +`issues/kernel/the-kernel-is-small-interrupts-post-and-threads-wait.md`. As +Linux at `Ubuntu-6.8.0-142.142`, APST stays within +`default_ps_max_latency_us` and honours `NVME_QUIRK_NO_APST` and +`NVME_QUIRK_NO_DEEPEST_PS` (`drivers/nvme/host/core.c:63,2495-2598`). + +**Exit**: host tests of the APST table; on the T14, the one proving machine +with a physical NVMe drive, the block figures and the idle minute with the +drive idle. **Mutation**: a table that admits a state past the bound. +**Oracle**: Linux's `nvme_configure_apst` over the T14's `id-ctrl`. diff --git a/issues/hardware/features-no-proving-machine-is-known-to-offer.md b/issues/hardware/features-no-proving-machine-is-known-to-offer.md new file mode 100644 index 00000000000..c771b54bc67 --- /dev/null +++ b/issues/hardware/features-no-proving-machine-is-known-to-offer.md @@ -0,0 +1,47 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Features no proving machine is known to offer + +No stage of +`issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md` +or `issues/kernel/toyos-uses-what-modern-hardware-offers-for-speed.md` takes +these, since neither the T14 nor a nightly EPYC KVM guest is known to +enumerate one. It is blocked on a proving machine that does; its exit is every +row a stage issue under the track it serves, or a `rejected` issue. Intel's +parts are named by the SDM 325462-093US, Vol. 1 Table 5-2 ("SDM"), or by the +ISE reference 319433-062, Table 1-2 ("ISE"), neither of which places any of +these in Tiger Lake. AMD's and Arm's are read in Linux at tag `v7.2`; the AMD +parts that carry each were not verified against AMD's documents, and the Arm +cores were not verified against Arm's. The Arm rows belong to +`issues/kernel/toyos-runs-on-arm64.md`'s CPU-state declaration. + +| Feature | Hardware | +|---|---| +| FRED and LKGS | Panther Lake, Clearwater Forest, Diamond Rapids (ISE) | +| LASS | Sierra Forest, Atom P6900, Core Ultra 200H (SDM) | +| LAM | the parts LASS names (SDM) | +| PKS | Alder Lake, Sapphire Rapids, Sierra Forest (SDM) | +| User interrupts (UINTR) | Sapphire Rapids, Sierra Forest, Atom P6900, Core Ultra 200H (SDM) | +| UMONITOR, UMWAIT, TPAUSE | Tremont, Alder Lake, Sapphire Rapids (SDM) | +| Fast zero-length `rep movsb`, fast short `rep stosb` | Alder Lake, Sapphire Rapids (SDM) | +| AMX | Sapphire Rapids (SDM) | +| AVX10.1, AVX10.2 | AVX10.1: Granite Rapids, Nova Lake; AVX10.2: Diamond Rapids, Nova Lake (ISE) | +| APX | Diamond Rapids, Nova Lake (ISE) | +| Thread Director, HRESET | Alder Lake (SDM) | +| TDX | Emerald Rapids (ISE) | +| Total Storage Encryption | Panther Lake (ISE) | +| TME | 11th-generation Core lines that set CPUID.(7,0):ECX bit 13, which varies by line (datasheet 631121-012 §1.3); the T14 reads ECX 0x18c05fde, bit 13 clear | +| INVLPGB, TLBSYNC | AMD, CPUID 0x80000008:EBX bit 3 (`arch/x86/include/asm/cpufeatures.h:331,335`); whether a nightly EPYC guest sees it waits on its runner's CPUID capture | +| SEV-SNP | AMD, CPUID 0x8000001F:EAX bit 4 (`cpufeatures.h:448,453`) | +| Shadow stack on AMD | AMD; unverified: that AMD enumerates it through the bit Linux reads for Intel, CPUID.(7,0):ECX bit 7 (`cpufeatures.h:390,397`), and which parts set it; whether a nightly EPYC guest sees it waits on its runner's CPUID capture | +| MTE | Armv8.5 (`arch/arm64/Kconfig:2150-2170`) | +| Pointer authentication | Armv8.3 (`arch/arm64/Kconfig:1967-1974`) | +| BTI | Armv8.5 (`arch/arm64/Kconfig:2087-2093`) | +| GCS | Armv9.4 (`arch/arm64/Kconfig:2236-2254`) | +| POE | Armv8.9 (`arch/arm64/Kconfig:2198-2213`) | +| SME | Arm (`arch/arm64/Kconfig:2311-2319`) | +| NVMe host memory buffer | an NVMe drive whose `id-ctrl` reads HMPRE non-zero; the T14's has not been read, and blockd builds the buffer only once a proving machine's does | diff --git a/issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md b/issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md new file mode 100644 index 00000000000..67f1be92423 --- /dev/null +++ b/issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md @@ -0,0 +1,24 @@ +--- +status: assigned +kind: tooling +opened: 2026-09-29 +--- + +# Linux's readings of the T14 and the TCG model are not committed + +The floor of +`issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md` +is tag `Ubuntu-6.8.0-142.142` (53e5d07aac028a1523ab0b115f079d6d1bc831ef) of +`https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/noble`, +config sha256 3b8533dd9d235ca634ac58f82c5ce1ee35f12ef620693e17033184d2c9ca5890, +and what it reads on the T14 and on the PR gate's TCG model are the fixtures +of `issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md`. +Pull request #601 holds it. Ubuntu leaves the T14 only after this issue and +`issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md` +close. + +**Exit**: both captures committed, and no Linux image; before the wipe the +T14's adds CPUID 5, 0x19 and 0x80000001, MSR 0xCF, the split-lock line, and +the config's `X86_KERNEL_IBT` and `X86_INTEL_MEMORY_PROTECTION_KEYS`. +**Mutation**: a config hash off by one byte is refused. **Oracle**: that +Linux. diff --git a/issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md b/issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md new file mode 100644 index 00000000000..9e57c354f45 --- /dev/null +++ b/issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md @@ -0,0 +1,23 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# No program measures ToyOS against Linux on one machine + +One Rust program, built for `x86_64-unknown-linux-gnu` and for ToyOS, is to +measure memcpy from 64 B to 64 MiB; AES-128-GCM, ChaCha20-Poly1305 and SHA-256 +at sshd's RustCrypto versions; pipe round trips, and throughput at 64 B to 64 +KiB; munmaps per second of a page a sibling on another CPU touched; lateness +past a 1 ms timer; NVMe reads, read-only; an idle minute's package energy; and +the T14's PCI, USB, NVMe and cpuidle identity. It runs on the T14 under its +Ubuntu before the wipe and under the shipped ToyOS kernel. A munmap pair's two +sides each read their x2APIC ID from CPUID.0BH:EDX before and after the timed +window, and a pure function over the four readings refuses the figure when the +two sides' IDs match or either side's changed across the window. + +**Exit**: the outputs committed as fixtures, and that function host-tested. +**Mutation**, each red: a pair on one CPU passes; only the readings before the +window compared, so a sibling that moved onto the initiator's CPU inside it +passes. **Oracle**: Linux. diff --git a/issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md b/issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md index 5458bcb8ae9..2faa9d1d82d 100644 --- a/issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md +++ b/issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md @@ -15,9 +15,7 @@ under the T14's `CONFIG_INTEL_IOMMU_DEFAULT_ON=y` and `CONFIG_IOMMU_DEFAULT_DMA_LAZY=y`, turns DMA remapping on (`drivers/iommu/intel/iommu.c:227`) and gives every device a translated default domain (`drivers/iommu/iommu.c:197-210`), which maps only what its -driver maps. A row of the -hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +driver maps. **Exit**: a function's DMA reaches only what its driver mapped and the reserved ranges firmware names for it; a `boot-actuators` arm that points an unclaimed diff --git a/issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md b/issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md new file mode 100644 index 00000000000..532e0f201ed --- /dev/null +++ b/issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md @@ -0,0 +1,21 @@ +--- +status: assigned +kind: track +opened: 2026-09-29 +--- + +# A pure function decides a CPU's speculation mitigations as Linux does + +From a CPU's facts to the line Linux at `Ubuntu-6.8.0-142.142` prints in each +`/sys/devices/system/cpu/vulnerabilities/*` file and the mitigation it +selects, built by the kernel and by a host test. Pull request #602 holds it. + +**Exit**: each committed fixture's facts give its lines: the T14's and the TCG +model's from +`issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md`, +and each nightly EPYC guest's once its runner is captured. **Mutation**: `GDS` +deleted from `cpu_vuln_blacklist`'s TIGERLAKE_L row reds the T14's fixture, +and `SRSO` deleted from its family 0x19 row reds a family-0x19 EPYC guest's. +**Oracle**: those lines, and Linux's `cpu_vuln_whitelist` and +`cpu_vuln_blacklist` row for row +(`arch/x86/kernel/cpu/common.c:1182-1244,1284-1344`). diff --git a/issues/kernel/a-split-lock-goes-unnoticed.md b/issues/kernel/a-split-lock-goes-unnoticed.md new file mode 100644 index 00000000000..8af3a7dabf0 --- /dev/null +++ b/issues/kernel/a-split-lock-goes-unnoticed.md @@ -0,0 +1,17 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# A split lock goes unnoticed + +The kernel never sets `MSR_TEST_CTRL` (0x33) bit 29, so a user `lock` +instruction across a cache line locks the bus for every CPU and nothing +reports it. Linux at `Ubuntu-6.8.0-142.142` detects it by default and warns +(`sld_warn`, `arch/x86/kernel/cpu/intel.c:1083`); ToyOS ends the process by +name instead. + +**Exit**: on the T14, the one proving machine known to enumerate split-lock +detection, a `lock add` across a line is ended by name. **Mutation**: bit 29 +clear. **Oracle**: the T14's CPU. diff --git a/issues/kernel/a-threads-kernel-stack-has-no-guard-page.md b/issues/kernel/a-threads-kernel-stack-has-no-guard-page.md index 1394b4b573b..53a04684dfc 100644 --- a/issues/kernel/a-threads-kernel-stack-has-no-guard-page.md +++ b/issues/kernel/a-threads-kernel-stack-has-no-guard-page.md @@ -15,8 +15,7 @@ write. The per-CPU idle stacks already have an unmapped page below them (`kernel/src/arch/x86_64/percpu.rs:482`). Linux at `Ubuntu-6.8.0-142.142`, under the T14's `CONFIG_VMAP_STACK=y`, maps every task stack in vmalloc space between guard pages (`kernel/fork.c:314-318`, `arch/Kconfig:1327-1336`), so -the first write past the end faults. A row of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +the first write past the end faults. **Exit**: every thread's kernel stack has an unmapped page below it; a `boot-actuators` arm that recurses a thread past its stack gets a double fault diff --git a/issues/kernel/a-tlb-shootdown-reaches-only-the-cpus-that-ran-the-space.md b/issues/kernel/a-tlb-shootdown-reaches-only-the-cpus-that-ran-the-space.md new file mode 100644 index 00000000000..8c19c4414d9 --- /dev/null +++ b/issues/kernel/a-tlb-shootdown-reaches-only-the-cpus-that-ran-the-space.md @@ -0,0 +1,29 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# A TLB shootdown reaches only the CPUs that ran the space + +A shootdown blocks on every other CPU (`kernel/src/mm/unmapped.rs:5`), +whether or not it ever ran the address space. It waits on +`issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md`. A CPU that +switches away keeps the space's entries: under PCID every CR3 load sets +NOFLUSH (`kernel/src/arch/x86_64/paging.rs:295-306`), so a CPU leaves the +target set only when those entries are gone, not when it stops running the +space. The TCG model has no PCID (`src/arch.rs:169`), so only a machine with +PCID can show a switched-out CPU still holding them. + +**Exit**: on every proving machine and under TCG, a sibling pinned on another +CPU touches a page the initiator unmaps and then faults; on each proving +machine with PCID, the sibling touches the page and blocks off its pinned CPU, +the initiator unmaps it while the sibling is blocked and a thread of another +space runs there, and the sibling faults on touching it again after it wakes +there; cases in `kernel-loom/tests/tlb_shootdown.rs` switch a CPU into the +space, and out of it, while the initiator reads the target set; the T14's +munmap figure. **Mutation**, each red: a target set that omits a CPU the space +ran on; a CPU that joins the set after loading CR3; a CPU cleared from the set +when it switches away from the space, its red recorded over repeated runs, +since it rests on the TLB keeping the entry across the block. **Oracle**: loom, +and each proving machine's TLB. diff --git a/issues/kernel/each-boot-prints-the-vulnerabilities-lines-linux-prints.md b/issues/kernel/each-boot-prints-the-vulnerabilities-lines-linux-prints.md new file mode 100644 index 00000000000..8c04912bfe1 --- /dev/null +++ b/issues/kernel/each-boot-prints-the-vulnerabilities-lines-linux-prints.md @@ -0,0 +1,18 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Each boot prints the vulnerabilities lines Linux prints + +**Exit**: each proving machine's ToyOS boot prints, for each vulnerabilities +file, the line Linux at `Ubuntu-6.8.0-142.142` printed on that machine: the +T14's from +`issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md`, +each EPYC guest's from its runner's capture. It omits only clauses about what +ToyOS does not run: `spectre_v1`'s swapgs barriers while it runs no `swapgs`, +the KVM and VM-exit clauses since it runs no guest, and `IBRS_FW` since the +kernel calls no firmware; `spec_store_bypass` reads "Mitigation: Speculative +Store Bypass disabled per program". **Mutation**: `ARCH_CAPABILITIES` read as +0 reds the T14. **Oracle**: Linux's lines. diff --git a/issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md b/issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md index bba5fe80b30..e6d86f01af0 100644 --- a/issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md +++ b/issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md @@ -15,8 +15,7 @@ Linux at `Ubuntu-6.8.0-142.142`, under the T14's offset at every syscall entry (`arch/x86/entry/common.c:73`, `include/linux/randomize_kstack.h:40,50`), drawn on the exit before it (`arch/x86/include/asm/entry-common.h:85`), 7 bits after alignment on x86-64 -(`entry-common.h:76-83`). A row of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +(`entry-common.h:76-83`). **Exit**: every syscall's handler runs below an offset drawn per syscall, of at least 7 bits; a `boot-actuators` probe records one handler frame's address diff --git a/issues/kernel/fsgsbase-is-measured-then-taken-or-rejected.md b/issues/kernel/fsgsbase-is-measured-then-taken-or-rejected.md new file mode 100644 index 00000000000..8715ca63e28 --- /dev/null +++ b/issues/kernel/fsgsbase-is-measured-then-taken-or-rejected.md @@ -0,0 +1,21 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# FSGSBASE is measured, then taken or rejected + +`CR4.FSGSBASE` is forbidden (`kernel/src/arch/x86_64/control_regs.rs:62-75`): +GS always holds the kernel's per-CPU base and the kernel runs no `swapgs`. +Taking it costs a `swapgs` entry and the swapgs barriers of +`issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md`. + +**Exit**: the T14's pipe round trips with and without it, a loss becoming a +`rejected` issue with both figures. Taken: on every proving machine and under +TCG, a `boot-actuators` arm enters vector 2 at CPL 0 while the user GS base is +live, between entry and its `swapgs` or after the exit path's, and the handler +finds the kernel's per-CPU data; `CR4_FORBIDDEN`, the `user-writable-gsbase` +kernel (`src/build.rs:1370`) and the test `gsbase_locked` are deleted. +**Mutation**: an IST entry that decides `swapgs` from the saved CS alone. +**Oracle**: Linux's `paranoid_entry`. diff --git a/issues/kernel/idle-cpus-enter-the-c-states-cst-names.md b/issues/kernel/idle-cpus-enter-the-c-states-cst-names.md new file mode 100644 index 00000000000..ea53ca33f83 --- /dev/null +++ b/issues/kernel/idle-cpus-enter-the-c-states-cst-names.md @@ -0,0 +1,16 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Idle CPUs enter the C-states `_CST` names + +The kernel runs no `mwait` and reads no `_CST`. The states come from `_CST`, +as Linux takes the T14's, after the AML interpreter that +`issues/kernel/the-scheduler-and-the-clocks-never-talk.md` waits on. + +**Exit**: on the T14, since a KVM guest's idle is its host's: the idle +minute's package energy, and timer lateness no worse than `hlt`'s by more than +the chosen state's exit latency. **Mutation**: always the deepest state. +**Oracle**: Linux's figures. diff --git a/issues/kernel/indirect-branches-and-returns-run-without-thunks.md b/issues/kernel/indirect-branches-and-returns-run-without-thunks.md new file mode 100644 index 00000000000..45b3f614171 --- /dev/null +++ b/issues/kernel/indirect-branches-and-returns-run-without-thunks.md @@ -0,0 +1,30 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# Indirect branches and returns run without thunks + +Nothing in the kernel's build asks for a retpoline, a return thunk or +straight-line-speculation padding. Linux at `Ubuntu-6.8.0-142.142` builds with +`MITIGATION_RETPOLINE`, `MITIGATION_RETHUNK`, `MITIGATION_ITS` and `SLS` +(`debian.master/config/annotations:8043-8045,12190`) and installs the return +thunk its decision selects: ITS's on the T14; on AMD, retbleed's untrained +return (`arch/x86/kernel/cpu/bugs.c:1125`) or SRSO's safe RET, in its alias +form on family 0x19 (`bugs.c:2726-2732`). + +**Exit**: a gate over `kernel.elf`, on the decoder of +`issues/build/no-gate-decodes-kernel-elfs-instructions.md`, finds no raw +indirect branch or `ret` outside the thunks and entry, an `int3` after each, and every placement Linux's linker +script asserts (`arch/x86/kernel/vmlinux.lds.S:510-539`): +`retbleed_return_thunk` and `srso_safe_ret` at a line start, the SRSO alias +pair's addresses differing in exactly bits 2, 8, 14 and 20, and each ITS +thunk's branch in its line's upper half. Each proving machine's boot installs +the thunks the decision selects for it: ITS's on the T14, SRSO's alias on a +family-0x19 EPYC guest. No proving machine is family 0x17, so the untrained +return is proven by the gate and the decision's host test alone. +**Mutation**, each red: an ITS thunk at a line start; `retbleed_return_thunk` +one byte past its line start; the SRSO alias one cache line off its pair; an +`int3` gone; ITS's thunk installed on every CPU. **Oracle**: that linker +script, and `Documentation/admin-guide/hw-vuln/indirect-target-selection.rst`. diff --git a/issues/kernel/kernel-forward-copies-and-fills-are-one-rep-movsb-or-stosb-on-every-cpu.md b/issues/kernel/kernel-forward-copies-and-fills-are-one-rep-movsb-or-stosb-on-every-cpu.md new file mode 100644 index 00000000000..ae7e1d78368 --- /dev/null +++ b/issues/kernel/kernel-forward-copies-and-fills-are-one-rep-movsb-or-stosb-on-every-cpu.md @@ -0,0 +1,35 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Kernel forward copies and fills are one `rep movsb` or `rep stosb` on every CPU + +The kernel's `memcpy`, `memset` and `memmove` are `compiler_builtins`', which +frame `rep movsq` or `rep stosq` with two byte strings, and take one `rep movsb` +or `rep stosb` only when built with the `ermsb` target feature +(`rust/library/compiler-builtins/compiler-builtins/src/mem/x86_64.rs:15-17,23-58,88-126`); +`memmove`'s backward branch, `copy_backward` (`:62-85`), keeps `rep movsq` +either way. The kernel links the sysroot's `compiler_builtins`, which no kernel +build flag recompiles: the feature is `rustflags = ["-Ctarget-feature=+ermsb"]` +under `[target.x86_64-unknown-none]` in the std build's `bootstrap.toml` +(`std_config`, `src/sysroot.rs:522`; bootstrap's `core/config/toml/target.rs:41`), +and it moves the sysroot key (`RECIPE`, `src/sysroot.rs:65`), which reaches +CI's installed toolchain only once the release tag's `trees()` +(`src/release.rs:25-31`) hashes `src/sysroot.rs`, the work of +`issues/build/the-release-tag-hashes-none-of-the-build-system-that-builds-the-toolchain.md`, +which lands first. One path on every +CPU: `rep movsb` is correct without ERMS, CPUID.(7,0):EBX bit 9, and no CPU is +refused. Zen 2 lacks ERMS (a Ryzen 9 PRO 3900, family 0x17, reads EBX +0x219C91A9: InstLatx64 ddff8a92, +`AuthenticAMD/AuthenticAMD0870F10_K17_Matisse_CPUID.txt:52`), and no proving +machine is family 0x17. + +**Exit**: a gate over `kernel.elf`, on the decoder of +`issues/build/no-gate-decodes-kernel-elfs-instructions.md`, finds `memcpy`, +`memset` and `memmove`'s forward branch each one `rep movsb` or `rep stosb`, +with no `rep movsq` or `rep stosq`; the T14's pipe figures; and Zen 2's cost +recorded, as a family-0x17 figure against the build without `ermsb` or as a +`tooling` issue that owes it. **Mutation**: the build without `ermsb` reds the +gate. **Oracle**: Linux's pipe figures. diff --git a/issues/kernel/kernel-functions-return-with-their-used-registers-intact.md b/issues/kernel/kernel-functions-return-with-their-used-registers-intact.md index 6ea64923aad..ad26a54bff8 100644 --- a/issues/kernel/kernel-functions-return-with-their-used-registers-intact.md +++ b/issues/kernel/kernel-functions-return-with-their-used-registers-intact.md @@ -14,11 +14,11 @@ builds with `-fzero-call-used-regs=used-gpr` (`Makefile:891-894`). LLVM acts on the function attribute `zero-call-used-regs` (`llvm/lib/CodeGen/PrologEpilogInserter.cpp:1212`), and the rustc fork at 1b236638 has no option that sets it -(`compiler/rustc_session/src/options.rs`). A row of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +(`compiler/rustc_session/src/options.rs`). **Exit**: the kernel, `core` and `alloc` build under a rustc option that sets `zero-call-used-regs=used-gpr` on every function, carried in the fork to -upstream quality; a gate over `kernel.elf` finds each function's used +upstream quality; a gate over `kernel.elf`, on the decoder of +`issues/build/no-gate-decodes-kernel-elfs-instructions.md`, finds each function's used call-clobbered general registers zeroed before its return thunk, and a build without the option reds it. diff --git a/issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md b/issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md index 42b790c801d..fe1548068ef 100644 --- a/issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md +++ b/issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md @@ -14,8 +14,7 @@ executed at CPL 0. Linux at `Ubuntu-6.8.0-142.142`, under the T14's `CONFIG_STRICT_KERNEL_RWX=y`, makes text and rodata read-only and everything else non-executable (`arch/x86/mm/init_64.c:1402-1447`), and under `CONFIG_DEBUG_WX=y` walks the tables for a writable and executable mapping at -boot (`arch/x86/mm/dump_pagetables.c:434`). Two rows of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +boot (`arch/x86/mm/dump_pagetables.c:434`). **Exit**: kernel text is mapped read-only and executable, rodata read-only and `NX`, and every other kernel mapping `NX`; each boot walks the kernel tables diff --git a/issues/kernel/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md b/issues/kernel/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md new file mode 100644 index 00000000000..8e716f8e4b5 --- /dev/null +++ b/issues/kernel/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md @@ -0,0 +1,27 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# No entry or switch clears the BHB, fills the RSB or issues an IBPB + +Nothing in `kernel/src` clears branch history, fills the return stack buffer +or issues an indirect branch prediction barrier. Linux at +`Ubuntu-6.8.0-142.142` runs `clear_bhb_loop` on entry where its decision +selects the BHI loop (`arch/x86/entry/entry_64.S:1534`), +`__FILL_RETURN_BUFFER` on a switch where it selects RSB filling +(`arch/x86/include/asm/nospec-branch.h:151`), and an IBPB on a switch between +address spaces one of which asked for it (`arch/x86/mm/tlb.c:384`). In ToyOS +the ask is the flag of +`issues/kernel/no-program-runs-with-speculative-store-bypass-disabled.md`. + +**Exit**: a gate over `kernel.elf`, on the decoder of +`issues/build/no-gate-decodes-kernel-elfs-instructions.md`, matches it against +both of Linux's sequences; on +every proving machine probes count what +`issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +selects for it: a clear per syscall where it selects the loop, a fill per +switch where it selects filling, and two IBPBs over B, C, idle, B, A, idle, A, +B. **Mutation**: either sequence edited, the clear's condition inverted, no +fill, the IBPB decided by the previous root. **Oracle**: Linux's sequences. diff --git a/issues/kernel/no-kernel-address-is-drawn-per-boot.md b/issues/kernel/no-kernel-address-is-drawn-per-boot.md new file mode 100644 index 00000000000..31d36981770 --- /dev/null +++ b/issues/kernel/no-kernel-address-is-drawn-per-boot.md @@ -0,0 +1,20 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# No kernel address is drawn per boot + +The direct map sits at the constant `PHYS_OFFSET` +(`kernel/src/mm/mod.rs:33`) and the kernel image in it at the physical +address firmware allocated (`bootloader/src/arch/x86_64.rs:90`), so nothing +but firmware moves the kernel's layout. Linux at `Ubuntu-6.8.0-142.142` builds +with `RANDOMIZE_BASE` and `RANDOMIZE_MEMORY` +(`debian.master/config/annotations:10674,10677`): at most 9 bits of text slot, +and a direct map placed at PUD granularity. + +**Exit**: on every proving machine, over 64 boots in a `Tier::Weekly` test, +each claimed bit is set 12 to 52 times, the guard of +`issues/kernel/the-kernel-has-no-stack-protector.md` and the first spawn's +bases included. **Mutation**: a fixed base. **Oracle**: Linux's bits. diff --git a/issues/kernel/no-program-runs-with-speculative-store-bypass-disabled.md b/issues/kernel/no-program-runs-with-speculative-store-bypass-disabled.md new file mode 100644 index 00000000000..75e8fe7a229 --- /dev/null +++ b/issues/kernel/no-program-runs-with-speculative-store-bypass-disabled.md @@ -0,0 +1,18 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# No program runs with speculative store bypass disabled + +The kernel never sets SSBD, so no process can have it. Linux at +`Ubuntu-6.8.0-142.142` defaults to `prctl`: a task that asks runs with it +(`arch/x86/kernel/cpu/bugs.c:2172,2278`). In ToyOS a program asks by a flag in +`system.toml`, and a flagged program's children inherit it at spawn. + +**Exit**: on every proving machine a flagged program runs with SSBD set, +through the control +`issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +names for its CPU, and so does its child, while an unflagged one runs with it +clear. **Mutation**: no inheritance. **Oracle**: the CPU's own register. diff --git a/issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md b/issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md new file mode 100644 index 00000000000..c4f9f89ac6b --- /dev/null +++ b/issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md @@ -0,0 +1,16 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# No test can hold a thread on a named CPU + +No `boot-actuators` arm and no `SYS_DEBUG` action pins a thread, so no test +can put two threads on two CPUs it knows. The pin is a `test-actuators` +action, so the shipped kernel carries none. + +**Exit**: a guest test pins a thread to each CPU in turn and reads its x2APIC +ID from CPUID.0BH:EDX across 1000 yields at each, on every proving machine and +under TCG: one ID per pin, a different one per CPU. **Mutation**: a pin the +scheduler's placement ignores. **Oracle**: the CPU's own x2APIC ID. diff --git a/issues/kernel/no-user-address-is-drawn-per-spawn.md b/issues/kernel/no-user-address-is-drawn-per-spawn.md new file mode 100644 index 00000000000..62e3cb99486 --- /dev/null +++ b/issues/kernel/no-user-address-is-drawn-per-spawn.md @@ -0,0 +1,19 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# No user address is drawn per spawn + +Every spawn puts the image at `USER_VM_BASE` (`kernel/src/loader/mod.rs:48`) +and the stack at `STACK_BASE` (`kernel/src/vma.rs:12`), and allocates +mappings in one fixed window (`kernel/src/vma.rs:18`), so an address one +process leaks is the same address in the next. Linux at `Ubuntu-6.8.0-142.142` +draws 32 bits for the image and the mmap base (`CONFIG_ARCH_MMAP_RND_BITS`, +`debian.master/config/annotations:18`) and 22 for the stack. + +**Exit**: on every proving machine, over 256 spawns each claimed bit of the +three bases is set 88 to 168 times, and each base claims Linux's bits or its +shortfall is filed as a defect. **Mutation**: `BASE + n * 2 MiB`; a constant +seed. **Oracle**: Linux's bits. diff --git a/issues/kernel/programs-hold-aes-keys-as-key-locker-handles.md b/issues/kernel/programs-hold-aes-keys-as-key-locker-handles.md new file mode 100644 index 00000000000..391fe02e0f3 --- /dev/null +++ b/issues/kernel/programs-hold-aes-keys-as-key-locker-handles.md @@ -0,0 +1,19 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# Programs hold AES keys as Key Locker handles + +Under Key Locker a program wraps an AES key into a handle under a wrapping key +the kernel loads into every CPU, and encrypts with the handle, so the key +itself need not stay in memory. Linux at `Ubuntu-6.8.0-142.142` has none: +nothing under its `arch/x86` names Key Locker. It is blocked on its first +consumer, a program that keeps an AES key for a session's life, and lands with +it, after `issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md`. + +**Exit**: on each proving machine that enumerates CPUID.19H:EBX bits 0 and 4 +and ECX bit 1, a handle wrapped on one pinned CPU gives FIPS-197 C.1's +ciphertext on another. **Mutation**: a wrapping key per CPU. **Oracle**: +FIPS-197. diff --git a/issues/kernel/spec-ctrl-and-gds-stay-as-firmware-left-them.md b/issues/kernel/spec-ctrl-and-gds-stay-as-firmware-left-them.md new file mode 100644 index 00000000000..3ccd0e00466 --- /dev/null +++ b/issues/kernel/spec-ctrl-and-gds-stay-as-firmware-left-them.md @@ -0,0 +1,21 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# SPEC_CTRL and GDS stay as firmware left them + +Nothing in `kernel/src` reads or writes `IA32_SPEC_CTRL` (0x48) or +`IA32_MCU_OPT_CTRL` (0x123), so every CPU runs under the speculation controls +firmware left, and an Intel part affected by Gather Data Sampling runs +unmitigated if firmware left `GDS_MITG_DIS` set. Linux at +`Ubuntu-6.8.0-142.142` writes both as its decision selects +(`arch/x86/kernel/cpu/bugs.c:89,777`). + +**Exit**: on every proving machine each CPU's 0x48 holds what +`issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +selects for it, on the T14 Linux's read less SSBD; on the T14, the one proving +machine with GDS, a probe sets `GDS_MITG_DIS` and reads it clear after the +kernel's write. **Mutation**: the clear deleted. **Oracle**: Linux's reads on +each machine. diff --git a/issues/kernel/the-direct-map-uses-1-gib-leaves-where-the-cpu-has-them.md b/issues/kernel/the-direct-map-uses-1-gib-leaves-where-the-cpu-has-them.md new file mode 100644 index 00000000000..57924b70cdf --- /dev/null +++ b/issues/kernel/the-direct-map-uses-1-gib-leaves-where-the-cpu-has-them.md @@ -0,0 +1,39 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# The direct map uses 1 GiB leaves where the CPU has them + +The kernel's direct map has no 1 GiB leaf: `paging::init` maps it 2 MiB at a +time (`kernel/src/arch/x86_64/paging.rs:953-956`). On a CPU that enumerates +`pdpe1gb`, CPUID.80000001H:EDX bit 26, a GiB the direct map covers whole and +that holds one memory type takes one 1 GiB leaf; the GiB that `direct_map_end` +cuts (it rounds only to 2 MiB, `toyos-bootmap/src/x86_64.rs:22`), any other +GiB, and every GiB on a CPU without it keep 2 MiB leaves. Which leaf a GiB +takes is a pure function that `paging::init` calls, living beside +`direct_map_end` in `toyos-bootmap/src/x86_64.rs` and host-tested there. The +walkers that write, `guard_4k` (`paging.rs:749`) and `ensure_table` (`:820`) +under `map_2m`, split a 1 GiB leaf into 2 MiB leaves of its type before they +descend; unsplit, `guard_kernel_page` writes a page table into the GiB's first +page. The walkers that cannot split, `policy_at` (`:718`, `&self`) and the +lock-free `debug_page_walk` (`:1098`, called by the `#PF` handler, +`idt/exceptions.rs:206,332`), read a PS=1 PDPT entry as a 1 GiB leaf and stop +there. The TCG model gains `+pdpe1gb`, which TCG implements +(`target/i386/cpu.c:945` at QEMU v11.1.1), so the PR gate maps 1 GiB leaves. + +**Exit**: the decision's host test gives a GiB holding one UC range 2 MiB +leaves, the GiB a non-GiB-aligned `end` cuts 2 MiB leaves, a uniform GiB one +leaf, and every GiB 2 MiB leaves without `pdpe1gb`; under TCG with `+pdpe1gb`, +a `boot-actuators` arm reads `direct_map_policy` of a page in an unsplit 1 GiB +leaf and gets its type, finds `debug_page_walk` of that page reporting the +1 GiB leaf without descending, then guards a 4 KiB page and maps an MMIO range +inside a 1 GiB leaf, and finds the GiB's other pages mapped with their type; +each proving machine's boot reports its leaf counts; the T14's 64 KiB pipe +figure. **Mutation**, each red: a 1 GiB leaf for every GiB; a 1 GiB leaf for +the last GiB when `end` is not GiB-aligned; `guard_4k` without the split, +reading the PS=1 PDPT entry as a PD; `policy_at` without the PDPT PS check; +`debug_page_walk` without it. **Oracle**: SDM Vol. 3A §14.11.9; Linux's +`split_mem_range` (`arch/x86/mm/init.c`), which gives 1 GiB pages only to a +range's GiB-aligned interior. diff --git a/issues/kernel/the-kernel-has-no-stack-protector.md b/issues/kernel/the-kernel-has-no-stack-protector.md new file mode 100644 index 00000000000..d006560db11 --- /dev/null +++ b/issues/kernel/the-kernel-has-no-stack-protector.md @@ -0,0 +1,21 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# The kernel has no stack protector + +Nothing in the build asks rustc for a stack protector, so an overflow of a +kernel stack buffer reaches the return address unchecked. Linux at +`Ubuntu-6.8.0-142.142` builds with `STACKPROTECTOR_STRONG` +(`debian.master/config/annotations:13402`), its guard at a fixed `%gs` offset +and switched per task. + +**Exit**: the kernel builds with a strong stack protector whose guard is at +`%gs:`, through LLVM and rustc changes the orchestrator admitted as +upstream-quality cross-platform options; `stack-protector-3.ll` and a rustc +assembly test see `%gs:` and no `__stack_chk_guard`; on every proving +machine the guest test `kernel_stack_canary` panics on thread B's guard in A's +frame. **Mutation**: the old lowering; the flags dropped; `context_switch`'s +write gone; a constant guard. **Oracle**: FileCheck, and the CPU's compare. diff --git a/issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md b/issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md index 83a69b84690..8a03007abb6 100644 --- a/issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md +++ b/issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md @@ -17,8 +17,7 @@ pointer XORed with a per-cache secret and its own address (`mm/slub.c:479-490`), `CONFIG_SLAB_FREELIST_RANDOM` shuffles each slab's 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`). Three rows of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +(`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, diff --git a/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md new file mode 100644 index 00000000000..6d7aa2f67a0 --- /dev/null +++ b/issues/kernel/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md @@ -0,0 +1,43 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# The kernel is at least as secure as Linux on every machine ToyOS supports + +Parity with Linux at `Ubuntu-6.8.0-142.142`, pinned by the first issue below, +is the floor on every CPU ToyOS supports, and the kernel also takes every +security feature such a CPU offers. The proving machines are the T14 and the +nightly's AMD EPYC KVM guests; the PR gate's TCG model proves wiring only. A +probe is a `boot-actuators` arm or a `test-actuators` `SYS_DEBUG` action. + +**Exit**: every issue below is closed, in the order listed. + +- `issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md` +- `issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +- `issues/kernel/spec-ctrl-and-gds-stay-as-firmware-left-them.md` +- `issues/kernel/no-program-runs-with-speculative-store-bypass-disabled.md` +- `issues/build/no-gate-decodes-kernel-elfs-instructions.md` +- `issues/kernel/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md` +- `issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md` +- `issues/kernel/indirect-branches-and-returns-run-without-thunks.md` +- `issues/kernel/tsx-stays-as-firmware-left-it.md` +- `issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md` +- `issues/kernel/no-user-address-is-drawn-per-spawn.md` +- `issues/kernel/the-kernel-has-no-stack-protector.md` +- `issues/kernel/no-kernel-address-is-drawn-per-boot.md` +- `issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md` +- `issues/kernel/kernel-functions-return-with-their-used-registers-intact.md` +- `issues/kernel/a-threads-kernel-stack-has-no-guard-page.md` +- `issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md` +- `issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md` +- `issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md` +- `issues/boot-media/the-loader-never-sets-the-firmwares-memory-overwrite-request.md` +- `issues/kernel/user-programs-run-without-a-shadow-stack.md` +- `issues/kernel/the-kernel-runs-without-indirect-branch-tracking.md` +- `issues/kernel/the-kernel-runs-without-a-shadow-stack.md` +- `issues/kernel/user-programs-have-no-protection-keys.md` +- `issues/kernel/programs-hold-aes-keys-as-key-locker-handles.md` +- `issues/kernel/a-split-lock-goes-unnoticed.md` +- `issues/kernel/each-boot-prints-the-vulnerabilities-lines-linux-prints.md` diff --git a/issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md b/issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md deleted file mode 100644 index 4b7b655bae2..00000000000 --- a/issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md +++ /dev/null @@ -1,423 +0,0 @@ ---- -status: open -kind: track -opened: 2026-09-29 ---- - -# The kernel mitigates what Linux mitigates on the T14 - -Owner ruling: ToyOS also needs to be at least as secure as Linux on the same -hardware. The hardware is the T14 (`issues/hardware/the-t14-boots-toyos-unattended.md`), -an i5-1135G7, Tiger Lake, family 6 model 0x8C -(`issues/hardware/the-t14-touchpad-is-i2c-hid-and-unbuilt.md`). The Linux is -the kernel the T14 runs, Ubuntu's 6.8.0-142-generic. Its source is tag -`Ubuntu-6.8.0-142.142` (commit 53e5d07aac028a1523ab0b115f079d6d1bc831ef) of -`https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/noble`, -where every Linux `path:line` here is read; `bugs.c` and `common.c` are in -`arch/x86/kernel/cpu/`. Its config is `/boot/config-6.8.0-142-generic` from -`linux-modules-6.8.0-142-generic_6.8.0-142.142_amd64.deb`, sha256 -3b8533dd9d235ca634ac58f82c5ce1ee35f12ef620693e17033184d2c9ca5890. The -scoreboard is that kernel's `/sys/devices/system/cpu/vulnerabilities/*` lines -on the T14, and the hardening table below. - -## Hardening defaults - -The track matches every hardening default the pinned Ubuntu config sets, and -no more. A hardening default is an option the config sets that, under the -default command line, makes an attack on the kernel or a process harder and -that no vulnerabilities line reports; access policy (the LSMs, lockdown, -`*_RESTRICT`, `STRICT_DEVMEM`) is the capability model's. `CONFIG_SLS` is the -one entry of -the `CPU_MITIGATIONS` menu (`arch/x86/Kconfig:2475-2664`) that no line -reports. Numbers are the config's lines; each row is closed by its stage, its -defect, the ToyOS mechanism named, or the reason it does not apply. - -| Option | Disposition | -|---|---| -| `RANDOMIZE_BASE` (528) | S10 | -| `RANDOMIZE_MEMORY` (532) | S10 | -| `ARCH_MMAP_RND_BITS=32` (909) | S8 | -| `STACKPROTECTOR_STRONG` (882) | S9 | -| `SLS` (564) | S5 | -| `X86_USER_SHADOW_STACK` (502) | `issues/kernel/user-programs-run-without-a-shadow-stack.md` | -| `RESET_ATTACK_MITIGATION` (2457) | `issues/boot-media/the-loader-never-sets-the-firmwares-memory-overwrite-request.md` | -| `RANDOMIZE_KSTACK_OFFSET_DEFAULT` (933) | `issues/kernel/every-syscall-runs-at-one-kernel-stack-offset.md` | -| `ZERO_CALL_USED_REGS` (11477) | `issues/kernel/kernel-functions-return-with-their-used-registers-intact.md` | -| `VMAP_STACK` (930) | `issues/kernel/a-threads-kernel-stack-has-no-guard-page.md` | -| `STRICT_KERNEL_RWX` (935) | `issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md` | -| `DEBUG_WX` (12074) | `issues/kernel/kernel-text-is-writable-and-every-kernel-page-executable.md` | -| `SLAB_FREELIST_RANDOM` (1132) | `issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md` | -| `SLAB_FREELIST_HARDENED` (1133) | `issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md` | -| `RANDOM_KMALLOC_CACHES` (1136) | `issues/kernel/the-kernel-heap-has-none-of-slubs-hardening.md` | -| `X86_INTEL_TSX_MODE_OFF` (498) | `issues/kernel/tsx-stays-as-firmware-left-it.md` | -| `INTEL_IOMMU_DEFAULT_ON` (9892) | `issues/kernel/a-device-without-a-domain-of-its-own-reaches-all-memory.md` | -| `INIT_ON_ALLOC_DEFAULT_ON` (11474), pages | `pmm::alloc_page` and `alloc_contiguous` zero every page they hand out (`kernel/src/mm/pmm.rs:280-282,318-320`) | -| `SCHED_STACK_END_CHECK` (12084) | every scheduler pass panics on a running thread whose stack-end word changed (`kernel/src/sched/driver.rs:521,592,925-935`), as `schedule_debug` does (`kernel/sched/core.c:5957-5958`) | -| `HARDENED_USERCOPY` (11376) | the kernel side of every user copy is a slice, a `T` or a ring run, whose extent its type carries, never a bare pointer and length (`kernel/src/user_ptr.rs:146-161,187-214,247-275`) | -| `X86_UMIP` (493) | `CR4_OPTIONAL` sets UMIP wherever CPUID offers it (`kernel/src/arch/x86_64/control_regs.rs:60,212`), as `setup_umip` does (`common.c:360-370`) | -| `INIT_ON_ALLOC_DEFAULT_ON` (11474), slab | not applicable: Rust reads no allocation before writing it | -| `INIT_STACK_ALL_ZERO` (11473) | not applicable: Rust reads no local before writing it, and `copy_out` copies only a `UserSafe` type, which has no padding (`kernel/src/user_ptr.rs:25-28`) | -| `FORTIFY_SOURCE` (11377) | not applicable: every Rust slice copy checks both lengths | -| `UBSAN_BOUNDS`, `_SHIFT`, `_BOOL`, `_ENUM` (12040-12045) | not applicable: each reports C undefined behaviour that safe Rust defines or refuses | -| `STRICT_MODULE_RWX` (937) | not applicable: ToyOS loads no code into the kernel | -| `LEGACY_VSYSCALL_XONLY` (536) | not applicable: ToyOS maps no vsyscall page | -| `SHUFFLE_PAGE_ALLOCATOR` (1139) | not applicable: Linux shuffles only under `page_alloc.shuffle=1` (`mm/shuffle.c:12-30`) | -| `BPF_JIT_ALWAYS_ON` (124) | not applicable: ToyOS has no BPF | -| `MODULE_SIG` (982) | not applicable: ToyOS loads no modules | -| `KEXEC_SIG` (318) | not applicable: ToyOS has no kexec | - -## What is true today - -The kernel writes no CPU-vulnerability mitigation: no access to -`IA32_SPEC_CTRL` (0x48), `IA32_PRED_CMD` (0x49), `IA32_ARCH_CAPABILITIES` -(0x10A) or `IA32_FLUSH_CMD` (0x10B) anywhere in `kernel/src/`, no `lfence` -speculation barrier, no `verw` buffer clear, no retpoline or return-thunk -codegen flag. It executes no `swapgs`: GS holds kernel per-CPU data -permanently (`kernel/src/arch/x86_64/syscall.rs`, above `syscall_entry`) and -`FSGSBASE` is `CR4_FORBIDDEN` (`kernel/src/arch/x86_64/control_regs.rs`). -SMEP, SMAP and UMIP are taken only when offered (`CR4_OPTIONAL`, same file). -Every user process loads at `USER_VM_BASE` (`kernel/src/loader/mod.rs`) with -its stack directly under the image (`kernel/src/vma.rs`, `ALLOC_CEILING = -STACK_BASE`) and its mmap window at fixed bounds (`vma.rs`, `WINDOW`), and -`rebase_base` (`toyos-userbound/src/span.rs`) takes no random input. The -direct map sits at the fixed `PHYS_OFFSET` (`kernel/src/mm/mod.rs`) and the -kernel image at `PHYS_OFFSET` plus wherever firmware allocated it -(`bootloader/src/main.rs`): nothing draws either. The kernel target is -`x86_64-unknown-none` with no stack-protector or `cf-protection` flag -(`kernel/.cargo/config.toml`). - -## Stages - -System-mode TCG enumerates none of SPEC_CTRL, ARCH_CAPABILITIES and SSBD -(`target/i386/cpu.c` at QEMU v11.1.1: `CPUID_7_0_EDX_KERNEL_FEATURES` is 0 -outside `CONFIG_USER_ONLY`), nor BHI_CTRL (`TCG_7_2_EDX_FEATURES`). The -nightly's `guest` and `audio` shards run KVM with `-cpu host` -(`.github/workflows/nightly.yml`, `src/arch.rs` `Arch::cpu`), so there the -branches gated on those bits run on whatever CPU the runner has. A QEMU test -therefore asserts wiring only: what the guest applied equals S1 applied to -the facts the guest itself reports on a `boot-actuators` line. Independence -comes from S1's T14 fixture and the T14 run, never from KVM. - -- **S0 — Evidence.** On the T14 under its stock Ubuntu boot, as root: - - ``` - apt-get install -y msr-tools cpuid busybox-static cpio && modprobe msr - cat /proc/version /proc/cmdline /proc/sys/vm/mmap_rnd_bits - dpkg-query -W linux-image-6.8.0-142-generic linux-modules-6.8.0-142-generic - sha256sum /boot/config-6.8.0-142-generic - grep . /sys/devices/system/cpu/vulnerabilities/* - grep microcode /proc/cpuinfo - cpuid -r -l 1; cpuid -r -l 7 -s 0; cpuid -r -l 7 -s 2 - cpuid -r -l 0x80000000; cpuid -r -l 0x80000008; cpuid -r -l 0x80000021 - rdmsr -a 0x10a; rdmsr -a 0x48; rdmsr -a 0x123 - mkdir -p rd/bin rd/sys rd/proc && cp /usr/bin/busybox rd/bin/ - printf '#!/bin/busybox sh\n/bin/busybox mount -t sysfs s /sys\n/bin/busybox mount -t proc p /proc\n/bin/busybox cat /proc/version\n/bin/busybox grep . /sys/devices/system/cpu/vulnerabilities/*\n/bin/busybox poweroff -f\n' > rd/init && chmod +x rd/init - (cd rd && find . | cpio -o -H newc) > s0.cpio && cp /boot/vmlinuz-6.8.0-142-generic . - ``` - - and `rdmsr -a 0x122` where 0x10A bit 7 (TSX_CTRL_MSR) is set, and `rdmsr -a - 0x10f` where CPUID.(7,0):EDX bits 11 and 13 (RTM_ALWAYS_ABORT, - TSX_FORCE_ABORT, `arch/x86/include/asm/cpufeatures.h:422-423`) are. - `/proc/cpuinfo`'s `microcode` is the revision as Linux reads it - (`arch/x86/include/asm/microcode.h:62-75`). On the development host, whose - QEMU is the one `.github/qemu-version` pins, `qemu-system-x86_64 --version` - and then `qemu-system-x86_64 -nodefaults -machine q35 -cpu - qemu64,+rdrand,+smap,+fsgsbase,+x2apic,+smep -smp 2 -m 4G -kernel - vmlinuz-6.8.0-142-generic -initrd s0.cpio -append "console=ttyS0 panic=-1" - -serial stdio -display none -no-reboot` capture the TCG model's lines. S0 - builds ToyOS's `boot-actuators` facts line, printed on the T14 and on the TCG - model: the same CPUID leaves, 0x10A, and 0x48 and 0x123 as read before the - kernel's first write to either. The capture is refused unless `/proc/version` - names 6.8.0-142, both packages are 6.8.0-142.142 and the config's sha256 is - the one above. The capture is one-time: the kernel - image, `s0.cpio` and busybox are never committed and no build or test boots - them. **Exit**: the captured text outputs committed as S1's fixtures. Ubuntu - is wiped from the T14 only after that commit. -- **S1 — The decision, a host-tested function.** A pure function, in a crate - the kernel and a host test both build, maps (vendor, family, model, - stepping, microcode revision, CPUID.1, CPUID.(7,0), CPUID.(7,2), - CPUID.0x80000008:EBX, CPUID.0x80000021:EAX, ARCH_CAPABILITIES) to each - vulnerability's Linux line and the mitigation ToyOS applies. The two - extended leaves are read where CPUID.0x80000000:EAX reaches them, as - `get_cpu_cap` does (`common.c:1072-1084`), and carry the AMD bits - `init_speculation_control` folds into IBRS, IBPB, STIBP and SSBD - (`common.c:994-1011`); the TCG model is AuthenticAMD. It carries `cpu_vuln_whitelist` and `cpu_vuln_blacklist` - (`common.c:1182-1344`), `cpu_set_bug_bits` (`common.c:1414-1578`), - `spectre_bad_microcodes` (`arch/x86/kernel/cpu/intel.c:141-163`) and - `bugs.c`'s selections under the default command line and the pinned config. - CPUID.(7,0):EDX bit 26 is SPEC_CTRL (IBRS and IBPB), 27 STIBP, 29 - ARCH_CAPABILITIES and 31 SSBD (`arch/x86/include/asm/cpufeatures.h:434-439`), - and CPUID.(7,2):EDX bit 4 is BHI_CTRL (`arch/x86/kernel/cpu/scattered.c:31`); - the kernel reads 0x10A only when bit 29 is set, as `x86_read_arch_cap_msr` - does (`common.c:1353-1361`), since the read is `#GP` otherwise. **Exit**: - host tests feed S0's T14 facts and get S0's T14 lines, and feed the TCG - model's facts and get the TCG model's lines. Negative control: deleting - `GDS` from the TIGERLAKE_L blacklist row (`common.c:1312`) reds the T14 - case. That match is the only way `X86_BUG_GDS` is set (`common.c:1521-1523`), - and without the bug `cpu_show_common` answers "Not affected" - (`bugs.c:3308-3309`). -- **S2 — SPEC_CTRL and GDS.** The per-CPU base of `IA32_SPEC_CTRL` is declared - once with the control registers and asserted by `self_check` on every CPU. - It is S1's whole `x86_spec_ctrl_base`: every bit `bugs.c` ORs into it, under - S1's conditions for each (`RRSBA_DIS_S` at 1736, `BHI_DIS_S` at 1800, `IBRS` - at 1934, `SSBD` at 2224). S6's SSBD is the one bit a context switch - changes, so the assertion is `SPEC_CTRL & !SSBD == base`. Where S1 finds GDS - and ARCH_CAPABILITIES.GDS_CTRL, `IA32_MCU_OPT_CTRL.GDS_MITG_DIS` is cleared - and read back and `GDS_MITG_LOCKED` is never written, as `update_gds_msr` - does (`bugs.c:777-812`); a lock firmware set is reported as Linux reports it, - "Mitigation: Microcode (locked)" (`bugs.c:850-861`). **Exit**, T14: on every - CPU 0x48 reads the declared base, which equals S0's Linux read of 0x48 with - SSBD masked; a `boot-actuators` arm sets `GDS_MITG_DIS` on every CPU before - the kernel's GDS write and reads it clear after, so deleting the clear - leaves it set and reds it. If S0 finds `GDS_MITG_LOCKED` set, the arm cannot - set it and S0's fixture says so. On KVM the read-back equals S1's base over - the reported facts. -- **S3 — The boundaries.** The branch history is cleared in software on - syscall entry where S1 finds BHI and no BHI_CTRL (`bugs.c:1831-1858`, - `arch/x86/entry/entry_64.S:119`), and S2's BHI_DIS_S replaces the loop where - BHI_CTRL is present; Tiger Lake has eIBRS without BHI_CTRL, so the T14 takes - the loop. The RSB is filled on context switch where S1 selects retpoline, - LFENCE or IBRS mode and never under eIBRS (`bugs.c:1741-1789`, - `entry_64.S:205`). IBPB is conditional (Decisions). Each sequence is - Linux's: the clear is `clear_bhb_loop`, five outer passes of five inner - branches and then `lfence` (`entry_64.S:1534-1569`), and the fill is - `__FILL_RETURN_BUFFER` with `RSB_CLEAR_LOOPS`, 32 calls each followed by - `int3` and then `lfence` (`arch/x86/include/asm/nospec-branch.h:132,137-162`). - **Exit**: a gate over `kernel.elf` decodes both sequences at the symbols it - names and compares each decoded instruction — opcode, immediate and - normalised relative target — against `clear_bhb_loop` - (`entry_64.S:1534-1569`) and `__FILL_RETURN_BUFFER` under `RSB_CLEAR_LOOPS` - (`nospec-branch.h:132-162`), excluding `clear_bhb_loop`'s `.align 64, 0xcc` - (`entry_64.S:1540,1549`), whose bytes depend on where the linker places the - function, and `ASM_CREDIT_CALL_DEPTH` (`nospec-branch.h:161`), which exists - only under `CALL_DEPTH_TRACKING`, a mitigation ToyOS has no counterpart for: - deleting `3: jmp 4f`/`nop` from the inner loop - (`entry_64.S:1558-1560`), deleting the `int3` `__FILL_RETURN_SLOT` places - after each fill `call` (`nospec-branch.h:137-141`), or flattening the `call - 1f`/`call 2f`/`RET` nesting into direct `jmp`s each reds it. A - `boot-actuators` per-thread counter the clear sequence increments advances - by exactly 1000 over one thread's 1000 syscalls where S1 selects the loop, - the T14, and by 0 where it does not; inverting the runtime condition gives 0 - on the T14 and reds it. A `boot-actuators` count of RSB fills over a probe's - context switches equals the switch count where S1 over the reported facts - selects a filling mode and 0 otherwise; the TCG model is in retpoline mode - (`common.c:1231` lacks NO_SPECTRE_V2, and AUTO without eIBRS or RETBLEED - takes retpoline, `bugs.c:1878-1895`), so dropping the fill reds it there. - The IBPB decision is a pure function of the per-CPU last-user state and the - incoming process, host-tested on A→idle→B (IBPB when either is flagged) and - A→idle→A (none); deciding from the previous root instead, which on A→idle→B - is idle's kernel root (`kernel/src/arch/x86_64/hw.rs` activates one on every - switch), reds it. A `boot-actuators` arm drives B, C, idle, B, A, idle, A, B - through the switch path on one CPU with only A flagged and counts the writes - of `PRED_CMD_IBPB`, value 1, to `IA32_PRED_CMD` - (`arch/x86/include/asm/msr-index.h:61-62`): 2 (B→A, A→B) where S1 finds - IBPB, the T14, and 0 where it does not; dropping the write from the switch - path, or writing 0 in place of `PRED_CMD_IBPB`, gives 0 on the T14 and reds - it. -- **S4 — Spectre v1.** SMAP moves to the required set: the kernel refuses to - boot on an x86-64 CPU without it. With no `swapgs` executed and `FSGSBASE` - forbidden, Linux's own rule (`bugs.c:943-944`: `FSGSBASE || - !smap_works_speculatively()`) then owes no swapgs barrier. The usercopy - barrier and user-pointer sanitization go where the range is checked: an - `lfence` after `in_user_half` in `kernel/src/user_ptr.rs`'s `window` and - after `is_user_object` in its `object`, and the checked address masked to - the user half before `translate_user` walks it, as `barrier_nospec` in - `_copy_from_user` (`lib/usercopy.c:21`) and `mask_user_address` - (`arch/x86/include/asm/uaccess_64.h:64`) do. **Exit**: a standing guest test - boots without SMAP and asserts the refusal names SMAP; moving SMAP back to - `CR4_OPTIONAL` boots and reds it. The harness has one `-cpu` string per - accelerator (`src/arch.rs`, `Arch::cpu`), so the stage adds a per-test - override: that string without `+smap` under TCG, and `host,-smap` under KVM. On the T14 a - bounds-check-bypass gadget through each of the three sites recovers a byte - planted outside the user half in at most 16 of 1000 trials (chance is 1 in - 256; per-site false-red probability 7.6e-7, exact binomial tail). Each - site's mutation, its `lfence` or the mask deleted, must recover it in more - than 16, or that site is unmeasured and the stage is not done. -- **S5 — Indirect branches and returns.** Every indirect `call`/`jmp` goes - through `-Zretpoline-external-thunk` and every return through - `-Zfunction-return=thunk-extern` into the kernel's own thunks, whose body - is the one S1 selects: a retpoline, or for ITS an aligned thunk whose - branch's last byte lies in a cacheline's upper half - (`Documentation/admin-guide/hw-vuln/indirect-target-selection.rst:6-8,56-59`, - `arch/x86/lib/retpoline.S:371-403`), which the T14 takes - (`bugs.c:1222-1223,1313-1319`). The retpoline flag is a target modifier, so - the `x86_64-unknown-none` `core` and `alloc` that `src/toolchain.rs` builds - are built under it. Compiled code is then left with no raw `ret` or - indirect `jmp`, so SLS is owed in the kernel's own assembly, the thunks and - the entry code: every `ret` and indirect `jmp` there is followed by `int3`. - **Exit**: a gate over - `kernel.elf` finds no raw indirect `call`/`jmp` or `ret` outside the thunks - and the entry sequences it names by symbol, an `int3` after every `ret` and - indirect `jmp` there, and every aligned thunk's branch ending at `addr & 63 - >= 32`; placing a thunk's branch at a cacheline start, or deleting the `int3` - after one thunk's `ret`, reds it. Each boot reads its - live thunk bodies back and compares them with the body S1 selects over the - facts it reports: a `thunk_body` that answers the aligned thunk for every - input puts a bare `jmp *%reg` in the TCG model, whose selection is the - retpoline, and reds it. -- **S6 — Speculative Store Bypass.** `IA32_SPEC_CTRL.SSBD` is set while a - flagged process runs and clear otherwise, Linux's default (`bugs.c:2200-2202`: - the default command selects `SPEC_STORE_BYPASS_PRCTL`). The flag is one bit - in `SpawnArgs` on the existing `SYS_SPAWN` (`toyos-abi/src/syscall.rs`), set - by init from the program's `system.toml` entry and inherited by whatever a - flagged process spawns; it also marks the process for S3's IBPB. **Exit**, - T14: a `boot-actuators` probe logs `IA32_SPEC_CTRL.SSBD` as read on the first - switch into each process, and a guest test reads it clear for an unflagged - process, set for a flagged one, and set for a child a flagged process spawns - with the bit clear in its `SpawnArgs`; dropping the inheritance at spawn - clears the child's and reds it. On KVM the expected bits are S1's over the - reported facts. -- **S7 — Microcode loading. PARKED for the owner.** Gather Data Sampling and - some ITS variants are microcode-only fixes; Linux's coverage of them on the - T14 assumes the shipped BIOS carries microcode at or past the fixing - revision. Root `CLAUDE.md`'s dependency rule allows vendor firmware only - "loaded only by that device's own driver through its IOMMU domain; it never - executes on the CPU" — a CPU microcode update is firmware that *does* - execute on the CPU, the one case that rule does not carve out. This stage is - needed only if S0 finds the T14's shipped BIOS revision is below the - revision Linux's mitigation table requires; if S0 finds the BIOS already at - or past it, this stage is moot and is closed without being built. Either way - it is the owner's call, not this track's. -- **S8 — User-space ASLR.** The image base, the stack base and the mmap - window's base are each drawn per spawn from `arch::entropy::draw`; a draw - still `None` after `entropy::ATTEMPTS` refuses the spawn. Each base's entropy - is stated in bits against Linux's: `CONFIG_ARCH_MMAP_RND_BITS=32` for the - image and mmap bases (`fs/binfmt_elf.c:1127-1129`, `arch/x86/mm/mmap.c:77`), - 22 bits for the stack (`arch/x86/include/asm/elf.h:331`, `STACK_RND_MASK` - 0x3fffff pages). At 2 MiB alignment the 2^47-byte user half holds 2^26 - slots, so a base short of Linux's figure is filed as a defect with both - numbers. **Exit**: a guest test spawns one binary 256 times, each run - reporting its three bases from its own addresses; every claimed bit of every - base's slot index is set in between 88 and 168 runs (±5σ; per-bit false-red - probability 3.3e-7, exact binomial tail). `base = BASE + n * 2 MiB` leaves - every slot bit above bit 7 clear in all 256 runs and reds it. Each of S10's - 64 boots also prints its first spawn's three bases, bit-tested by S10's - rule; a constant-seeded generator repeats them every boot and reds it. -- **S9 — Kernel stack protector.** `-Zstack-protector=strong` for the kernel - and its `core` and `alloc` (`src/toolchain.rs`), every protected frame - loading and comparing a per-thread guard at `%gs:N`. Linux gets `%gs:40` - from a Linux triple under `-mcmodel=kernel` (`arch/x86/Makefile:169`, - `arch/x86/include/asm/stackprotector.h:9`). LLVM paths here are - `rust/src/llvm-project` at the commit `rust/` pins, a79bc52c1d5e. The - kernel's triple is `x86_64-unknown-none` (`kernel/.cargo/config.toml`), and - there X86 takes the segment slot only when `hasStackGuardSlotTLS` holds - (`llvm/lib/Target/X86/X86ISelLoweringCall.cpp:548-551,564`: glibc, musl, - Fuchsia, Android). Otherwise it falls through (`:604`, `:638-640`) to the - one global `__stack_chk_guard` - (`llvm/lib/CodeGen/TargetLoweringBase.cpp:2388-2411`), and the - `stack-protector-guard`, `-reg` and `-offset` module flags act only on - those four. No ToyOS-owned mechanism reaches the slot: X86's one - other lowering, `LOAD_STACK_GUARD`, is 64-bit Mach-O's alone - (`llvm/lib/Target/X86/X86ISelLowering.cpp:2770-2772`), and naming the - kernel a glibc or musl triple is a false claim about its environment. S9 is - four steps, each with its own test: - 1. **LLVM.** X86's `getIRStackGuard` takes the segment slot on any triple - whose module sets `stack-protector-guard` to `tls`, as RISC-V's does - (`llvm/lib/Target/RISCV/RISCVISelLowering.cpp:25703-25707`), and - `insertSSPDeclarations` then declares no global. Clang accepts - `-mstack-protector-guard=tls` and `-mstack-protector-guard-reg=gs` on - every x86 triple (`clang/lib/Driver/ToolChains/Clang.cpp:3479-3491, - 3561-3570`), and the backend lowers them to the global on a bare one, so - the change is an upstream fix. Test: RUN lines in - `llvm/test/CodeGen/X86/stack-protector-3.ll` for - `x86_64-unknown-none-elf` under `-code-model=kernel`, with `tls`, `gs` - and an offset, check `%gs:` and no `__stack_chk_guard`; the - unchanged lowering emits `__stack_chk_guard(%rip)` and reds them. - 2. **rustc**, which sets none of those flags today. - `-Zstack-protector-guard`, `-Zstack-protector-guard-reg` and - `-Zstack-protector-guard-offset` - (`compiler/rustc_session/src/options.rs`) set them in - `compiler/rustc_codegen_llvm/src/context.rs`, as Clang's - `clang/lib/CodeGen/CodeGenModule.cpp:1543-1553` does. Test: a - `tests/assembly-llvm/stack-protector/` test for `x86_64-unknown-none` - checks `%gs:` in prologue and epilogue and no - `__stack_chk_guard`; dropping the flags from `context.rs` reds it. - The orchestrator's ruling: root `CLAUDE.md`'s dependency rule governs — - "a fork carries a change written to upstream quality and goes when - upstream has it" — so a general cross-platform option written to - upstream quality is admitted when ToyOS needs it and upstream lacks it. - `src/forkcheck.rs` admits a cross-platform `rust/` file only for a - target arm at an existing dispatch site today; steps 1 and 2 each add a - cross-platform option instead, and the stage that lands them amends its - module header to admit exactly such an upstream-quality cross-platform - change. - 3. **The kernel.** `PerCpu` holds the guard at `N`, asserted at compile - time as `stackprotector.h:55` asserts 40. The kernel defines no - `__stack_chk_guard`, so a toolchain that falls back to the global fails - its link, and its `__stack_chk_fail` panics naming the stack protector. - A frame compares against the slot as it was on entry, so nothing changes - the slot under a live frame of the running thread - (`stackprotector.h:39-49`). `_start` (`kernel/src/arch/x86_64/boot.rs`) - writes the BSP's first `IA32_GS_BASE` and guard before - `call kernel_main`, where `percpu::init_bsp` writes the base from Rust - today, after the boot's first frames. A later write of the base carries - the slot's value over. An AP's `PerCpu` holds its first thread's guard - before the trampoline publishes it (`kernel/src/arch/x86_64/smp.rs`), as - `cpu_init_stack_canary` does (`stackprotector.h:66-69`, - `arch/x86/kernel/smpboot.c:969`). After that only `context_switch` - (`kernel/src/arch/x86_64/switch.rs`) writes the slot: the incoming - thread's own guard, after the stack switch, as `__switch_to_asm` does - (`arch/x86/entry/entry_64.S:193-196`). Each thread draws its guard from - `arch::entropy::draw` at creation; a draw still `None` after - `entropy::ATTEMPTS` refuses the thread, and `_start` halts on that bound. - 4. **Exit**: the guest test `kernel_stack_canary` is one boot with - `smp: 1`, so two threads share one slot. A `test-actuators` program's - threads A and B each call twice a `SYS_DEBUG` action whose frame holds a - local array, so it is protected, and which returns the `%gs:N` it reads - inside that frame; between its two calls each hands off to the other, - through a context switch. The test asserts each thread's two reads - agree and A's differ from B's. A then calls an action that writes a - given value over its own frame's guard through an out-of-bounds write - past that array. - Given A's read, it returns, so the frame compares against what `%gs:N` - held. Given B's read, the kernel panics naming the stack protector, the - boot's last event. Deleting `context_switch`'s write of the incoming - thread's guard leaves one value in the slot for both threads: A's and - B's reads agree and the overflow with B's guard returns, and both red. - Each of S10's 64 boots prints the guard `_start` drew, and S10's rule - holds for each of its 64 bits; a constant guard sets every bit in 0 or - 64 boots and reds it. -- **S10 — Kernel ASLR, `Tier::Weekly` (`src/tiers.rs`).** Its first step - times one `boot-actuators` boot of the TCG model and records 64 times that as - the weekly cost. The loader, which builds the mapping the kernel starts in, - draws the kernel image's virtual base and the direct map's base per boot - and refuses the boot when its draw fails; the kernel receives both in its - boot information, and `PHYS_OFFSET`'s two declarations - (`kernel/src/mm/mod.rs`, `bootloader/src/main.rs`) become that one - handed-over value. Each base's bits are stated against Linux's: - `CONFIG_RANDOMIZE_BASE` puts the text in a 2 MiB slot of `KERNEL_IMAGE_SIZE` - = 1 GiB (`arch/x86/include/asm/page_64_types.h:95-96`, at most 9 bits), and - `CONFIG_RANDOMIZE_MEMORY` draws `page_offset_base` at PUD granularity - (`arch/x86/mm/kaslr.c:146`). **Exit**: 64 QEMU boots each print both bases - on a `boot-actuators` line; every claimed bit is set in between 12 and 52 of - them (per-bit false-red probability 1.0e-7, exact binomial tail); a fixed - base reds it. - -## Decisions - -- **IBPB is conditional**, Linux's default: `spectre_v2_user_select_mitigation` - maps the default command to PRCTL, which enables `switch_mm_cond_ibpb` - (`bugs.c:1512-1543`), and `arch/x86/mm/tlb.c:442-444` (`cond_mitigation`) - issues IBPB on a switch between two processes only when either is flagged, - printing "IBPB: conditional" (`bugs.c:3195-3205`). The flag is S6's. -- **Probes add no syscall.** A probe is compiled under `boot-actuators`, or - is a `SYS_DEBUG` action under `test-actuators`. - -## Exit - -The T14's ToyOS boot prints, for each vulnerabilities file S0 captured, -exactly S0's Linux line. Where ToyOS mitigates differently, the line and why -it is not worse: - -- `spectre_v1`: S0's line; ToyOS owes none of its swapgs barriers because it - executes no `swapgs` (S4). -- `spectre_v2`: S0's line without its VM-exit clauses (`PBRSB-eIBRS: …`, - `, KVM: …`); ToyOS runs no guest, so it has no VM exit to mitigate. -- `spec_store_bypass`: "Mitigation: Speculative Store Bypass disabled per - program"; any program can be flagged in `system.toml`, where Linux's - default mitigates only a process that asks by prctl. - -Reading ARCH_CAPABILITIES as 0 instead of from 0x10A turns `spectre_v2` into -"Mitigation: Retpolines; …" and `gather_data_sampling` into "Vulnerable: No -microcode", and reds it. S1–S6 and S8–S10 are green, S7 is closed by S0's -finding or by the owner, and every defect the hardening table cites is closed. diff --git a/issues/kernel/the-kernel-runs-without-a-shadow-stack.md b/issues/kernel/the-kernel-runs-without-a-shadow-stack.md new file mode 100644 index 00000000000..e0c80694d90 --- /dev/null +++ b/issues/kernel/the-kernel-runs-without-a-shadow-stack.md @@ -0,0 +1,19 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# The kernel runs without a shadow stack + +Nothing checks a kernel return, so an overwritten kernel return address is +taken. Linux at `Ubuntu-6.8.0-142.142` has no kernel shadow stack; the kernel +takes one where the CPU enumerates supervisor shadow stacks, CET_SSS, +CPUID.(7,1):EDX bit 18, which the T14 reads set (EDX 0x00040000). + +**Exit**: on the T14, the one proving machine known to enumerate CET_SSS, a +forged kernel return is `#CP`, the guest test `kernel_stack_canary` of +`issues/kernel/the-kernel-has-no-stack-protector.md` runs under it, and a +store through `DirectMap` to a shadow stack faults. **Mutation**: `SH_STK_EN` +clear; SSP not switched; the frame left in the writable direct map. +**Oracle**: the T14's CPU. diff --git a/issues/kernel/the-kernel-runs-without-indirect-branch-tracking.md b/issues/kernel/the-kernel-runs-without-indirect-branch-tracking.md new file mode 100644 index 00000000000..e9b6dd64060 --- /dev/null +++ b/issues/kernel/the-kernel-runs-without-indirect-branch-tracking.md @@ -0,0 +1,19 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# The kernel runs without indirect branch tracking + +Nothing in the build asks for `endbr64` and nothing sets `S_CET.ENDBR_EN`, so +an indirect call or jump in the kernel may land anywhere in its text. Linux at +`Ubuntu-6.8.0-142.142` leaves `X86_KERNEL_IBT` unset +(`debian.master/config/annotations:834`); the kernel takes it where the CPU +enumerates IBT. + +**Exit**: a gate over `kernel.elf`, on the decoder of +`issues/build/no-gate-decodes-kernel-elfs-instructions.md`, finds `endbr64` at +every IDT and `IA32_LSTAR` target and no `notrack`; on the T14, the one proving machine known to +enumerate IBT, a call to a function without one is `#CP`. **Mutation**: +`syscall_entry`'s `endbr64` gone; `ENDBR_EN` clear. **Oracle**: the T14's CPU. diff --git a/issues/kernel/toyos-uses-what-modern-hardware-offers-for-speed.md b/issues/kernel/toyos-uses-what-modern-hardware-offers-for-speed.md new file mode 100644 index 00000000000..f6b97784fe0 --- /dev/null +++ b/issues/kernel/toyos-uses-what-modern-hardware-offers-for-speed.md @@ -0,0 +1,32 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# ToyOS uses what modern hardware offers for speed + +ToyOS takes each hardware feature that makes it faster on every CPU that +enumerates it; a CPU without one runs the plain path, or is refused by name +where the stage says so. The proving machines are the T14 and the nightly's +AMD EPYC KVM guests, and a stage's correctness test runs on each that has its +feature. A figure is the T14's, since the EPYC guests are shared CI runners: +the median of 11 runs with its spread, taken by the program of +`issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md`. +A stage is done at or past that program's Linux figure, or with the gap filed +as a defect carrying both; a feature Linux has no counterpart for lands only +if it beats ToyOS without it. What no proving machine is known to offer is +`issues/hardware/features-no-proving-machine-is-known-to-offer.md`. + +**Exit**: every issue below is closed, in the order listed. + +- `issues/kernel/no-test-can-hold-a-thread-on-a-named-cpu.md` +- `issues/hardware/no-program-measures-toyos-against-linux-on-one-machine.md` +- `issues/kernel/user-programs-use-avx-under-xsave.md` +- `issues/build/no-gate-decodes-kernel-elfs-instructions.md` +- `issues/kernel/kernel-forward-copies-and-fills-are-one-rep-movsb-or-stosb-on-every-cpu.md` +- `issues/kernel/the-direct-map-uses-1-gib-leaves-where-the-cpu-has-them.md` +- `issues/kernel/a-tlb-shootdown-reaches-only-the-cpus-that-ran-the-space.md` +- `issues/kernel/fsgsbase-is-measured-then-taken-or-rejected.md` +- `issues/kernel/idle-cpus-enter-the-c-states-cst-names.md` +- `issues/hardware/blockd-drives-nvme-apst.md` diff --git a/issues/kernel/tsx-stays-as-firmware-left-it.md b/issues/kernel/tsx-stays-as-firmware-left-it.md index 27078c01f56..a2bd73517cd 100644 --- a/issues/kernel/tsx-stays-as-firmware-left-it.md +++ b/issues/kernel/tsx-stays-as-firmware-left-it.md @@ -15,10 +15,12 @@ T14's `CONFIG_X86_INTEL_TSX_MODE_OFF=y`, runs `tsx_init` (`tsx.c:139-156`), clears enumeration where RTM_ALWAYS_ABORT (`tsx.c:108-137,170-176`), and otherwise, where `ARCH_CAPABILITIES.TSX_CTRL_MSR`, sets `RTM_DISABLE | TSX_CPUID_CLEAR` -(`tsx.c:23-41,189-226`). A row of the hardening table in -`issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md`. +(`tsx.c:23-41,189-226`). -**Exit**: S1 of that track carries `tsx_init`'s decision and every CPU applies -it; on the T14, ToyOS's reads of 0x122, 0x10F and 0x123 equal S0's Linux reads +**Exit**: +`issues/kernel/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +carries `tsx_init`'s decision and every CPU applies it; on the T14, ToyOS's +reads of 0x122, 0x10F and 0x123 equal the Linux reads of +`issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-are-not-committed.md` where each exists, and dropping the write leaves 0x122 or 0x10F at firmware's value and reds it. diff --git a/issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md b/issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md new file mode 100644 index 00000000000..b0e4ac6ecc8 --- /dev/null +++ b/issues/kernel/user-pointer-checks-have-no-spectre-v1-fence-and-smap-is-optional.md @@ -0,0 +1,21 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# User-pointer checks have no Spectre v1 fence, and SMAP is optional + +`user_ptr`'s range checks carry no `lfence` and no index mask, and SMAP is an +optional bit of the CR4 declaration +(`kernel/src/arch/x86_64/control_regs.rs:60`), so a CPU without it boots, and +a mispredicted check can load through a user pointer. Linux at +`Ubuntu-6.8.0-142.142` reports `usercopy/swapgs barriers and __user pointer +sanitization`. + +**Exit**: SMAP is required and a boot without it is refused by name; on every +proving machine a gadget through each `user_ptr` site recovers a planted byte +no more often than chance, 1 in 256, by a bound the stage derives over 1000 +trials from the same gadget's unfenced rate on that machine. **Mutation**: +SMAP optional again; a site's fence or mask removed exceeds the bound. +**Oracle**: the CPU's own speculation. diff --git a/issues/kernel/user-programs-have-no-protection-keys.md b/issues/kernel/user-programs-have-no-protection-keys.md new file mode 100644 index 00000000000..06a756d2652 --- /dev/null +++ b/issues/kernel/user-programs-have-no-protection-keys.md @@ -0,0 +1,24 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# User programs have no protection keys + +`CR4.PKE` is never set and no mapping carries a key, so a thread cannot fence +part of its own memory off from itself. Linux at `Ubuntu-6.8.0-142.142` builds +with `X86_INTEL_MEMORY_PROTECTION_KEYS` +(`debian.master/config/annotations:15334`). Its ABI is approved in principle +and shaped in this issue's pull request, which lands after +`issues/kernel/user-programs-use-avx-under-xsave.md`, since PKRU is XSAVE +state. + +**Exit**: under TCG with `+pku` (`target/i386/cpu.c:992` at QEMU v11.1.1), on +the T14, and on each EPYC guest that enumerates PKU: a write under a key the +thread denied is `#PF` with PK set, and a sibling thread's is not; `window` +refuses a `read(2)` into a page under a key the thread write-denied, and a +`write(2)` from a buffer under a key it access-denied. **Mutation**: no key in +the entry; one PKRU for all threads; `window` without the key check; `window` +checking the key only on `Access::Write`, which the `write(2)` reds. +**Oracle**: TCG and the T14 on SDM Vol. 3A §5.6.2. diff --git a/issues/kernel/user-programs-run-without-a-shadow-stack.md b/issues/kernel/user-programs-run-without-a-shadow-stack.md index ba44987e0c4..8900397773f 100644 --- a/issues/kernel/user-programs-run-without-a-shadow-stack.md +++ b/issues/kernel/user-programs-run-without-a-shadow-stack.md @@ -12,7 +12,15 @@ checked against a hardware-maintained copy that ordinary writes cannot reach, so a corrupted return address on the data stack is refused rather than taken. ToyOS builds no user shadow stack: no program can enable one, and nothing in `kernel/src` maps a shadow-stack page or sets `CR4.CET`/`MSR_IA32_U_CET`. +ToyOS does not make it opt-in: the loader refuses every program and library +without `GNU_PROPERTY_X86_FEATURE_1_SHSTK`, on every CPU, with or without +shadow-stack hardware. -**Exit**: a process can have a shadow stack of its own, the kernel maps and -enables one for it, and a `RET` to a return address forged on the data stack -is refused rather than taken. +**Exit**: the guest test `unmarked_object_refused` passes on every proving +machine and under TCG; a sysroot link with one unmarked input reds, since lld +ANDs the property; on the T14, the one proving machine known to enumerate +shadow stacks, a forged return is `#CP` in two threads, a caught panic +unwinds, a store to the shadow stack is `#PF`, and `user_ptr` refuses a `read` +into it with `BadAddress`. **Mutation**: the property check gone; `SH_STK_EN` +clear; one shadow stack for two threads; no `incssp`; the stack mapped +writable or faulted in as CoW. **Oracle**: the T14's CPU. diff --git a/issues/kernel/user-programs-use-avx-under-xsave.md b/issues/kernel/user-programs-use-avx-under-xsave.md new file mode 100644 index 00000000000..cbaeab370b4 --- /dev/null +++ b/issues/kernel/user-programs-use-avx-under-xsave.md @@ -0,0 +1,22 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# User programs use AVX to AVX-512 under XSAVE + +`CR4.OSXSAVE` stays clear and a thread's state is its `FXSAVE64` image +(`kernel/src/arch/x86_64/fpu.rs:6-7`), so AVX is `#UD` in every user program. +The kernel enables every vector component the CPU enumerates and saves them +by one path on every CPU, XSAVEOPT, refusing by name a CPU without it: TCG +implements neither XSAVEC nor XSAVES (`target/i386/cpu.c:1012-1014` at QEMU +v11.1.1), and the CET state of +`issues/kernel/user-programs-run-without-a-shadow-stack.md` is switched by +`wrmsr`, as `fs_base` is. The TCG model gains `+xsave,+xsaveopt,+avx,+avx2`, +which TCG implements (`cpu.c:905,907,981,1012`). + +**Exit**: on every proving machine and under TCG, threads that fill their +vector registers differently each find their own after a switch; the T14's +ChaCha20-Poly1305 figure. **Mutation**: a save mask short of Hi16_ZMM, and +under TCG of YMM. **Oracle**: TCG and the T14 on SDM Vol. 1 §13. diff --git a/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md b/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md new file mode 100644 index 00000000000..1e6b8e4fef2 --- /dev/null +++ b/issues/kernel/whether-the-kernel-loads-cpu-microcode-is-the-owners.md @@ -0,0 +1,14 @@ +--- +status: owner +kind: question +opened: 2026-09-29 +--- + +# Whether the kernel loads CPU microcode is the owner's + +Microcode is firmware that runs on the CPU, which root `CLAUDE.md` does not +carve out: it admits only device firmware that never executes on the CPU. Until +the kernel loads it, a CPU whose BIOS ships stale microcode stays below Linux +at `Ubuntu-6.8.0-142.142` on every line that rests on microcode. + +**Exit**: the owner rules.