Skip to content
Draft
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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,40 @@ kernel/microcode/intel-ucode/* — Intel microcode, redistributable unmodified
sha256 03efb1491c7e899feb2665fa299363e64035e5444c1b8bc1f6ebed30de964e12)
SPDX-License-Identifier: LicenseRef-Intel-Microcode

The kernel embeds 06-8c-01 and loads it on the CPU it names; 06-cc-02 is
`toyos-microcode`'s test input. The licence permits redistribution in binary
form, unmodified, on three conditions: the copyright notice and the
disclaimer are reproduced with the distribution, which every x86-64 image does
as /system/share/licenses/Intel-microcode-license.txt; Intel's name endorses
nothing derived from it; and no reverse engineering, decompilation or
disassembly.


kernel/microcode/amd-ucode/* — AMD microcode, redistributable unmodified
------------------------------------------------------------------------

microcode_amd_fam17h.bin 22,596 bytes, a container of seven patches for
family 0x17 CPUs, 0x0860010d for CPUID 0x00860f01 (Renoir, the
AMD bench's Ryzen 7 4800U) among them
sha256 966e4b796ec689c618868d08f8a37f347b0e7bfce4ae9df793e08471d363b7d0
Upstream: https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git,
`amd-ucode/` at tag 20260916
(commit ab23307cfe7f9366c819025ca3e4778299bc2db2), byte for byte
Licence text: licenses/AMD-ucode-license.txt (upstream's
`LICENSES/LICENSE.amd-ucode`, sha256
2103bd999f77522c5ab5fd35df0579ed00e2a6b885f6f8ebd444e97ffa482991)
SPDX-License-Identifier: LicenseRef-AMD-ucode

The kernel embeds it whole and loads, on a CPU one of its patches names, that
patch. The licence grants redistribution free of fees on five terms: the
copyright notice and the licence travel with the software, which every x86-64
image does as /system/share/licenses/AMD-ucode-license.txt; AMD is not
referenced in promoting a product in a way that implies AMD endorses or
certified it; no reverse engineering, decompilation or disassembly; the rights
to third-party technologies the software may implement are the
redistributor's to obtain, AMD conveying none; and U.S. and European export
law binds whoever uses it.


tests/cavp/ — NIST's SHA-2 test vectors, CAVP SHAVS
----------------------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,14 @@ pub const TRUST_ROOTS_LICENCE: (&str, &[u8]) = (
include_bytes!("../licenses/CDLA-Permissive-2.0-webpki-root-certs.txt"),
);

/// Where on ROOT the licence texts of the CPU microcode an x86-64 kernel
/// embeds are, and the texts: each maker's licence asks that a redistribution
/// carry it.
pub const MICROCODE_LICENCES: &[(&str, &[u8])] = &[
("share/licenses/Intel-microcode-license.txt", include_bytes!("../licenses/Intel-microcode-license.txt")),
("share/licenses/AMD-ucode-license.txt", include_bytes!("../licenses/AMD-ucode-license.txt")),
];

/// The Mozilla root program's authorities as `webpki-root-certs` publishes
/// them, each certificate a PEM block in the published order.
pub fn trust_roots() -> Vec<u8> {
Expand Down Expand Up @@ -835,6 +843,11 @@ fn build_and_assemble(

root_files.push((TRUST_ROOTS.to_string(), trust_roots()));
root_files.push((TRUST_ROOTS_LICENCE.0.to_string(), TRUST_ROOTS_LICENCE.1.to_vec()));
let microcode = match arch {
Arch::X86_64 => MICROCODE_LICENCES,
Arch::Aarch64 => &[],
};
root_files.extend(microcode.iter().map(|(path, text)| (path.to_string(), text.to_vec())));

// Extra files (test binaries, shared libs)
for (name, data) in extra_files {
Expand Down
34 changes: 29 additions & 5 deletions build/licence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,20 @@ const FONTS_ONLY: &[&str] = &["OFL-1.1"];
/// CDLA-Permissive-2.0 for the Mozilla roots.
const DATA_ONLY: &[&str] = &["CDLA-Permissive-2.0"];

/// What a licence is judged as: [`FONTS_ONLY`] passes only for a font and
/// [`DATA_ONLY`] only for the trust roots.
/// Allowed for a [`Terms::Firmware`] row, and nowhere else: the owner's
/// ruling that vendor firmware a device or CPU verifies by its maker's
/// signature may ship, pinned and unmodified (root `CLAUDE.md`).
const FIRMWARE_ONLY: &[&str] = &["LicenseRef-Intel-Microcode", "LicenseRef-AMD-ucode"];

/// What a licence is judged as: [`FONTS_ONLY`] passes only for a font,
/// [`DATA_ONLY`] only for the trust roots and [`FIRMWARE_ONLY`] only for
/// vendor firmware.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Scope {
Code,
Font,
Data,
Firmware,
}

impl Scope {
Expand All @@ -103,6 +110,7 @@ impl Scope {
Scope::Code => false,
Scope::Font => FONTS_ONLY.contains(&id),
Scope::Data => DATA_ONLY.contains(&id),
Scope::Firmware => FIRMWARE_ONLY.contains(&id),
}
}
}
Expand All @@ -119,12 +127,15 @@ pub enum Terms {
/// A font's glyphs, or a raster of them, under an SPDX expression: the one
/// place [`FONTS_ONLY`] passes.
Font(&'static str),
/// Vendor firmware a device or CPU verifies by its maker's signature,
/// under an SPDX expression: the one place [`FIRMWARE_ONLY`] passes.
Firmware(&'static str),
}

impl Terms {
fn expr(self) -> &'static str {
match self {
Terms::Spdx(e) | Terms::Font(e) => e,
Terms::Spdx(e) | Terms::Font(e) | Terms::Firmware(e) => e,
}
}
}
Expand Down Expand Up @@ -577,13 +588,19 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[
"kernel/microcode/intel-ucode/06-8c-01",
"efe83e312b90f7fe4b8f75260087edf03e048d2f4f80caef2ef631c842714bb3",
"NOTICE",
Terms::Spdx("LicenseRef-Intel-Microcode"),
Terms::Firmware("LicenseRef-Intel-Microcode"),
),
(
"kernel/microcode/intel-ucode/06-cc-02",
"4e43bb4d23c3638f8c16967d61e8f6456ae0da2ddd1da82ab579a50715147bb1",
"NOTICE",
Terms::Spdx("LicenseRef-Intel-Microcode"),
Terms::Firmware("LicenseRef-Intel-Microcode"),
),
(
"kernel/microcode/amd-ucode/microcode_amd_fam17h.bin",
"966e4b796ec689c618868d08f8a37f347b0e7bfce4ae9df793e08471d363b7d0",
"NOTICE",
Terms::Firmware("LicenseRef-AMD-ucode"),
),
];

Expand Down Expand Up @@ -1136,6 +1153,7 @@ fn judge_files(ledger: &[Row], tracked: &[String], shipping: &Shipping, report:
let scope = match terms {
Terms::Spdx(_) => Scope::Code,
Terms::Font(_) => Scope::Font,
Terms::Firmware(_) => Scope::Firmware,
};
report.judge(Finding::at(Kind::File, path, terms.expr(), String::new()), scope);
}
Expand Down Expand Up @@ -1493,6 +1511,12 @@ mod tests {
assert!(verdict_of("CDLA-Permissive-2.0").is_some(), "CDLA passed outside data");
assert!(judge_licence("CDLA-Permissive-2.0", Scope::Font).is_some(), "CDLA passed for a font");
assert_eq!(judge_licence("CDLA-Permissive-2.0", Scope::Data), None);
for firmware in FIRMWARE_ONLY {
assert!(verdict_of(firmware).is_some(), "{firmware} passed outside firmware");
assert!(judge_licence(firmware, Scope::Font).is_some(), "{firmware} passed for a font");
assert_eq!(judge_licence(firmware, Scope::Firmware), None);
}
assert!(judge_licence("OFL-1.1", Scope::Firmware).is_some(), "OFL passed as firmware");
assert_eq!(verdict_of("MPL-2.0"), None);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
status: open
kind: defect
opened: 2026-10-12
---

# A panic before the reset register is read holds the machine for good

On x86-64 the kernel learns its reset from the FADT in `power::init_reset`,
which `arch::boot::interrupts` calls, well after `arch::boot::after_console`
(`kernel/src/main.rs`). A panic before it finds
`power::can_reboot` false, and `panic_reboot::arm` returns `Bound::Held`:
the panel is held until somebody cuts the power. On the AMD bench every panic
before `clock::init` (`arch::boot::clock`) is held as well, whatever the
reset: its CPU states no counter frequency (CPUID leaves 0x15 and 0x16), so
`panic_reboot::deadline` has nothing to time the bound with and `arm` takes
its "no clock" arm.

Everything `after_console` runs is in that window, the BSP's microcode load
among them. `issues/a-fatal-event-stands-down-both-bounds-and-may-leave-nothing-to-end-the-machine.md`
covers a later window, where a reset is known and both bounds stand down.

**Evidence.** #886's two negative-control arms panicked at the microcode
read-back inside `after_console`, and both benches had to be power-cycled by
hand: the T14 armed with `microcode-reseal` and the AMD bench with its
trigger write removed. The AMD bench's log partition, read after the power
cycle, carries the failed read-back; the T14's cannot tell the panic from a
fault at the trigger
(https://github.com/ToyOSOrg/ToyOS/pull/886#issuecomment-6131006022).

Owner: the orchestrator.

**Exit**: a T14 boot and an AMD bench boot armed with `test-early-panic`,
which panics right after `after_console`, each reset themselves with nobody
at the machine, and the boot after it reports the panic.
90 changes: 22 additions & 68 deletions issues/the-kernel-loads-no-cpu-microcode.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,31 @@
---
status: open
status: assigned
kind: defect
opened: 2026-09-29
---

# The kernel loads no CPU microcode

The kernel is to load CPU microcode signed by the CPU's maker, pinned by
version and hash the way vendor device firmware is (owner, 2026-09-30). It
loads none, so 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 kernel loads current microcode early on every CPU, at least as
current as Linux's.

`toyos-microcode` validates an Intel update file and picks the update for one
CPU; nothing calls it. The T14's eight CPUs run 0xbe from its firmware, which
is Intel's newest for them at `microcode-20260925`, so a load there is a no-op.

**Where.** The kernel, on every CPU, from a file it embeds per CPU ToyOS
supports on metal. Not the loader: it runs on the BSP alone, reaching the APs
takes EFI MP Services, and the kernel reads every CPU's revision anyway. In
`percpu::init_bsp` after `idt::init` and in `percpu::init_ap` after
`control_regs::init`, before `fpu::init` on each: after the IDT, so a load that
faults reports on this kernel's channels; before anything acts on an
enumeration an update changes (CPUID.7.0:EDX, `IA32_ARCH_CAPABILITIES`, RTM
and HLE); and before `ROSTER.echo`, so `boot_aps` starting one AP at a time is
the serialisation per core that SDM Vol. 3A §12.11.6.3 asks. A CPU with
CPUID.1:ECX[31] set loads nothing, as Linux does, and `IA32_PLATFORM_ID` is
read on a GenuineIntel CPU only. The update data starts 16-byte aligned (§12.11.6),
and the trigger is an `asm!` of its own: `cpu::wrmsr` is `nomem`, and the CPU
reads the update through this write. INIT keeps an update; a hard reset clears
it (§12.11.6.1).

**Verified.** After the trigger the CPU writes 0 to `IA32_BIOS_SIGN_ID`, runs
CPUID.01H, and panics unless it reads back the update's revision (Example
12-10). After `boot_aps`, CPUs that report different revisions panic the boot.
Each CPU logs its platform, the revision it found and the one it runs. It
stops rather than run the firmware's revision behind a log line: the file is
pinned and chosen by the CPU's own signature, platform and revision, so a CPU
that does not take it means one of those is wrong, a ToyOS defect, and a boot
that went on would run below the revision its image claims with nothing red.

**AMD.** linux-firmware's `amd-ucode/microcode_amd_fam{17,19,1a}h.bin` is a
container (magic 0x00414d44): an equivalence table from CPUID.01H:EAX to a
processor ID, then patches. MSR C001_0020 takes a patch's address, and MSR 8B
must then read back its patch ID; family 0x17 also invalidates the patch's
pages (Linux `amd.c`, `__apply_microcode_amd`). On families 0x17, 0x19 and part
of 0x1a below a per-CPU cutoff revision the CPU's own signature check is broken
(EntrySign; `cpu_has_entrysign`, `need_sha_check`), so the hash pin is the only
check, as `amd_shas.c` is Linux's. linux-firmware's `LICENSE.amd-ucode` is
unread. ToyOS has no AMD metal: CI's EPYCs are KVM guests, which load
nothing.

**Licence.** `LicenseRef-Intel-Microcode` is in no `ALLOWED` row of
`build/licence.rs`, and a committed file ships once a shipped package's directory
holds it. So the commit that makes the kernel depend on `toyos-microcode` reds
the licence gate on both files under `kernel/microcode/intel-ucode/`, the
test-only `06-cc-02` included, whatever the kernel embeds, until an exception
scoped to CPU microcode admits them.

Each step's exit, in the testing ladder's order:

- host: the step's decisions are pure in `toyos-microcode`, each refused by
name and each red under its mutation: a hypervisor or a vendor no file
covers, `select`, a read-back that is not the update's revision, CPUs that
disagree;
- metal: a T14 boot logs platform 7 and 0xbe current on all eight CPUs; a
`boot-actuators` arm that raises only the header's revision to 0xbf and
reseals its checksum stops that boot at the read-back or at a fault, since
Intel's payload still says 0xbe;
- metal, on no machine ToyOS has: the load arm, on a CPU whose firmware runs
older microcode than the embedded file; AMD's loader, on AMD metal;
- guest: every CPU logs why it loads nothing, a hypervisor under KVM and a
vendor no file covers under TCG's `AuthenticAMD` `qemu64`, and a guest test
version and hash the way vendor device firmware is (owner, 2026-09-30).
Pull request #886 builds the loader (`toyos-microcode`,
`kernel/src/arch/x86_64/microcode.rs`) and deletes this file once its exit
holds on both benches.

**Exit**: the kernel loads, early on every CPU, the newest update its
maker's embedded file holds for it, and a CPU that does not take one says so
and runs what it reads back.

- host: every decision `toyos-microcode` takes is refused by name and red
under its mutation;
- metal: the `microcode` row is green on the T14, whose eight CPUs run
Intel's 0xbe for 0x806c1 platform 7, and on the AMD bench, whose CPUs each
run the embedded container's 0x0860010d; the T14 armed with
`microcode-reseal`, and the AMD bench with its trigger write removed, each
complete the boot and red the row on a `refused` line;
- guest: every guest loads nothing, for its CPU reports a hypervisor. QEMU
11.1.1 sets CPUID.01H:ECX[31] under TCG as under KVM, read over QMP from a
paused `qemu64` with the suite's own `-cpu` string
(https://github.com/ToyOSOrg/evidence/blob/499ad40d53eca38cfe6eb7c3689088ee57d9402e/design-measurements.txt#L256-L269),
so the guest suite booting green is the whole guest exit, and no guest test
asserts the line.
1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ toyos-gicv3 = { path = "gicv3" }
toyos-gpt = { path = "../lib/gpt" }
toyos-hda = { path = "../lib/hda" }
toyos-its = { path = "its" }
toyos-microcode = { path = "microcode" }
toyos-pci = { path = "pci" }
toyos-phys = { path = "phys" }
toyos-smmu = { path = "smmu" }
Expand Down
5 changes: 4 additions & 1 deletion kernel/microcode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

[package]
name = "toyos-microcode"
description = "Intel microcode update files validated and matched to a CPU as the SDM's Microcode Update Facilities define them, pure."
description = "Intel's microcode update files and AMD's containers validated and matched to a CPU as each maker reads them, whether a CPU loads at all, and the files the kernel embeds, pure."
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false

[dependencies]
toyos-cpuvuln = { path = "../cpuvuln" }
Binary file not shown.
Loading