diff --git a/Cargo.lock b/Cargo.lock index 1c2f33a3299..ca2cbd3f6a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1828,7 +1828,6 @@ dependencies = [ "toyos-blackbox", "toyos-blockhold", "toyos-bootmap", - "toyos-cpuvuln", "toyos-dma", "toyos-elf", "toyos-fat32", @@ -4152,10 +4151,6 @@ dependencies = [ "webpki-root-certs", ] -[[package]] -name = "toyos-cpuvuln" -version = "0.1.0" - [[package]] name = "toyos-desktop" version = "0.1.0" @@ -4319,9 +4314,6 @@ dependencies = [ [[package]] name = "toyos-microcode" version = "0.1.0" -dependencies = [ - "toyos-cpuvuln", -] [[package]] name = "toyos-mixer" diff --git a/Cargo.toml b/Cargo.toml index 1da5a159e1e..a5aef444ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ members = [ "build/ssh", "kernel", "kernel/amdvi", - "kernel/cpuvuln", "kernel/dma", "kernel/gicv3", "kernel/its", diff --git a/issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md b/issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md deleted file mode 100644 index cf5e78e241e..00000000000 --- a/issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -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/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md`, -and each 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/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md b/issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md new file mode 100644 index 00000000000..cadf2370b6a --- /dev/null +++ b/issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md @@ -0,0 +1,21 @@ +--- +status: open +kind: track +opened: 2026-09-29 +--- + +# A pure function decides a CPU's speculation mitigations from its vendor's guidance + +Nothing decides which speculative-execution mitigations a CPU needs. The +function to build maps a CPU's signature, microcode revision and enumeration +words to the mitigations Intel's or AMD's published affected-processor guidance +prescribes for it. Its enumeration words are CPUID 7.0 EDX, 0x80000008 EBX, +0x80000021 EAX and `IA32_ARCH_CAPABILITIES` where enumerated. #915 deleted +`toyos-cpuvuln`, which reproduced Linux's decision with no caller. + +**Exit**: +- A host test holds the function's verdict, for each proving machine's words, + to the vendor's published row for that CPU, committed with its provenance. +- Each proving machine's boot logs the words ToyOS read there. +- **Mutation**: a vendor row dropped reds that machine's test. +- **Oracle**: the vendor's table, and ToyOS's own read on the machine. diff --git a/issues/amd-erratum-1386-is-assumed-where-no-public-amd-revision-guide-clears-a-part.md b/issues/amd-erratum-1386-is-assumed-where-no-public-amd-revision-guide-clears-a-part.md new file mode 100644 index 00000000000..761f79e9b43 --- /dev/null +++ b/issues/amd-erratum-1386-is-assumed-where-no-public-amd-revision-guide-clears-a-part.md @@ -0,0 +1,33 @@ +--- +status: open +kind: tooling +opened: 2026-10-11 +--- + +# AMD erratum 1386 is assumed where no public AMD revision guide clears a part + +`kernel::xsave::erratum_1386` (`kernel/pure/xsave.rs`) refuses `XSAVES` to +the parts AMD's public revision guides list erratum 1386 against, "No fix +planned": ZP-B2 (55449 rev 1.21), Rome-B0 (56323 rev 1.01), Milan-B1 and -B2 +(56683 rev 1.08). It also refuses it to every other AMD family 17h and 19h part, +and to a family 18h part of any vendor but Intel, unless a guide clears it. +The cleared parts are ZP-B1 and PiR-B2 (55449) and Genoa-B1 (57095 rev 1.01, +which does not list the erratum). The rule rests on the cost asymmetry, not on +a document. With `IA32_XSS` 0, `XSAVEC` saves what `XSAVES` would, and only +the modified optimisation is lost. A wrong no hands a thread an older XMM value +of its own (orchestrator's ruling A on #915). + +Evidence, in [`amd-guides.md`](https://github.com/ToyOSOrg/evidence/blob/d4754efa08523eaddecf53dc8f9239b35e4b4268/design/amd-guides.md): +- every guide the Internet Archive holds under AMD's `processor-tech-docs/revision-guides/` prefix, hashed; +- 58251 and 58730, family 1Ah, are read too and do not list 1386; +- the guide for family 17h models 60h-6Fh, which PPR 55922 names as docRevG, is not public; +- `www.amd.com/system/files/TechDocs/` and `developer.amd.com/wp-content/resources/` were not searched. + +Family 1Ah models outside 00h-1Fh are unguided and not named: the ruling covers +families 17h to 19h. + +Owner: the orchestrator. + +**Exit**: a separate reader's clean-room specification of AMD's own code for +erratum 1386 settles the models no public guide covers, and the rule is +corrected to it. diff --git a/issues/amd-erratum-1386s-scope-rests-on-linux-where-amd-publishes-no-revision-guide.md b/issues/amd-erratum-1386s-scope-rests-on-linux-where-amd-publishes-no-revision-guide.md deleted file mode 100644 index f90f14a253d..00000000000 --- a/issues/amd-erratum-1386s-scope-rests-on-linux-where-amd-publishes-no-revision-guide.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-10-11 ---- - -# AMD erratum 1386's scope rests on Linux where AMD publishes no revision guide - -`toyos_cpuvuln::erratum_1386` (`kernel/cpuvuln/src/erratum.rs`) refuses -`XSAVES` to the parts AMD's public revision guides list erratum 1386 against: -family 17h ZP-B2 (55449 rev 1.21), Rome-B0 (56323 rev 1.01), and family 19h -Milan-B1 and -B2 (56683 rev 1.08). AMD publishes no guide for family 17h -models 10h-2Fh, 40h-7Fh, 90h-91h or A0h-AFh, the client parts, the AMD -bench's model 60h among them; there the scope is the models Linux files as -Zen1 or Zen2 (`arch/x86/kernel/cpu/amd.c:482-492` at upstream `87db7795`), -a secondary source. - -Evidence, each guide fetched from the Internet Archive's copy of -`www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/` -(`www.amd.com` itself answered neither HTTP/2 nor HTTP/1.1 from the -development host): -- the four guides above list the revisions named, and each says "No fix - planned"; 57095 rev 1.01 (family 19h models 10h-1Fh) does not list 1386; -- the guides archived beside them, 58251 and 58730, set their text in fonts a - stream-level text extraction does not decode, and were not read; -- Linux departs from AMD in three places: it names every Zen1 model where - 55449 clears ZP-B1 and PiR-B2, it lifts the erratum on ZP-B2 from microcode - `0x0800126e` and on Rome-B0 from `0x08301052` (`amd.c:881-903`), which no - guide states, and it does not name Milan. The kernel follows the guides on - all three. - -Owner: the orchestrator. - -**Exit**: AMD's own statement of erratum 1386 for every family 17h model the -table names from Linux, and for 58251's and 58730's parts, read and the table -corrected to it; or the table cut to the guides' revisions alone, on the -owner's ruling that a part no guide names keeps `XSAVES`. diff --git a/issues/general-crates-are-published-and-toyos-consumes-them.md b/issues/general-crates-are-published-and-toyos-consumes-them.md index 26deaa1f477..f6a1c4e25a6 100644 --- a/issues/general-crates-are-published-and-toyos-consumes-them.md +++ b/issues/general-crates-are-published-and-toyos-consumes-them.md @@ -14,10 +14,9 @@ opened: 2026-10-11 **Candidates** (scan of the restructured tree, 2026-10-11), best first: 1. `toyos-fat32-check`: FAT32 checker written from Microsoft's fatgen103. No ToyOS dependencies, `no_std`. 2. `toyos-fat32`: FAT32 read/write hardened against hostile volumes. It needs `toyos-wallclock` folded in, and its tests made self-contained. -3. `toyos-cpuvuln`: Linux's CPU vulnerability verdicts from CPUID/MSR facts. Its parity is pinned to one Linux tag. -4. The bcachefs `upstream` reader, split out of `lib/bcachefs`. -5. `toyos-microcode` and `toyos-usbhid`. -6. Later: the TCP stack (once `wire`'s grab-bag is factored out), the SSH server (once its track and defect issues close), and the AML interpreter (once it is decoupled from `toyos-acpi`). +3. The bcachefs `upstream` reader, split out of `lib/bcachefs`. +4. `toyos-microcode` and `toyos-usbhid`. +5. Later: the TCP stack (once `wire`'s grab-bag is factored out), the SSH server (once its track and defect issues close), and the AML interpreter (once it is decoupled from `toyos-acpi`). **Not candidates:** sha2/sha2-hw, random, crc, wallclock and tmpdir. Widely used crates already do their jobs. diff --git a/issues/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md b/issues/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md index 89d78ed2fb8..4b6ad61c4fe 100644 --- a/issues/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md +++ b/issues/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md @@ -6,27 +6,15 @@ opened: 2026-09-29 # Linux's readings of the T14 and the TCG model lack reads owed before the T14's wipe -The floor of -`issues/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 TCG model are the fixtures -of `issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md`, -`kernel/cpuvuln/fixtures/`'s `t14.txt`, `t14/` and `tcg/`; -`the_t14s_facts_are_linuxs_reading_of_it` and -`the_tcg_models_signature_gives_its_lines` hold `T14` and `TCG` to them. +The CPU readings this issue owed went with `toyos-cpuvuln` (#915); the +track's pin is in +`issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md`. Ubuntu leaves the T14 only after this issue and `issues/no-program-measures-toyos-against-linux-on-one-machine.md` close. -**Exit**: before the wipe the T14's readings add CPUID 5, 0x19 and -0x80000001, MSR 0xCF, MSR 0x3A (`rdmsr -a 0x3a`, IA32_FEAT_CTL), the -split-lock line, and the config's `X86_KERNEL_IBT` and -`X86_INTEL_MEMORY_PROTECTION_KEYS`, each committed with the test that reads -it, and `the_t14s_facts_are_linuxs_reading_of_it` holds `T14`'s `feat_ctl` -to 0x3A's. **Mutation**: an added reading off by one bit reds the test that -reads it. **Oracle**: that Linux. +**Exit**: `wt/toyos-linux-sweep`, which holds this section and this slug +under #915's fence, settles the turbostat readings below. ## Linux's counter readings, read 2026-10-03 @@ -35,9 +23,7 @@ hardware counters (`issues/toyos-explains-itself.md`). Read as root under Ubuntu's `6.8.0-142-generic` from 16:22:37 UTC, one after the other, and committed whole with their commands in the `SOURCE` beside them: turbostat idle and under one `yes` per CPU in `tests/metal/t14-linux/`, which -`counters_on_metal` prints ToyOS's reading beside, and `perf stat` over the -msr PMU in `kernel/cpuvuln/fixtures/t14/perf-msr.txt`, which -`the_t14_has_what_linux_counted_on_it` holds the T14's counter verdict to. +`counters_on_metal` prints ToyOS's reading beside. Idle, per 10 s: busy 0.11 to 0.36%, busy clock 980 to 1668 MHz, TSC 2419 MHz, SMI 0. Loaded: busy 99.77%, busy clock 3800 MHz for the first two intervals, 3555 MHz in the third, then 3075 to 3094 MHz; SMI 0. diff --git a/issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md b/issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md index 2a3bef40fa6..e6be9a43d1d 100644 --- a/issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md +++ b/issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md @@ -20,7 +20,7 @@ the ask is the flag of `issues/no-gate-decodes-kernel-elfs-instructions.md`, matches it against both of Linux's sequences; on every proving machine probes count what -`issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.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 diff --git a/issues/no-kvm-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md b/issues/no-kvm-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md deleted file mode 100644 index 79a5bfb9181..00000000000 --- a/issues/no-kvm-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-09-29 ---- - -# No KVM runner has had its CPUID and vulnerability lines captured - -`toyos-cpuvuln`'s `fixtures/awaiting-capture/` holds the lines this crate -reads out of the pinned Linux for an EPYC Milan and an EPYC Turin as KVM -guests, from bare-metal CPUID dumps with the hypervisor bit set. Which bits -KVM passes through, whether it enumerates `ARCH_CAPABILITIES`, and whether the -host's microcode gives `IBPB_BRTYPE` are guesses there. In nightly run -36496779560 the 14 KVM shards ran on EPYC 7763 (11), 9V74 (2) and 9V45 (1), -each as `-cpu host,+rdrand,+smap,+fsgsbase,+x2apic,+smep` with `-smp cores=N`. - -**Exit**: a CI step given `/dev/kvm` boots the pinned Linux under that command -line on its runner and records CPUID leaves 0, 1, 7.0, 7.2, 0x80000000, 0x80000008 and -0x80000021, MSR 0x10A where enumerated, the microcode revision, and `grep .` over -`/sys/devices/system/cpu/vulnerabilities/`; the two fixtures are replaced by -what it records, and one is added per CPU model it meets. diff --git a/issues/no-kvm-runners-cpuid-has-been-read-by-toyos.md b/issues/no-kvm-runners-cpuid-has-been-read-by-toyos.md new file mode 100644 index 00000000000..9d974628870 --- /dev/null +++ b/issues/no-kvm-runners-cpuid-has-been-read-by-toyos.md @@ -0,0 +1,18 @@ +--- +status: open +kind: tooling +opened: 2026-09-29 +--- + +# No KVM runner's CPUID has been read by ToyOS + +The nightly guest suite's KVM shards run on EPYC 7763, 9V74 and 9V45 CPUs +(nightly run 36496779560), each as `-cpu host,+rdrand,+smap,+fsgsbase,+x2apic,+smep` +with `-smp cores=N`. Which bits KVM passes through, and whether it enumerates +`IA32_ARCH_CAPABILITIES`, are unknown: no ToyOS boot on a runner has logged +them. + +**Exit**: a ToyOS guest boot on each runner CPU model logs CPUID leaves 0, 1, +7.0, 7.2, 0x80000000, 0x80000008 and 0x80000021, MSR 0x10A where it is +enumerated, and the microcode revision. One fixture per model is committed for +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md`. diff --git a/issues/no-program-runs-with-speculative-store-bypass-disabled.md b/issues/no-program-runs-with-speculative-store-bypass-disabled.md index 9ef087e7da4..63cb49387f8 100644 --- a/issues/no-program-runs-with-speculative-store-bypass-disabled.md +++ b/issues/no-program-runs-with-speculative-store-bypass-disabled.md @@ -13,6 +13,6 @@ The kernel never sets SSBD, so no process can have it. Linux at **Exit**: on every proving machine a flagged program runs with SSBD set, through the control -`issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.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/no-rule-decides-l1tf-mitigation-for-an-affected-intel-cpu.md b/issues/no-rule-decides-l1tf-mitigation-for-an-affected-intel-cpu.md new file mode 100644 index 00000000000..66a65a125ec --- /dev/null +++ b/issues/no-rule-decides-l1tf-mitigation-for-an-affected-intel-cpu.md @@ -0,0 +1,16 @@ +--- +status: open +kind: defect +opened: 2026-09-29 +--- + +# No rule decides L1TF's mitigation for an affected Intel CPU + +Nothing in the kernel decides whether a CPU is affected by L1 Terminal Fault +or what mitigates it. Intel's L1TF guidance prescribes the mitigation for the +CPUs it lists as affected. No proving machine is known to be one. + +**Exit**: the function of +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md` +decides L1TF's mitigation from Intel's L1TF guidance for every CPU Intel lists +as affected. A host test holds that decision to the document. diff --git a/issues/spec-ctrl-and-gds-stay-as-firmware-left-them.md b/issues/spec-ctrl-and-gds-stay-as-firmware-left-them.md index aa0566410c0..5148631a608 100644 --- a/issues/spec-ctrl-and-gds-stay-as-firmware-left-them.md +++ b/issues/spec-ctrl-and-gds-stay-as-firmware-left-them.md @@ -14,7 +14,7 @@ unmitigated if firmware left `GDS_MITG_DIS` set. Linux at (`arch/x86/kernel/cpu/bugs.c:89,777`). **Exit**: on every proving machine each CPU's 0x48 holds what -`issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.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 diff --git a/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md b/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md index d300e94b364..b6cb542d86e 100644 --- a/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md +++ b/issues/t14-rows-hold-toyos-to-linuxs-choice-or-copy.md @@ -8,17 +8,14 @@ opened: 2026-10-10 `reviewer.md`'s **Tests**: a policy another OS chose is never an oracle for what ToyOS's own setting must be, and its copy of a fact a primary source states is never an oracle. Two metal -rows in `tests/toyos.rs` use one: +rows in `tests/toyos.rs` used one. `counters_on_metal`'s, the request Linux's governor chose, +went in #915; the other remains: -- `counters_on_metal` holds every CPU's read-back `IA32_HWP_REQUEST` equal to Linux's - (`tests/metal/t14-linux/hwp-request.txt`). That value is the request Linux's governor chose, a policy, - not a correctness truth. - `acpi_tables_on_metal` holds the number of definition blocks the server loaded to `T14_DEFINITION_BLOCKS`, the 14 Linux reports loading. The firmware's XSDT and FADT, which the kernel already reads, are the primary source of that count. Owner: the orchestrator, which dispatches the reshape. -**Exit condition**: `counters_on_metal` holds the request to the value the kernel declares, with -no reference to `hwp-request.txt`; `acpi_tables_on_metal` holds the count to the definition blocks +**Exit condition**: `acpi_tables_on_metal` holds the count to the definition blocks the same boot's firmware tables list, and `T14_DEFINITION_BLOCKS` is deleted. diff --git a/issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md b/issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md index 74564696d80..cb8a0c2924e 100644 --- a/issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md +++ b/issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md @@ -6,7 +6,10 @@ 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, +Parity with Linux at tag `Ubuntu-6.8.0-142.142` +(53e5d07aac028a1523ab0b115f079d6d1bc831ef) of +`https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/noble`, +config sha256 3b8533dd9d235ca634ac58f82c5ce1ee35f12ef620693e17033184d2c9ca5890, 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 AMD EPYC KVM guests; the PR gate's TCG model proves wiring only. A @@ -16,10 +19,11 @@ One line of Linux's is declined, and ToyOS stays below it there: the firmware's memory-overwrite request, by the owner's "Keep crash records" (`issues/the-loader-never-sets-the-firmwares-memory-overwrite-request.md`). -**Exit**: every issue below is closed, in the order listed. +**Exit**: every issue below is closed, in the order listed. The T14's Linux +readings issue is not among them: since #915 it owes no reading this track's +work needs. -- `issues/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md` -- `issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +- `issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md` - `issues/spec-ctrl-and-gds-stay-as-firmware-left-them.md` - `issues/no-program-runs-with-speculative-store-bypass-disabled.md` - `issues/no-gate-decodes-kernel-elfs-instructions.md` diff --git a/issues/the-kernel-owns-cpu-performance-state.md b/issues/the-kernel-owns-cpu-performance-state.md index df1dceab0ce..06fd0d3626e 100644 --- a/issues/the-kernel-owns-cpu-performance-state.md +++ b/issues/the-kernel-owns-cpu-performance-state.md @@ -18,7 +18,7 @@ or read at boot by the declaration's own check before a read can reach it. Declared and read back: the HWP request, its package's and the energy/ performance bias, on every CPU that has all of them -(`toyos_cpuvuln::hwp`). Still to build: +(`kernel::cpu::hwp`). Still to build: - **RAPL**: PL1, PL2 and their windows through `MSR_PKG_POWER_LIMIT` and the MMIO mirror in the host bridge's MCHBAR, at the bar's values; a limit diff --git a/issues/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md b/issues/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md index 52170428b48..fe83912ad12 100644 --- a/issues/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md +++ b/issues/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md @@ -11,14 +11,14 @@ performance controls through CPPC, not HWP. On both the kernel declares no performance request, firmware's values stand, and `SYS_COUNTERS` carries no power envelope. -The refusals are `toyos_cpuvuln::hwp`'s (`kernel/cpuvuln/src/hwp.rs`): -- `HwpRefusal::Hybrid`, for CPUID.(7,0):EDX[15]: the declared minimum is a - ratio (`MSR_PLATFORM_INFO[47:40]`), and a hybrid CPU's HWP scale is not its - ratio scale. -- `HwpRefusal::NoHwp`, for CPUID.6:EAX[7] clear, which is where an AMD CPU +The refusals are `kernel::cpu::hwp`'s (`kernel/pure/cpu.rs`): +- `HwpRefusal::Hybrid`, for CPUID.(07H,0):EDX[15]. No bench ToyOS has is + hybrid, so the declaration cannot be measured on one, and the owner ruled + no design for a machine that cannot be measured (#915, ruling B). +- `HwpRefusal::NoHwp`, for CPUID.06H:EAX[7] clear, which is where an AMD CPU with CPPC is refused; its controls are not read. -- `HwpRefusal::NotIntel`, for HWP on any vendor but Intel, because the - minimum comes from `MSR_PLATFORM_INFO`, which is Intel's. +- `HwpRefusal::NotIntel`, for HWP's bits on any vendor but Intel, where SDM + Vol. 1 §21.1.1 gives them no meaning. `control_regs::hwp_declared` logs the refusal once and declares nothing on any CPU. diff --git a/issues/the-speculation-decision-does-not-model-an-affected-cpus-l1tf-line.md b/issues/the-speculation-decision-does-not-model-an-affected-cpus-l1tf-line.md deleted file mode 100644 index 596202191f5..00000000000 --- a/issues/the-speculation-decision-does-not-model-an-affected-cpus-l1tf-line.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -status: open -kind: defect -opened: 2026-09-29 ---- - -# The speculation decision does not model an affected CPU's l1tf line - -`toyos_cpuvuln::Decision::line` answers `Unmodelled::L1tf` for a CPU with -`X86_BUG_L1TF`, because at `Ubuntu-6.8.0-142.142` that line rests on two -things `Facts` does not carry: - -- **Whether RAM lies above `MAX_PA/2`**: `l1tf_select_mitigation` sets - `L1TF_PTEINV` only if no e820 RAM range reaches `l1tf_pfn_limit()` - (`bugs.c:2571-2583`; `asm/processor.h:217`), which is `x86_cache_bits` (CPUID 0x80000008:EAX, - raised to 44 for the models `override_cache_bits` names, `bugs.c:2513-2536`) - against the firmware's memory map. Without `L1TF_PTEINV` the line is - "Vulnerable" (`bugs.c:3330-3333,3376`). -- **`kvm_intel`'s `l1tf_vmx_mitigation`**: the line's `VMX:` part is - `VMENTER_L1D_FLUSH_AUTO` until `kvm_intel` loads (`bugs.c:3074-3089`), a - module Linux's userspace loads, not a CPU fact. - -The first is readable by the kernel (the loader's memory map, CPUID); the -second has no counterpart in a kernel that runs no VM, so parity needs a ruling -on which `VMX:` state ToyOS is held to. - -No machine ToyOS is tested on reaches it: the T14 has `RDCL_NO`, and the TCG -model and the KVM runners are AMD. - -**Exit**: the line is decided from the memory map and CPUID and the owner's -`VMX:` ruling, and held by a fixture captured under the pinned Linux on such a -CPU. diff --git a/issues/the-tree-names-linux-as-an-authority.md b/issues/the-tree-names-linux-as-an-authority.md index 4c03593900e..582c621de60 100644 --- a/issues/the-tree-names-linux-as-an-authority.md +++ b/issues/the-tree-names-linux-as-an-authority.md @@ -11,20 +11,25 @@ and **Tests** (#902): source, tests and manifests name Linux only as a host plat an interface defined outside ToyOS (a `libc` item, a firmware's `_OSI` string, a partition type), nothing takes its authority from another implementation's source, and a test's oracle for what hardware does is the vendor's document or ToyOS's own read checked against it, never a reading -captured under Linux. The tree breaks all three: a design cites Linux's source (`kernel/cpuvuln`, -`kernel/src/mm/mmio.rs`, `system/netstack/shard/tcp/src/rx.rs`), a value is set as Linux's +captured under Linux. The tree breaks all three: a design cites Linux's source (`kernel/src/mm/mmio.rs`, `system/netstack/shard/tcp/src/rx.rs`), a value is set as Linux's (`system/fileserver/src/resolve.rs`, `kernel/src/arch/x86_64/vtd/queue.rs`, `sdk/libc/src/linkreq.rs`, `sdk/libc/src/sockopt.rs`, `sdk/libc/src/memreq.rs`, `sdk/libc/src/posix_io.rs`, `sdk/libc/src/sigmask.rs`), and tests hold -ToyOS to Linux's code or readings (`system/netstack/i219/src/tests.rs`, `kernel/cpuvuln`'s fixtures, +ToyOS to Linux's code or readings (`system/netstack/i219/src/tests.rs`, `lib/acpi/tests/thinkpad_t14.rs`, the T14 rows in `tests/toyos.rs` reading `tests/metal/t14-linux/`). Evidence: every mention outside `issues/` at `07b5879ee`, [linux-mentions.txt](https://github.com/ToyOSOrg/evidence/blob/f42f8ab5a1107210c8e487963d9c530eb67741f4/linux-mentions.txt). -Owner: the orchestrator, which has briefed two branches to clear it: the source sweep -(`wt/toyos-linux-sweep`) and the `kernel/cpuvuln` design (`wt/toyos-cpufacts`). +`kernel/cpuvuln` and its fixtures went in #915. The other kernel lines are +the sweep's: `kernel/src/arch/aarch64/irqchip.rs`, +`kernel/src/arch/x86_64/vtd/fault.rs`, +`kernel/src/arch/x86_64/vtd/queue.rs`, `kernel/src/mm/mmio.rs` and +`kernel/usbhid`. + +Owner: the orchestrator, which has briefed the source sweep +(`wt/toyos-linux-sweep`) to clear it. **Exit**: `git grep -n -i linux -- kernel sdk/libc/src lib/acpi lib/hda lib/xhci lib/bcachefs system/fileserver system/logkeeper system/netstack/i219 system/netstack/shard diff --git a/issues/the-tree-says-who-uses-each-thing.md b/issues/the-tree-says-who-uses-each-thing.md index e4d3b52519b..7ceb9be2a47 100644 --- a/issues/the-tree-says-who-uses-each-thing.md +++ b/issues/the-tree-says-who-uses-each-thing.md @@ -119,7 +119,7 @@ Cargo.toml Cargo.lock clippy.toml .cargo/config.toml rule step 1: on one [target.] table per guest triple .github/ .claude/ .gitignore .gitmodules rule step 1 loader/ the UEFI loader, package bootloader -kernel/ CLAUDE.md; src/ pure/ loom/ sim/ pci/ gicv3/ ps2/ dma/ cpuvuln/ microcode/ (+ intel-ucode, licence) +kernel/ CLAUDE.md; src/ pure/ loom/ sim/ pci/ gicv3/ ps2/ dma/ microcode/ (+ intel-ucode, licence) system/ CLAUDE.md (the server doctrine); one directory per /system/bin name: supervisor compositor (desktop/ sprite/) netstack (mdns/ dns/ and the net family) soundserver (mixer/) console diskserver @@ -222,7 +222,7 @@ tree's top level: `.cargo`, `.claude`, `.github`, `.gitignore`, **Exit:** `PUBLISHED` is derived from `sdk/` and names `sdk/filepicker`; `SYSROOT_SOURCES` is derived from `sdk/` and the `lib/` crates libc links; a release dry run green. -6. **`lib/`, and what one thing uses into that thing:** `kernel/cpuvuln`, +6. **`lib/`, and what one thing uses into that thing:** `kernel/cpuvuln` (deleted by #915), `kernel/microcode`, the net family and `toyos-dns` into `system/netstack/`. Landed with stage 4, in one pull request. **Exit:** no `toyos-*` or `bcachefs` directory is left at the root; `git diff -M` diff --git a/issues/toyos-runs-in-any-open-source-hypervisor.md b/issues/toyos-runs-in-any-open-source-hypervisor.md index 16f2c0b4d58..2a793ea6fc4 100644 --- a/issues/toyos-runs-in-any-open-source-hypervisor.md +++ b/issues/toyos-runs-in-any-open-source-hypervisor.md @@ -68,10 +68,10 @@ the harness's alone. Adaptations that degrade nothing: the TCO table's q35 row (`lib/tco`, armed only by the `watchdog` parameter and refused by name elsewhere), netstack's 82574L row for QEMU's `e1000e`, the hard-lockup bound's path for a machine -with no PMU, `toyos-cpuvuln`'s reading of `CPUID.1:ECX[31]`, which Intel +with no PMU, `kernel::cpu`'s reading of `CPUID.1:ECX[31]`, which Intel defines as "Not Used: Intel processors always return 0. Allocated for use by -software emulation" (Intel SDM Vol. 1, 253665-093US, Table 21-9; AMD's APM -unread), and the loader's read of PI's DXE services (`loader/src/gcd.rs`), +software emulation" (Intel SDM Vol. 1, 253665-093US, Table 21-9) and AMD as +the hypervisor's (APM Vol. 3 §E.3.2), and the loader's read of PI's DXE services (`loader/src/gcd.rs`), which hands over the root bridges' windows alone where firmware has none. ## Each hypervisor, x86-64 diff --git a/issues/tsx-stays-as-firmware-left-it.md b/issues/tsx-stays-as-firmware-left-it.md index daac49b33c5..bfbdb5ed2fc 100644 --- a/issues/tsx-stays-as-firmware-left-it.md +++ b/issues/tsx-stays-as-firmware-left-it.md @@ -18,7 +18,7 @@ T14's `CONFIG_X86_INTEL_TSX_MODE_OFF=y`, runs `tsx_init` (`tsx.c:23-41,189-226`). **Exit**: -`issues/a-pure-function-decides-a-cpus-speculation-mitigations-as-linux-does.md` +`issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.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/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md` diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 13c8dc5098e..0c737b54c06 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -484,7 +484,6 @@ toyos-dma = { path = "dma" } toyos-blackbox = { path = "../lib/blackbox" } toyos-blockhold = { path = "../lib/blockhold" } toyos-bootmap = { path = "../lib/bootmap" } -toyos-cpuvuln = { path = "cpuvuln" } toyos-fat32 = { path = "../lib/fat32" } toyos-elf = { path = "../lib/elf" } toyos-gicv3 = { path = "gicv3" } diff --git a/kernel/cpuvuln/Cargo.toml b/kernel/cpuvuln/Cargo.toml deleted file mode 100644 index 74e3a0a386b..00000000000 --- a/kernel/cpuvuln/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -# A member of the host workspace (root `Cargo.toml`): -# `no_std` so the kernel can depend on it by path, and its tests run on the host. - -[package] -name = "toyos-cpuvuln" -description = "Verdicts from a CPU's CPUID and MSR facts, pure: Linux's on each speculative-execution vulnerability and its mitigation choice, which model-specific counters the CPU has and the HWP request intel_pstate asks of it; AMD's revision guides' on whether erratum 1386 forbids the CPU XSAVES." -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" diff --git a/kernel/cpuvuln/fixtures/awaiting-capture/milan-kvm.txt b/kernel/cpuvuln/fixtures/awaiting-capture/milan-kvm.txt deleted file mode 100644 index 379df7eefec..00000000000 --- a/kernel/cpuvuln/fixtures/awaiting-capture/milan-kvm.txt +++ /dev/null @@ -1,17 +0,0 @@ -/sys/devices/system/cpu/vulnerabilities/gather_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/indirect_target_selection:Not affected -/sys/devices/system/cpu/vulnerabilities/itlb_multihit:Not affected -/sys/devices/system/cpu/vulnerabilities/l1tf:Not affected -/sys/devices/system/cpu/vulnerabilities/mds:Not affected -/sys/devices/system/cpu/vulnerabilities/meltdown:Not affected -/sys/devices/system/cpu/vulnerabilities/mmio_stale_data:Not affected -/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/retbleed:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow:Vulnerable: Safe RET, no microcode -/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl -/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization -/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: disabled; RSB filling; PBRSB-eIBRS: Not affected; BHI: Not affected -/sys/devices/system/cpu/vulnerabilities/srbds:Not affected -/sys/devices/system/cpu/vulnerabilities/tsa:Vulnerable: Clear CPU buffers attempted, no microcode -/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Not affected -/sys/devices/system/cpu/vulnerabilities/vmscape:Not affected diff --git a/kernel/cpuvuln/fixtures/awaiting-capture/turin-kvm.txt b/kernel/cpuvuln/fixtures/awaiting-capture/turin-kvm.txt deleted file mode 100644 index 338f6c44fde..00000000000 --- a/kernel/cpuvuln/fixtures/awaiting-capture/turin-kvm.txt +++ /dev/null @@ -1,17 +0,0 @@ -/sys/devices/system/cpu/vulnerabilities/gather_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/indirect_target_selection:Not affected -/sys/devices/system/cpu/vulnerabilities/itlb_multihit:Not affected -/sys/devices/system/cpu/vulnerabilities/l1tf:Not affected -/sys/devices/system/cpu/vulnerabilities/mds:Not affected -/sys/devices/system/cpu/vulnerabilities/meltdown:Not affected -/sys/devices/system/cpu/vulnerabilities/mmio_stale_data:Not affected -/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/retbleed:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl -/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization -/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; STIBP: disabled; PBRSB-eIBRS: Not affected; BHI: Not affected -/sys/devices/system/cpu/vulnerabilities/srbds:Not affected -/sys/devices/system/cpu/vulnerabilities/tsa:Not affected -/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Not affected -/sys/devices/system/cpu/vulnerabilities/vmscape:Not affected diff --git a/kernel/cpuvuln/fixtures/t14.txt b/kernel/cpuvuln/fixtures/t14.txt deleted file mode 100644 index 15ba1120f37..00000000000 --- a/kernel/cpuvuln/fixtures/t14.txt +++ /dev/null @@ -1,17 +0,0 @@ -/sys/devices/system/cpu/vulnerabilities/gather_data_sampling:Mitigation: Microcode -/sys/devices/system/cpu/vulnerabilities/indirect_target_selection:Mitigation: Aligned branch/return thunks -/sys/devices/system/cpu/vulnerabilities/itlb_multihit:Not affected -/sys/devices/system/cpu/vulnerabilities/l1tf:Not affected -/sys/devices/system/cpu/vulnerabilities/mds:Not affected -/sys/devices/system/cpu/vulnerabilities/meltdown:Not affected -/sys/devices/system/cpu/vulnerabilities/mmio_stale_data:Not affected -/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/retbleed:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl -/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization -/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; PBRSB-eIBRS: SW sequence; BHI: SW loop, KVM: SW loop -/sys/devices/system/cpu/vulnerabilities/srbds:Not affected -/sys/devices/system/cpu/vulnerabilities/tsa:Not affected -/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Not affected -/sys/devices/system/cpu/vulnerabilities/vmscape:Not affected diff --git a/kernel/cpuvuln/fixtures/t14/SOURCE b/kernel/cpuvuln/fixtures/t14/SOURCE deleted file mode 100644 index 4b9d9c0e487..00000000000 --- a/kernel/cpuvuln/fixtures/t14/SOURCE +++ /dev/null @@ -1,20 +0,0 @@ -Linux's readings of the ThinkPad T14 this repository is tested on, under that -machine's Ubuntu, read as root on the T14 by `toyos-t14linux/capture.sh` at -commit 44eb3c2e (pull request #601), and it read -CPUID and the MSRs through the running kernel's /dev/cpu/*/cpuid and -/dev/cpu/*/msr, so an MSR holds what that kernel left in it. - - vulnerabilities.txt the run's `grep .` over the vulnerabilities directory, - committed as ../t14.txt - cpuid.txt CPU 0's leaves, one per line: - leaf subleaf eax ebx ecx edx - msr.txt one per line: msr cpu value; 0x122 and 0x10F are absent - because ARCH_CAPABILITIES bit 7 and CPUID.(7,0):EDX - bits 11 and 13 do not enumerate them - -One more reading of the same Ubuntu on the same machine, `6.8.0-142-generic` -with the `powersave` governor, taken as root by the orchestrator on -2026-10-03, as the tool printed it: - - perf-msr.txt `perf stat -a -A -e msr/aperf/,msr/mperf/,msr/smi/,msr/tsc/ sleep 60`, - the msr PMU's events per CPU across 60 s of an idle machine diff --git a/kernel/cpuvuln/fixtures/t14/cpuid.txt b/kernel/cpuvuln/fixtures/t14/cpuid.txt deleted file mode 100644 index b4e53c2bb94..00000000000 --- a/kernel/cpuvuln/fixtures/t14/cpuid.txt +++ /dev/null @@ -1,12 +0,0 @@ -00000000 00000000 0000001b 756e6547 6c65746e 49656e69 -00000001 00000000 000806c1 00100800 7ffafbbf bfebfbff -00000006 00000000 0017eff7 00000002 00000009 00000000 -00000007 00000000 00000002 f3bfa7eb 18c05fde fc100710 -00000007 00000001 00000000 00000000 00000000 00040000 -00000007 00000002 00000000 00000000 00000000 00000001 -0000000d 00000000 000002e7 00000a88 00000a88 00000000 -0000000d 00000001 0000000f 00000998 00003900 00000000 -00000014 00000000 00000001 0000004f 00000007 00000000 -80000000 00000000 80000008 00000000 00000000 00000000 -80000008 00000000 00003027 00000000 00000000 00000000 -80000021 00000000 00000000 00000000 00000000 00000000 diff --git a/kernel/cpuvuln/fixtures/t14/cpuinfo.txt b/kernel/cpuvuln/fixtures/t14/cpuinfo.txt deleted file mode 100644 index 15a6d57f5f3..00000000000 --- a/kernel/cpuvuln/fixtures/t14/cpuinfo.txt +++ /dev/null @@ -1,224 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 627.581 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 0 -cpu cores : 4 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 855.097 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 1 -cpu cores : 4 -apicid : 2 -initial apicid : 2 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 2 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 3282.151 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 2 -cpu cores : 4 -apicid : 4 -initial apicid : 4 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 3 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 615.767 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 3 -cpu cores : 4 -apicid : 6 -initial apicid : 6 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 4 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 399.863 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 0 -cpu cores : 4 -apicid : 1 -initial apicid : 1 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 5 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 3673.333 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 1 -cpu cores : 4 -apicid : 3 -initial apicid : 3 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 6 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 3800.000 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 2 -cpu cores : 4 -apicid : 5 -initial apicid : 5 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - -processor : 7 -vendor_id : GenuineIntel -cpu family : 6 -model : 140 -model name : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz -stepping : 1 -microcode : 0xbe -cpu MHz : 3162.755 -cache size : 8192 KB -physical id : 0 -siblings : 8 -core id : 3 -cpu cores : 4 -apicid : 7 -initial apicid : 7 -fpu : yes -fpu_exception : yes -cpuid level : 27 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities -vmx flags : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling -bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb gds bhi its its_native_only -bogomips : 4838.40 -clflush size : 64 -cache_alignment : 64 -address sizes : 39 bits physical, 48 bits virtual -power management: - diff --git a/kernel/cpuvuln/fixtures/t14/msr.txt b/kernel/cpuvuln/fixtures/t14/msr.txt deleted file mode 100644 index 3fb193cb6b1..00000000000 --- a/kernel/cpuvuln/fixtures/t14/msr.txt +++ /dev/null @@ -1,32 +0,0 @@ -0000008b 0 000000be00000000 -0000008b 1 000000be00000000 -0000008b 2 000000be00000000 -0000008b 3 000000be00000000 -0000008b 4 000000be00000000 -0000008b 5 000000be00000000 -0000008b 6 000000be00000000 -0000008b 7 000000be00000000 -00000048 0 0000000000000001 -00000048 1 0000000000000001 -00000048 2 0000000000000001 -00000048 3 0000000000000001 -00000048 4 0000000000000001 -00000048 5 0000000000000001 -00000048 6 0000000000000001 -00000048 7 0000000000000001 -0000010a 0 000000000a005c6b -0000010a 1 000000000a005c6b -0000010a 2 000000000a005c6b -0000010a 3 000000000a005c6b -0000010a 4 000000000a005c6b -0000010a 5 000000000a005c6b -0000010a 6 000000000a005c6b -0000010a 7 000000000a005c6b -00000123 0 0000000000000000 -00000123 1 0000000000000000 -00000123 2 0000000000000000 -00000123 3 0000000000000000 -00000123 4 0000000000000000 -00000123 5 0000000000000000 -00000123 6 0000000000000000 -00000123 7 0000000000000000 diff --git a/kernel/cpuvuln/fixtures/t14/perf-msr.txt b/kernel/cpuvuln/fixtures/t14/perf-msr.txt deleted file mode 100644 index ba322133350..00000000000 --- a/kernel/cpuvuln/fixtures/t14/perf-msr.txt +++ /dev/null @@ -1,38 +0,0 @@ - - Performance counter stats for 'system wide': - -CPU0 98,079,648 msr/aperf/ -CPU1 181,130,722 msr/aperf/ -CPU2 121,578,728 msr/aperf/ -CPU3 140,715,079 msr/aperf/ -CPU4 61,297,064 msr/aperf/ -CPU5 191,874,303 msr/aperf/ -CPU6 96,508,493 msr/aperf/ -CPU7 74,201,638 msr/aperf/ -CPU0 150,186,840 msr/mperf/ -CPU1 192,582,720 msr/mperf/ -CPU2 234,799,920 msr/mperf/ -CPU3 313,507,656 msr/mperf/ -CPU4 156,309,048 msr/mperf/ -CPU5 326,606,712 msr/mperf/ -CPU6 227,450,760 msr/mperf/ -CPU7 156,326,856 msr/mperf/ -CPU0 0 msr/smi/ -CPU1 0 msr/smi/ -CPU2 0 msr/smi/ -CPU3 0 msr/smi/ -CPU4 0 msr/smi/ -CPU5 0 msr/smi/ -CPU6 0 msr/smi/ -CPU7 0 msr/smi/ -CPU0 145,157,386,466 msr/tsc/ -CPU1 145,157,434,015 msr/tsc/ -CPU2 145,157,376,550 msr/tsc/ -CPU3 145,157,230,321 msr/tsc/ -CPU4 145,157,567,260 msr/tsc/ -CPU5 145,157,642,420 msr/tsc/ -CPU6 145,157,528,396 msr/tsc/ -CPU7 145,157,356,567 msr/tsc/ - - 60.002902881 seconds time elapsed - diff --git a/kernel/cpuvuln/fixtures/tcg/SOURCE b/kernel/cpuvuln/fixtures/tcg/SOURCE deleted file mode 100644 index fb15772189a..00000000000 --- a/kernel/cpuvuln/fixtures/tcg/SOURCE +++ /dev/null @@ -1,14 +0,0 @@ -The same Linux on the TCG model, booted on the development host by -`toyos-t14linux/tcg.sh` at commit 44eb3c2e (pull request #601) as - - 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 - -The kernel is the T14's /boot/vmlinuz-6.8.0-142-generic, and s0.cpio is the -T14's busybox with an init that mounts sysfs and proc, prints /proc/version and -`grep .` over /sys/devices/system/cpu/vulnerabilities/*, and powers off. Both -came off the T14 in the run that took ../t14/, and neither is committed. - - console.txt the serial port, CRLF as it arrived diff --git a/kernel/cpuvuln/fixtures/tcg/console.txt b/kernel/cpuvuln/fixtures/tcg/console.txt deleted file mode 100644 index 937d5c44eed..00000000000 --- a/kernel/cpuvuln/fixtures/tcg/console.txt +++ /dev/null @@ -1,397 +0,0 @@ -[ 0.000000] Linux version 6.8.0-142-generic (buildd@lcy02-amd64-049) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #142-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 2 14:24:27 UTC 2026 (Ubuntu 6.8.0-142.142-generic 6.8.12) -[ 0.000000] Command line: console=ttyS0 panic=-1 -[ 0.000000] KERNEL supported cpus: -[ 0.000000] Intel GenuineIntel -[ 0.000000] AMD AuthenticAMD -[ 0.000000] Hygon HygonGenuine -[ 0.000000] Centaur CentaurHauls -[ 0.000000] zhaoxin Shanghai -[ 0.000000] BIOS-provided physical RAM map: -[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable -[ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved -[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007ffdffff] usable -[ 0.000000] BIOS-e820: [mem 0x000000007ffe0000-0x000000007fffffff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000b0000000-0x00000000bfffffff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved -[ 0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved -[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000017fffffff] usable -[ 0.000000] BIOS-e820: [mem 0x000000fd00000000-0x000000ffffffffff] reserved -[ 0.000000] NX (Execute Disable) protection: active -[ 0.000000] APIC: Static calls initialized -[ 0.000000] SMBIOS 2.8 present. -[ 0.000000] DMI: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 -[ 0.000000] tsc: Fast TSC calibration using PIT -[ 0.000000] tsc: Detected 999.836 MHz processor -[ 0.004886] AGP: No AGP bridge found -[ 0.005065] last_pfn = 0x180000 max_arch_pfn = 0x400000000 -[ 0.005607] MTRR map: 4 entries (3 fixed + 1 variable; max 19), built from 8 variable MTRRs -[ 0.005782] x86/PAT: Configuration [0-7]: WB WC UC- UC WB WP UC- WT -[ 0.006657] last_pfn = 0x7ffe0 max_arch_pfn = 0x400000000 -[ 0.024253] found SMP MP-table at [mem 0x000f54c0-0x000f54cf] -[ 0.028424] RAMDISK: [mem 0x7fdd9000-0x7ffdffff] -[ 0.028890] ACPI: Early table checksum verification disabled -[ 0.029267] ACPI: RSDP 0x00000000000F52C0 000014 (v00 BOCHS ) -[ 0.029509] ACPI: RSDT 0x000000007FFE232F 000038 (v01 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030048] ACPI: FACP 0x000000007FFE211F 0000F4 (v03 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030562] ACPI: DSDT 0x000000007FFE0040 0020DF (v01 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030642] ACPI: FACS 0x000000007FFE0000 000040 -[ 0.030685] ACPI: APIC 0x000000007FFE2213 000080 (v03 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030709] ACPI: HPET 0x000000007FFE2293 000038 (v01 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030732] ACPI: MCFG 0x000000007FFE22CB 00003C (v01 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030753] ACPI: WAET 0x000000007FFE2307 000028 (v01 BOCHS BXPC 00000001 BXPC 00000001) -[ 0.030827] ACPI: Reserving FACP table memory at [mem 0x7ffe211f-0x7ffe2212] -[ 0.030849] ACPI: Reserving DSDT table memory at [mem 0x7ffe0040-0x7ffe211e] -[ 0.030858] ACPI: Reserving FACS table memory at [mem 0x7ffe0000-0x7ffe003f] -[ 0.030865] ACPI: Reserving APIC table memory at [mem 0x7ffe2213-0x7ffe2292] -[ 0.030871] ACPI: Reserving HPET table memory at [mem 0x7ffe2293-0x7ffe22ca] -[ 0.030877] ACPI: Reserving MCFG table memory at [mem 0x7ffe22cb-0x7ffe2306] -[ 0.030884] ACPI: Reserving WAET table memory at [mem 0x7ffe2307-0x7ffe232e] -[ 0.154458] No NUMA configuration found -[ 0.154525] Faking a node at [mem 0x0000000000000000-0x000000017fffffff] -[ 0.155066] NODE_DATA(0) allocated [mem 0x17ffd3000-0x17fffdfff] -[ 0.174522] Zone ranges: -[ 0.174584] DMA [mem 0x0000000000001000-0x0000000000ffffff] -[ 0.174670] DMA32 [mem 0x0000000001000000-0x00000000ffffffff] -[ 0.174683] Normal [mem 0x0000000100000000-0x000000017fffffff] -[ 0.174694] Device empty -[ 0.174715] Movable zone start for each node -[ 0.174741] Early memory node ranges -[ 0.174767] node 0: [mem 0x0000000000001000-0x000000000009efff] -[ 0.174881] node 0: [mem 0x0000000000100000-0x000000007ffdffff] -[ 0.174912] node 0: [mem 0x0000000100000000-0x000000017fffffff] -[ 0.175044] Initmem setup node 0 [mem 0x0000000000001000-0x000000017fffffff] -[ 0.214952] On node 0, zone DMA: 1 pages in unavailable ranges -[ 0.259966] On node 0, zone DMA: 97 pages in unavailable ranges -[ 0.306778] On node 0, zone Normal: 32 pages in unavailable ranges -[ 0.307281] ACPI: PM-Timer IO Port: 0x608 -[ 0.307692] ACPI: LAPIC_NMI (acpi_id[0xff] dfl dfl lint[0x1]) -[ 0.308416] IOAPIC[0]: apic_id 0, version 32, address 0xfec00000, GSI 0-23 -[ 0.308662] ACPI: INT_SRC_OVR (bus 0 bus_irq 0 global_irq 2 dfl dfl) -[ 0.309271] ACPI: INT_SRC_OVR (bus 0 bus_irq 5 global_irq 5 high level) -[ 0.309442] ACPI: INT_SRC_OVR (bus 0 bus_irq 9 global_irq 9 high level) -[ 0.309654] ACPI: INT_SRC_OVR (bus 0 bus_irq 10 global_irq 10 high level) -[ 0.309704] ACPI: INT_SRC_OVR (bus 0 bus_irq 11 global_irq 11 high level) -[ 0.310323] ACPI: Using ACPI (MADT) for SMP configuration information -[ 0.310428] ACPI: HPET id: 0x8086a201 base: 0xfed00000 -[ 0.310980] smpboot: Allowing 2 CPUs, 0 hotplug CPUs -[ 0.316917] PM: hibernation: Registered nosave memory: [mem 0x00000000-0x00000fff] -[ 0.317049] PM: hibernation: Registered nosave memory: [mem 0x0009f000-0x000fffff] -[ 0.317077] PM: hibernation: Registered nosave memory: [mem 0x7ffe0000-0xffffffff] -[ 0.317271] [mem 0xc0000000-0xfed1bfff] available for PCI devices -[ 0.317395] Booting paravirtualized kernel on bare hardware -[ 0.317906] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1910969940391419 ns -[ 0.319350] setup_percpu: NR_CPUS:8192 nr_cpumask_bits:2 nr_cpu_ids:2 nr_node_ids:1 -[ 0.320845] percpu: Embedded 86 pages/cpu s229376 r8192 d114688 u1048576 -[ 0.322058] Kernel command line: console=ttyS0 panic=-1 -[ 0.323259] random: crng init done -[ 0.324244] Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes, linear) -[ 0.324711] Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes, linear) -[ 0.326959] Fallback order for Node 0: 0 -[ 0.327327] Built 1 zonelists, mobility grouping on. Total pages: 1031904 -[ 0.327359] Policy zone: Normal -[ 0.327762] mem auto-init: stack:all(zero), heap alloc:on, heap free:off -[ 0.327875] AGP: Checking aperture... -[ 0.328781] AGP: No AGP bridge found -[ 0.329278] software IO TLB: area num 2. -[ 0.422990] Memory: 3998584K/4193784K available (22528K kernel code, 4441K rwdata, 14428K rodata, 4932K init, 4776K bss, 194940K reserved, 0K cma-reserved) -[ 0.431908] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1 -[ 0.433784] ftrace: allocating 58336 entries in 228 pages -[ 0.483521] ftrace: allocated 228 pages with 4 groups -[ 0.501299] Dynamic Preempt: voluntary -[ 0.504172] rcu: Preemptible hierarchical RCU implementation. -[ 0.504225] rcu: RCU restricting CPUs from NR_CPUS=8192 to nr_cpu_ids=2. -[ 0.504308] Trampoline variant of Tasks RCU enabled. -[ 0.504316] Rude variant of Tasks RCU enabled. -[ 0.504322] Tracing variant of Tasks RCU enabled. -[ 0.504424] rcu: RCU calculated value of scheduler-enlistment delay is 100 jiffies. -[ 0.504447] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2 -[ 0.505387] RCU Tasks: Setting shift to 1 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=2. -[ 0.505417] RCU Tasks Rude: Setting shift to 1 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=2. -[ 0.505429] RCU Tasks Trace: Setting shift to 1 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=2. -[ 0.526178] NR_IRQS: 524544, nr_irqs: 440, preallocated irqs: 16 -[ 0.572828] rcu: srcu_init: Setting srcu_struct sizes based on contention. -[ 0.605074] Console: colour *CGA 80x25 -[ 0.607687] printk: legacy console [ttyS0] enabled -[ 0.646169] ACPI: Core revision 20230628 -[ 0.666704] clocksource: hpet: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604467 ns -[ 0.672419] APIC: Switch to symmetric I/O mode setup -[ 0.673423] x2apic: IRQ remapping doesn't support X2APIC mode -[ 0.677245] ..TIMER: vector=0x30 apic1=0 pin1=2 apic2=-1 pin2=-1 -[ 0.689734] tsc: Marking TSC unstable due to TSCs unsynchronized -[ 0.690356] Calibrating delay loop (skipped), value calculated using timer frequency.. 1999.67 BogoMIPS (lpj=999836) -[ 0.702762] process: using AMD E400 aware idle routine -[ 0.707552] Last level iTLB entries: 4KB 512, 2MB 255, 4MB 127 -[ 0.707908] Last level dTLB entries: 4KB 512, 2MB 255, 4MB 127, 1GB 0 -[ 0.710931] Spectre V1 : Mitigation: usercopy/swapgs barriers and __user pointer sanitization -[ 0.711667] Spectre V2 : Mitigation: Retpolines -[ 0.712220] Spectre V2 : Spectre v2 / SpectreRSB: Filling RSB on context switch and VMEXIT -[ 0.718397] x86/fpu: x87 FPU will use FXSAVE -[ 0.952113] Freeing SMP alternatives memory: 48K -[ 0.952776] pid_max: default: 32768 minimum: 301 -[ 0.959230] LSM: initializing lsm=lockdown,capability,landlock,yama,apparmor,integrity -[ 0.960113] landlock: Up and running. -[ 0.960113] Yama: becoming mindful. -[ 0.960113] AppArmor: AppArmor initialized -[ 0.967373] Mount-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) -[ 0.968113] Mountpoint-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) -[ 1.071113] smpboot: CPU0: AMD QEMU Virtual CPU version 2.5+ (family: 0xf, model: 0x6b, stepping: 0x1) -[ 1.085052] Performance Events: PMU not available due to virtualization, using software events only. -[ 1.086330] signal: max sigframe size: 1440 -[ 1.091252] rcu: Hierarchical SRCU implementation. -[ 1.092554] rcu: Max phase no-delay instances is 400. -[ 1.106550] NMI watchdog: Perf NMI watchdog permanently disabled -[ 1.113447] smp: Bringing up secondary CPUs ... -[ 1.137181] smpboot: x86: Booting SMP configuration: -[ 1.137440] .... node #0, CPUs: #1 -[ 0.098321] calibrate_delay_direct() dropping max bogoMips estimate 3 = 15448800 -[ 0.098321] calibrate_delay_direct() dropping max bogoMips estimate 2 = 4961900 -[ 1.250933] smp: Brought up 1 node, 2 CPUs -[ 1.251113] smpboot: Max logical packages: 1 -[ 1.251194] smpboot: Total of 2 processors activated (4621.07 BogoMIPS) -[ 1.276697] devtmpfs: initialized -[ 1.281448] x86/mm: Memory block size: 128MB -[ 1.287820] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275000 ns -[ 1.290317] futex hash table entries: 512 (order: 3, 32768 bytes, linear) -[ 1.296225] pinctrl core: initialized pinctrl subsystem -[ 1.308211] PM: RTC time: 06:50:37, date: 2026-09-29 -[ 1.319113] NET: Registered PF_NETLINK/PF_ROUTE protocol family -[ 1.323443] DMA: preallocated 512 KiB GFP_KERNEL pool for atomic allocations -[ 1.324281] DMA: preallocated 512 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations -[ 1.324845] DMA: preallocated 512 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations -[ 1.325113] audit: initializing netlink subsys (disabled) -[ 1.327450] audit: type=2000 audit(1790664635.646:1): state=initialized audit_enabled=0 res=1 -[ 1.335113] thermal_sys: Registered thermal governor 'fair_share' -[ 1.335241] thermal_sys: Registered thermal governor 'bang_bang' -[ 1.335824] thermal_sys: Registered thermal governor 'step_wise' -[ 1.335824] thermal_sys: Registered thermal governor 'user_space' -[ 1.335824] thermal_sys: Registered thermal governor 'power_allocator' -[ 1.339267] cpuidle: using governor ladder -[ 1.340410] cpuidle: using governor menu -[ 1.343467] acpiphp: ACPI Hot Plug PCI Controller Driver version: 0.5 -[ 1.348317] PCI: ECAM [mem 0xb0000000-0xbfffffff] (base 0xb0000000) for domain 0000 [bus 00-ff] -[ 1.349113] PCI: ECAM [mem 0xb0000000-0xbfffffff] reserved as E820 entry -[ 1.351520] PCI: Using configuration type 1 for base access -[ 1.352292] mtrr: your CPUs had inconsistent fixed MTRR settings -[ 1.353704] mtrr: your CPUs had inconsistent variable MTRR settings -[ 1.354044] mtrr: your CPUs had inconsistent MTRRdefType settings -[ 1.354113] mtrr: probably your BIOS does not setup all CPUs. -[ 1.354183] mtrr: corrected configuration. -[ 1.357589] kprobes: kprobe jump-optimization is enabled. All kprobes are optimized if possible. -[ 1.478215] HugeTLB: registered 2.00 MiB page size, pre-allocated 0 pages -[ 1.478518] HugeTLB: 28 KiB vmemmap can be freed for a 2.00 MiB page -[ 1.497179] ACPI: Added _OSI(Module Device) -[ 1.497686] ACPI: Added _OSI(Processor Device) -[ 1.498056] ACPI: Added _OSI(Processor Aggregator Device) -[ 1.597862] ACPI: 1 ACPI AML tables successfully acquired and loaded -[ 1.657113] ACPI: _OSC evaluation for CPUs failed, trying _PDC -[ 1.661394] ACPI: Interpreter enabled -[ 1.663331] ACPI: PM: (supports S0 S3 S4 S5) -[ 1.663820] ACPI: Using IOAPIC for interrupt routing -[ 1.667486] PCI: Using host bridge windows from ACPI; if necessary, use "pci=nocrs" and report a bug -[ 1.667884] PCI: Using E820 reservations for host bridge windows -[ 1.670542] ACPI: Enabled 2 GPEs in block 00 to 3F -[ 1.725455] ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-ff]) -[ 1.726102] acpi PNP0A08:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI EDR HPX-Type3] -[ 1.729113] acpi PNP0A08:00: _OSC: platform does not support [PCIeHotplug LTR DPC] -[ 1.732475] acpi PNP0A08:00: _OSC: OS now controls [SHPCHotplug PME AER PCIeCapability] -[ 1.742113] PCI host bridge to bus 0000:00 -[ 1.743041] pci_bus 0000:00: root bus resource [io 0x0000-0x0cf7 window] -[ 1.743378] pci_bus 0000:00: root bus resource [io 0x0d00-0xffff window] -[ 1.743576] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window] -[ 1.743776] pci_bus 0000:00: root bus resource [mem 0x80000000-0xafffffff window] -[ 1.744020] pci_bus 0000:00: root bus resource [mem 0xc0000000-0xfebfffff window] -[ 1.744113] pci_bus 0000:00: root bus resource [mem 0x180000000-0x97fffffff window] -[ 1.744481] pci_bus 0000:00: root bus resource [bus 00-ff] -[ 1.749241] pci 0000:00:00.0: [8086:29c0] type 00 class 0x060000 conventional PCI endpoint -[ 1.760477] pci 0000:00:1f.0: [8086:2918] type 00 class 0x060100 conventional PCI endpoint -[ 1.761319] pci 0000:00:1f.0: quirk: [io 0x0600-0x067f] claimed by ICH6 ACPI/GPIO/TCO -[ 1.762113] pci 0000:00:1f.2: [8086:2922] type 00 class 0x010601 conventional PCI endpoint -[ 1.763113] pci 0000:00:1f.2: BAR 4 [io 0xc040-0xc05f] -[ 1.764132] pci 0000:00:1f.2: BAR 5 [mem 0xfebff000-0xfebfffff] -[ 1.765151] pci 0000:00:1f.3: [8086:2930] type 00 class 0x0c0500 conventional PCI endpoint -[ 1.765581] pci 0000:00:1f.3: BAR 4 [io 0x0700-0x073f] -[ 1.772603] ACPI: PCI: Interrupt link LNKA configured for IRQ 10 -[ 1.776494] ACPI: PCI: Interrupt link LNKB configured for IRQ 10 -[ 1.782917] ACPI: PCI: Interrupt link LNKC configured for IRQ 11 -[ 1.785790] ACPI: PCI: Interrupt link LNKD configured for IRQ 11 -[ 1.786912] ACPI: PCI: Interrupt link LNKE configured for IRQ 10 -[ 1.788846] ACPI: PCI: Interrupt link LNKF configured for IRQ 10 -[ 1.789550] ACPI: PCI: Interrupt link LNKG configured for IRQ 11 -[ 1.790150] ACPI: PCI: Interrupt link LNKH configured for IRQ 11 -[ 1.790440] ACPI: PCI: Interrupt link GSIA configured for IRQ 16 -[ 1.790655] ACPI: PCI: Interrupt link GSIB configured for IRQ 17 -[ 1.790858] ACPI: PCI: Interrupt link GSIC configured for IRQ 18 -[ 1.791057] ACPI: PCI: Interrupt link GSID configured for IRQ 19 -[ 1.791113] ACPI: PCI: Interrupt link GSIE configured for IRQ 20 -[ 1.791432] ACPI: PCI: Interrupt link GSIF configured for IRQ 21 -[ 1.792439] ACPI: PCI: Interrupt link GSIG configured for IRQ 22 -[ 1.793113] ACPI: PCI: Interrupt link GSIH configured for IRQ 23 -[ 1.801405] iommu: Default domain type: Translated -[ 1.801491] iommu: DMA domain TLB invalidation policy: lazy mode -[ 1.805653] SCSI subsystem initialized -[ 1.809113] ACPI: bus type USB registered -[ 1.809113] usbcore: registered new interface driver usbfs -[ 1.809113] usbcore: registered new interface driver hub -[ 1.809468] usbcore: registered new device driver usb -[ 1.810952] pps_core: LinuxPPS API ver. 1 registered -[ 1.811196] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti -[ 1.812113] PTP clock support registered -[ 1.813556] EDAC MC: Ver: 3.0.0 -[ 1.837252] NetLabel: Initializing -[ 1.837698] NetLabel: domain hash size = 128 -[ 1.838113] NetLabel: protocols = UNLABELED CIPSOv4 CALIPSO -[ 1.839682] NetLabel: unlabeled traffic allowed by default -[ 1.846847] mctp: management component transport protocol core -[ 1.847113] NET: Registered PF_MCTP protocol family -[ 1.847113] PCI: Using ACPI for IRQ routing -[ 1.857442] vgaarb: loaded -[ 1.861113] hpet: 3 channels of 0 reserved for per-cpu timers -[ 1.861397] hpet0: at MMIO 0xfed00000, IRQs 2, 8, 0 -[ 1.861645] hpet0: 3 comparators, 64-bit 100.000000 MHz counter -[ 1.868045] clocksource: Switched to clocksource hpet -[ 1.906810] VFS: Disk quotas dquot_6.6.0 -[ 1.908728] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes) -[ 1.936455] AppArmor: AppArmor Filesystem Enabled -[ 1.939968] pnp: PnP ACPI init -[ 1.968926] ACPI: IRQ 4 override to edge(!), high(!) -[ 1.969401] system 00:04: [mem 0xb0000000-0xbfffffff window] has been reserved -[ 1.974719] pnp: PnP ACPI: found 5 devices -[ 2.130079] clocksource: acpi_pm: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 2085701024 ns -[ 2.132247] NET: Registered PF_INET protocol family -[ 2.134093] IP idents hash table entries: 65536 (order: 7, 524288 bytes, linear) -[ 2.619084] tcp_listen_portaddr_hash hash table entries: 2048 (order: 3, 32768 bytes, linear) -[ 2.625541] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear) -[ 2.635134] TCP established hash table entries: 32768 (order: 6, 262144 bytes, linear) -[ 2.635134] TCP bind hash table entries: 32768 (order: 8, 1048576 bytes, linear) -[ 2.637626] TCP: Hash tables configured (established 32768 bind 32768) -[ 2.640016] MPTCP token hash table entries: 4096 (order: 5, 98304 bytes, linear) -[ 2.640953] UDP hash table entries: 2048 (order: 4, 65536 bytes, linear) -[ 2.642151] UDP-Lite hash table entries: 2048 (order: 4, 65536 bytes, linear) -[ 2.643612] NET: Registered PF_UNIX/PF_LOCAL protocol family -[ 2.644098] NET: Registered PF_XDP protocol family -[ 2.644369] pci_bus 0000:00: resource 4 [io 0x0000-0x0cf7 window] -[ 2.644369] pci_bus 0000:00: resource 5 [io 0x0d00-0xffff window] -[ 2.644369] pci_bus 0000:00: resource 6 [mem 0x000a0000-0x000bffff window] -[ 2.644369] pci_bus 0000:00: resource 7 [mem 0x80000000-0xafffffff window] -[ 2.645825] pci_bus 0000:00: resource 8 [mem 0xc0000000-0xfebfffff window] -[ 2.646023] pci_bus 0000:00: resource 9 [mem 0x180000000-0x97fffffff window] -[ 2.646788] PCI: CLS 0 bytes, default 64 -[ 2.647844] PCI-DMA: Using software bounce buffering for IO (SWIOTLB) -[ 2.648047] software IO TLB: mapped [mem 0x000000007bdd9000-0x000000007fdd9000] (64MB) -[ 2.680966] Trying to unpack rootfs image as initramfs... -[ 2.694566] Initialise system trusted keyrings -[ 2.694566] Key type blacklist registered -[ 2.736580] workingset: timestamp_bits=36 max_order=20 bucket_order=0 -[ 2.737023] zbud: loaded -[ 2.772643] Freeing initrd memory: 2076K -[ 2.837656] squashfs: version 4.0 (2009/01/31) Phillip Lougher -[ 2.895486] fuse: init (API version 7.39) -[ 2.973686] integrity: Platform Keyring initialized -[ 2.974628] integrity: Machine keyring initialized -[ 3.295040] Key type asymmetric registered -[ 3.295411] Asymmetric key parser 'x509' registered -[ 3.296694] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 243) -[ 3.307340] io scheduler mq-deadline registered -[ 3.378207] shpchp: Standard Hot Plug PCI Controller Driver version: 0.4 -[ 3.387681] input: Power Button as /devices/LNXSYSTM:00/LNXPWRBN:00/input/input0 -[ 3.391290] ACPI: button: Power Button [PWRF] -[ 3.412658] Serial: 8250/16550 driver, 32 ports, IRQ sharing enabled -[ 3.447742] 00:02: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A -[ 3.830776] Linux agpgart interface v0.103 -[ 3.991184] loop: module loaded -[ 3.998015] ACPI: bus type drm_connector registered -[ 4.003909] tun: Universal TUN/TAP device driver, 1.6 -[ 4.012172] PPP generic driver version 2.4.2 -[ 4.022220] i8042: PNP: PS/2 Controller [PNP0303:KBD,PNP0f13:MOU] at 0x60,0x64 irq 1,12 -[ 4.193670] serio: i8042 KBD port at 0x60,0x64 irq 1 -[ 4.195110] serio: i8042 AUX port at 0x60,0x64 irq 12 -[ 4.200640] mousedev: PS/2 mouse device common for all mice -[ 4.207620] rtc_cmos 00:03: RTC can wake from S4 -[ 4.212902] input: AT Translated Set 2 keyboard as /devices/platform/i8042/serio0/input/input1 -[ 4.213935] rtc_cmos 00:03: registered as rtc0 -[ 4.215489] rtc_cmos 00:03: setting system clock to 2026-09-29T06:50:40 UTC (1790664640) -[ 4.223983] rtc_cmos 00:03: alarms up to one day, y3k, 242 bytes nvram, hpet irqs -[ 4.282706] i2c_dev: i2c /dev entries driver -[ 4.287693] device-mapper: core: CONFIG_IMA_DISABLE_HTABLE is disabled. Duplicate IMA measurements will not be recorded in the IMA log. -[ 4.333982] device-mapper: uevent: version 1.0.3 -[ 4.406419] device-mapper: ioctl: 4.48.0-ioctl (2023-03-01) initialised: dm-devel@redhat.com -[ 4.410672] amd_pstate: the _CPC object is not present in SBIOS or ACPI disabled -[ 4.414474] ledtrig-cpu: registered to indicate activity on CPUs -[ 4.423670] drop_monitor: Initializing network drop monitor service -[ 4.424916] NET: Registered PF_INET6 protocol family -[ 4.520917] Segment Routing with IPv6 -[ 4.522232] In-situ OAM (IOAM) with IPv6 -[ 4.524806] NET: Registered PF_PACKET protocol family -[ 4.526814] Key type dns_resolver registered -[ 4.531227] IPI shorthand broadcast: enabled -[ 4.744574] registered taskstats version 1 -[ 4.752959] Loading compiled-in X.509 certificates -[ 4.839158] Loaded X.509 cert 'Build time autogenerated kernel key: aaf6cface807121d2b25f83f2288de5f0eb7acff' -[ 4.864624] Loaded X.509 cert 'Canonical Ltd. Live Patch Signing 2025 Kmod: d541cef61dc7e793b7eb7e899970a2eef0b5dc8c' -[ 4.871401] Loaded X.509 cert 'Canonical Ltd. Live Patch Signing: 14df34d1a87cf37625abec039ef2bf521249b969' -[ 4.901832] Loaded X.509 cert 'Canonical Ltd. Kernel Module Signing 2025 Kmod: 4627603d2357a2a3f81006370894c221175893e9' -[ 4.907559] Loaded X.509 cert 'Canonical Ltd. Kernel Module Signing: 88f752e560a1e0737e31163a466ad7b70a850c19' -[ 4.908180] blacklist: Loading compiled-in revocation X.509 certificates -[ 4.910125] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing: 61482aa2830d0ab2ad5af10b7250da9033ddcef0' -[ 4.910375] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (2017): 242ade75ac4a15e50d50c84b0d45ff3eae707a03' -[ 4.910375] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (ESM 2018): 365188c1d374d6b07c3c8f240f8ef722433d6a8b' -[ 4.911584] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (2019): c0746fd6c5da3ae827864651ad66ae47fe24b3e8' -[ 4.912009] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (2021 v1): a8d54bbb3825cfb94fa13c9f8a594a195c107b8d' -[ 4.912362] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (2021 v2): 4cf046892d6fd3c9a5b03f98d845f90851dc6a8c' -[ 4.912467] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (2021 v3): 100437bb6de6e469b581e61cd66bce3ef4ed53af' -[ 4.930151] Loaded X.509 cert 'Canonical Ltd. Secure Boot Signing (Ubuntu Core 2019): c1d57b8f6b743f23ee41f4f7ee292f06eecadfb9' -[ 5.212326] Key type .fscrypt registered -[ 5.212563] Key type fscrypt-provisioning registered -[ 5.245722] Key type encrypted registered -[ 5.246576] AppArmor: AppArmor sha256 policy hashing enabled -[ 5.247257] ima: No TPM chip found, activating TPM-bypass! -[ 5.247442] Loading compiled-in module X.509 certificates -[ 5.248860] Loaded X.509 cert 'Build time autogenerated kernel key: aaf6cface807121d2b25f83f2288de5f0eb7acff' -[ 5.248860] ima: Allocated hash algorithm: sha256 -[ 5.250754] ima: No architecture policies found -[ 5.253597] evm: Initialising EVM extended attributes: -[ 5.254134] evm: security.selinux -[ 5.254666] evm: security.SMACK64 -[ 5.254780] evm: security.SMACK64EXEC -[ 5.254875] evm: security.SMACK64TRANSMUTE -[ 5.254977] evm: security.SMACK64MMAP -[ 5.255066] evm: security.apparmor -[ 5.255149] evm: security.ima -[ 5.255225] evm: security.capability -[ 5.303128] evm: HMAC attrs: 0x1 -[ 5.363667] PM: Magic number: 10:620:823 -[ 5.368500] powernow_k8: Power state transitions not supported -[ 5.374692] powernow_k8: Power state transitions not supported -[ 5.638671] RAS: Correctable Errors collector initialized. -[ 5.644733] Unstable clock detected, switching default tracing clock to "global" -[ 5.644733] If you want to keep using the local clock, then add: -[ 5.644733] "trace_clock=local" -[ 5.644733] on the kernel command line -[ 5.732529] clk: Disabling unused clocks -[ 5.901153] Freeing unused decrypted memory: 2028K -[ 6.196336] Freeing unused kernel image (initmem) memory: 4932K -[ 6.196681] Write protecting the kernel read-only data: 38912k -[ 6.257481] Freeing unused kernel image (rodata/data gap) memory: 1956K -[ 6.992659] x86/mm: Checked W+X mappings: passed, no W+X pages found. -[ 6.996439] Run /init as init process -Linux version 6.8.0-142-generic (buildd@lcy02-amd64-049) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #142-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 2 14:24:27 UTC 2026 -/sys/devices/system/cpu/vulnerabilities/gather_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/indirect_target_selection:Not affected -/sys/devices/system/cpu/vulnerabilities/itlb_multihit:Not affected -/sys/devices/system/cpu/vulnerabilities/l1tf:Not affected -/sys/devices/system/cpu/vulnerabilities/mds:Not affected -/sys/devices/system/cpu/vulnerabilities/meltdown:Not affected -/sys/devices/system/cpu/vulnerabilities/mmio_stale_data:Not affected -/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/retbleed:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow:Not affected -/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Not affected -/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization -/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Retpolines; STIBP: disabled; RSB filling; PBRSB-eIBRS: Not affected; BHI: Not affected -/sys/devices/system/cpu/vulnerabilities/srbds:Not affected -/sys/devices/system/cpu/vulnerabilities/tsa:Not affected -/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Not affected -/sys/devices/system/cpu/vulnerabilities/vmscape:Not affected -[ 7.516080] ACPI: PM: Preparing to enter system sleep state S5 -[ 7.521893] reboot: Power down diff --git a/kernel/cpuvuln/src/counters.rs b/kernel/cpuvuln/src/counters.rs deleted file mode 100644 index 4ad06afcb62..00000000000 --- a/kernel/cpuvuln/src/counters.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Which of a CPU's model-specific counters exist, as Linux's `msr` PMU decides -//! (`arch/x86/events/msr.c`): a counter a CPU lacks is `#GP` to read, and the -//! kernel has no fault fixup, so it reads only what this admits. -//! -//! **One departure from Linux**: `MSR_SMI_COUNT` is not admitted under a -//! hypervisor (`CPUID.1:ECX[31]`). Linux admits it there and its probe read -//! decides (`probe.c:43-47`), but a hypervisor answers it with its own count -//! for the guest (QEMU's `env->msr_smi_count`), not the firmware's. `APERF` -//! and `MPERF` are admitted wherever leaf 6 says, a hypervisor's included, as -//! Linux admits them: one that enumerates them and faults the read ends the -//! boot that read them first. - -use crate::{table, Ident, Vendor, CPUID_1_ECX_HYPERVISOR}; - -/// What one CPU says about itself, read on that CPU. -#[derive(Clone, Copy, Debug)] -pub struct CounterFacts { - pub vendor: Vendor, - /// CPUID.1:EAX. - pub signature: u32, - pub cpuid_1_ecx: u32, - /// 0 unless CPUID.0:EAX reaches 6, as `init_scattered_cpuid_features` - /// reads it (`scattered.c:65-71`). - pub cpuid_6_ecx: u32, -} - -/// The counters a CPU has. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Counters { - /// `IA32_APERF` and `IA32_MPERF`. - pub aperf_mperf: bool, - /// `MSR_SMI_COUNT`. - pub smi: bool, -} - -/// `X86_FEATURE_APERFMPERF` (`scattered.c:27`). -const CPUID_6_ECX_APERFMPERF: u32 = 1 << 0; - -pub fn counters(facts: &CounterFacts) -> Counters { - Counters { - // `test_aperfmperf` (`msr.c:20-23,158-159`), whatever the vendor. - aperf_mperf: facts.cpuid_6_ecx & CPUID_6_ECX_APERFMPERF != 0, - // `test_intel` (`msr.c:161`), outside a hypervisor. - smi: table::counts_smis(&Ident::new(facts.vendor, facts.signature)) - && facts.cpuid_1_ecx & CPUID_1_ECX_HYPERVISOR == 0, - } -} - -#[cfg(test)] -mod tests { - extern crate std; - - use std::collections::BTreeSet; - use std::vec::Vec; - - use super::*; - - /// The T14's CPU 0, off `fixtures/t14/cpuid.txt`. - fn t14() -> CounterFacts { - let leaves: Vec> = include_str!("../fixtures/t14/cpuid.txt") - .lines() - .map(|l| l.split(' ').map(|w| u32::from_str_radix(w, 16).expect(w)).collect()) - .collect(); - let leaf = |n: u32| leaves.iter().find(|l| l[..2] == [n, 0]).expect("a leaf the capture read"); - let id: Vec = [leaf(0)[3], leaf(0)[5], leaf(0)[4]].iter().flat_map(|r| r.to_le_bytes()).collect(); - assert!(leaf(0)[2] >= 6, "the T14 reaches leaf 6"); - CounterFacts { - vendor: Vendor::from_id(id[..].try_into().unwrap()), - signature: leaf(1)[2], - cpuid_1_ecx: leaf(1)[4], - cpuid_6_ecx: leaf(6)[4], - } - } - - /// QEMU 11.1.1's `qemu64`, the TCG model: `AuthenticAMD` family 0xF model - /// 0x6B (`target/i386/cpu.c:3545-3550`), and leaf 6's ECX zero on every - /// model (`cpu.c:8777-8782`). - const TCG: CounterFacts = CounterFacts { - vendor: Vendor::Amd, - signature: 0x0006_0fb1, - cpuid_1_ecx: 0, - cpuid_6_ecx: 0, - }; - - /// Linux on the T14 listed exactly the events whose test and probe read - /// passed (`probe.c:28-59`), so its `perf stat` over them is its verdict on - /// that CPU, and this is held to it. - #[test] - fn the_t14_has_what_linux_counted_on_it() { - let counted: BTreeSet<&str> = include_str!("../fixtures/t14/perf-msr.txt") - .lines() - .filter_map(|l| l.split_whitespace().nth(2)?.strip_prefix("msr/")?.strip_suffix('/')) - .collect(); - assert_eq!(counted, BTreeSet::from(["aperf", "mperf", "smi", "tsc"])); - assert_eq!(counters(&t14()), Counters { aperf_mperf: true, smi: true }); - } - - #[test] - fn the_tcg_model_has_none() { - assert_eq!(counters(&TCG), Counters { aperf_mperf: false, smi: false }); - } - - /// Under a hypervisor the SMI count is the hypervisor's, and APERF and - /// MPERF stay whatever leaf 6 says. - #[test] - fn a_guest_counts_no_smis() { - let guest = CounterFacts { cpuid_1_ecx: t14().cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, ..t14() }; - assert_eq!(counters(&guest), Counters { aperf_mperf: true, smi: false }); - } - - #[test] - fn an_amd_with_leaf_6_has_aperf_and_mperf_and_no_smi_count() { - let zen = CounterFacts { cpuid_6_ecx: CPUID_6_ECX_APERFMPERF, ..TCG }; - assert_eq!(counters(&zen), Counters { aperf_mperf: true, smi: false }); - } - - /// The model list is the T14's model and Linux's other rows, and nothing - /// in family 6 that Linux leaves out. - #[test] - fn an_intel_model_off_the_list_counts_no_smis() { - // Family 6 model 0x8C stepping 1 is the T14's; 0x66 is CANNONLAKE_L, - // which `test_intel` does not name. - let model = |m: u32| CounterFacts { signature: 0x0000_0601 | (m >> 4) << 16 | (m & 0xf) << 4, ..t14() }; - assert!(counters(&model(0x8C)).smi); - assert!(!counters(&model(0x66)).smi); - // Family 0xF Intel is outside `test_intel`'s family, and the T14's - // family and model on another vendor outside its vendor. - assert!(!counters(&CounterFacts { signature: 0x0000_0f41, ..t14() }).smi); - assert!(!counters(&CounterFacts { vendor: Vendor::Centaur, ..t14() }).smi); - } -} diff --git a/kernel/cpuvuln/src/erratum.rs b/kernel/cpuvuln/src/erratum.rs deleted file mode 100644 index ee4844c4e2f..00000000000 --- a/kernel/cpuvuln/src/erratum.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Whether AMD erratum 1386, "XSAVES Instruction May Fail to Save XMM -//! Registers to the Provided State Save Area", names a CPU: after an `XRSTORS` -//! that put the XMM registers in their initial state, an `XSAVES` may skip -//! them while `MXCSR` has changed, and a later restore hands a thread an older -//! XMM value of its own. -//! -//! **AMD's revision guides decide, not Linux**, wherever AMD publishes one, -//! each read at the revision named here, and each saying "No fix planned", so -//! no microcode revision lifts the erratum: -//! -//! | guide | parts | lists 1386 against | -//! |---|---|---| -//! | 55449 rev 1.21 | family 17h models 00h-0Fh | ZP-B2 (`00800F12h`) alone; not ZP-B1 (`00800F11h`) or PiR-B2 (`00800F82h`) | -//! | 56323 rev 1.01 | family 17h models 30h-3Fh | Rome-B0 (`00830F10h`), its one revision | -//! | 56683 rev 1.08 | family 19h models 00h-0Fh | Milan-B1 (`00A00F11h`) and Milan-B2 (`00A00F12h`) | -//! | 57095 rev 1.01 | family 19h models 10h-1Fh | nothing | -//! -//! AMD publishes no guide for family 17h's other models, so there the scope -//! is the models Linux files as Zen1 or Zen2 (`bsp_init_amd`, -//! `arch/x86/kernel/cpu/amd.c:482-492` at upstream `87db7795`, not at this -//! crate's pinned tag), each of which runs `fix_erratum_1386` (`:887-903`); -//! `issues/amd-erratum-1386s-scope-rests-on-linux-where-amd-publishes-no-revision-guide.md` -//! owns that gap. - -use crate::{Ident, Vendor}; - -/// `(family, first model, last model)` the erratum names. -const NAMED: [(u32, u32, u32); 7] = [ - // 55449's and 56323's ranges, and Linux's Zen1 and Zen2 beside them. - (0x17, 0x00, 0x2f), - (0x17, 0x30, 0x4f), - (0x17, 0x50, 0x5f), - (0x17, 0x60, 0x7f), - (0x17, 0x90, 0x91), - (0x17, 0xa0, 0xaf), - // 56683's. - (0x19, 0x00, 0x0f), -]; - -/// `(family, model, stepping)` of the revisions 55449 lists without it. -const CLEARED: [(u32, u32, u32); 2] = [(0x17, 0x01, 0x1), (0x17, 0x08, 0x2)]; - -/// Whether the erratum names the CPU whose CPUID.0 vendor is `vendor` and -/// whose CPUID.1:EAX is `signature`, so that it must not save with `XSAVES`. -pub fn erratum_1386(vendor: Vendor, signature: u32) -> bool { - let id = Ident::new(vendor, signature); - id.vendor == Vendor::Amd - && NAMED.iter().any(|&(family, first, last)| id.family == family && (first..=last).contains(&id.model)) - && !CLEARED.contains(&(id.family, id.model, id.stepping)) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn amd(signature: u32) -> bool { - erratum_1386(Vendor::Amd, signature) - } - - #[test] - fn each_revision_amds_guides_list_it_against_is_named() { - for signature in [0x0080_0f12, 0x0083_0f10, 0x00a0_0f11, 0x00a0_0f12] { - assert!(amd(signature), "{signature:#x}"); - } - } - - #[test] - fn the_revisions_55449_lists_without_it_are_not_named() { - assert!(!amd(0x0080_0f11)); - assert!(!amd(0x0080_0f82)); - } - - #[test] - fn genoa_which_57095_lists_without_it_is_not_named() { - assert!(!amd(0x00a1_0f11)); - } - - /// The AMD bench, a Ryzen 7 4800U: family 17h model 60h as its Linux - /// reports it, at whichever stepping, which its recorded words do not - /// carry. - #[test] - fn the_amd_bench_is_named_at_every_stepping() { - for stepping in 0..16 { - assert!(amd(0x0086_0f00 | stepping), "stepping {stepping}"); - } - } - - #[test] - fn a_family_17h_model_no_source_names_is_not_named() { - for model in [0x80u32, 0x8f, 0x92, 0x9f, 0xb0, 0xff] { - assert!(!amd(0x0080_0f00 | (model >> 4) << 16 | (model & 0xf) << 4), "{model:#x}"); - } - } - - /// The T14's Tiger Lake, TCG's `qemu64` (`AuthenticAMD` family 0xF), and - /// a Hygon family 18h part, Zen1's sibling, which no AMD guide covers. - #[test] - fn no_other_vendor_or_family_is_named() { - assert!(!erratum_1386(Vendor::Intel, 0x0008_06c1)); - assert!(!erratum_1386(Vendor::Intel, 0x0080_0f12)); - assert!(!amd(0x0006_0fb1)); - assert!(!erratum_1386(Vendor::Hygon, 0x0090_0f02)); - } -} diff --git a/kernel/cpuvuln/src/hwp.rs b/kernel/cpuvuln/src/hwp.rs deleted file mode 100644 index 636f0763913..00000000000 --- a/kernel/cpuvuln/src/hwp.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Whether a CPU's performance request is declared through HWP, and the -//! request: the one `intel_pstate` asks of it under the `powersave` governor -//! at its defaults, read at upstream Linux `v6.8`'s -//! `drivers/cpufreq/intel_pstate.c` and `arch/x86/include/asm/msr-index.h`, -//! not at this crate's pinned tag. The minimum is `MSR_PLATFORM_INFO[47:40]` -//! (`core_get_min_pstate`, `:1859-1864`), the maximum the CPU's highest level, -//! turbo included (`__intel_pstate_get_hwp_cap`, `:940-948`), and the -//! preference `HWP_EPP_BALANCE_PERFORMANCE` (`msr-index.h:500`), the -//! `balance_performance` it leaves on every CPU `intel_epp_balance_perf` -//! (`:3410-3419`) does not list. -//! -//! **The refusals are ToyOS's, not Linux's**: `intel_pstate` drives hybrid -//! CPUs on a scale of their own and leaves other vendors to other drivers, -//! and neither is modelled here. - -use crate::{Ident, Vendor}; - -/// What one CPU says about itself, read on that CPU. -#[derive(Clone, Copy, Debug)] -pub struct HwpFacts { - pub vendor: Vendor, - /// CPUID.1:EAX. - pub signature: u32, - /// 0 unless CPUID.0:EAX reaches 6. - pub cpuid_6_eax: u32, - /// 0 unless CPUID.0:EAX reaches 6. - pub cpuid_6_ecx: u32, - /// 0 unless CPUID.0:EAX reaches 7. - pub cpuid_7_0_edx: u32, -} - -/// A CPU whose request is declared. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Hwp { - /// `IA32_HWP_INTERRUPT` exists (CPUID.6:EAX[8]); it is `#GP` where not. - pub notify: bool, -} - -/// Why a CPU's request is not declared, and its performance state stays what -/// firmware left. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum HwpRefusal { - NoHwp, - NotIntel, - NotFamily6, - NoEpp, - NoPackageRequest, - NoEnergyPerfBias, - Hybrid, -} - -impl HwpRefusal { - pub const fn reason(self) -> &'static str { - match self { - Self::NoHwp => "no HWP (CPUID.6:EAX[7])", - Self::NotIntel => "HWP on a CPU that is not Intel's, and the minimum is MSR_PLATFORM_INFO's, which is Intel's", - Self::NotFamily6 => "an Intel CPU outside family 6, which no CPUID bit enumerates MSR_PLATFORM_INFO on", - Self::NoEpp => "HWP without an energy/performance preference (CPUID.6:EAX[10])", - Self::NoPackageRequest => "HWP without a package request (CPUID.6:EAX[11])", - Self::NoEnergyPerfBias => "no energy/performance bias (CPUID.6:ECX[3])", - Self::Hybrid => "a hybrid CPU (CPUID.(7,0):EDX[15]), whose HWP scale is not its ratio scale", - } - } -} - -// `arch/x86/include/asm/cpufeatures.h`'s bits at their CPUID positions. -const CPUID_6_EAX_HWP: u32 = 1 << 7; -const CPUID_6_EAX_HWP_NOTIFY: u32 = 1 << 8; -const CPUID_6_EAX_HWP_EPP: u32 = 1 << 10; -const CPUID_6_EAX_HWP_PKG_REQ: u32 = 1 << 11; -const CPUID_6_ECX_EPB: u32 = 1 << 3; -const CPUID_7_0_EDX_HYBRID_CPU: u32 = 1 << 15; - -/// **All or nothing**: a CPU lacking one register the declaration writes gets -/// none of them, so no reader takes firmware's value of one for the -/// declaration's. -pub fn hwp(facts: &HwpFacts) -> Result { - let eax = facts.cpuid_6_eax; - let refusal = if eax & CPUID_6_EAX_HWP == 0 { - Some(HwpRefusal::NoHwp) - } else if facts.vendor != Vendor::Intel { - Some(HwpRefusal::NotIntel) - } else if Ident::new(facts.vendor, facts.signature).family != 6 { - Some(HwpRefusal::NotFamily6) - } else if eax & CPUID_6_EAX_HWP_EPP == 0 { - Some(HwpRefusal::NoEpp) - } else if eax & CPUID_6_EAX_HWP_PKG_REQ == 0 { - Some(HwpRefusal::NoPackageRequest) - } else if facts.cpuid_6_ecx & CPUID_6_ECX_EPB == 0 { - Some(HwpRefusal::NoEnergyPerfBias) - } else if facts.cpuid_7_0_edx & CPUID_7_0_EDX_HYBRID_CPU != 0 { - Some(HwpRefusal::Hybrid) - } else { - None - }; - match refusal { - Some(refusal) => Err(refusal), - None => Ok(Hwp { notify: eax & CPUID_6_EAX_HWP_NOTIFY != 0 }), - } -} - -/// `HWP_EPP_BALANCE_PERFORMANCE`. -pub const HWP_EPP: u64 = 0x80; - -/// `IA32_HWP_REQUEST` for a CPU whose `IA32_HWP_CAPABILITIES` reads -/// `capabilities`, in a package whose `MSR_PLATFORM_INFO` reads -/// `platform_info`: no desired level, no activity window, no package control, -/// so the CPU chooses between the two levels itself (SDM Vol. 3B, "Managing -/// HWP"; `msr-index.h:495-504`). -pub const fn hwp_request(capabilities: u64, platform_info: u64) -> u64 { - let min = (platform_info >> 40) & 0xff; - let max = capabilities & 0xff; - min | max << 8 | HWP_EPP << 24 -} - -#[cfg(test)] -mod tests { - extern crate std; - - use std::vec::Vec; - - use super::*; - - /// The T14's CPU 0, off `fixtures/t14/cpuid.txt`. - fn t14() -> HwpFacts { - let leaves: Vec> = include_str!("../fixtures/t14/cpuid.txt") - .lines() - .map(|l| l.split(' ').map(|w| u32::from_str_radix(w, 16).expect(w)).collect()) - .collect(); - let leaf = |n: u32| leaves.iter().find(|l| l[..2] == [n, 0]).expect("a leaf the capture read"); - let id: Vec = [leaf(0)[3], leaf(0)[5], leaf(0)[4]].iter().flat_map(|r| r.to_le_bytes()).collect(); - assert!(leaf(0)[2] >= 7, "the T14 reaches leaf 7"); - HwpFacts { - vendor: Vendor::from_id(id[..].try_into().unwrap()), - signature: leaf(1)[2], - cpuid_6_eax: leaf(6)[2], - cpuid_6_ecx: leaf(6)[4], - cpuid_7_0_edx: leaf(7)[5], - } - } - - /// `cpuinfo.txt` lists `hwp hwp_notify hwp_act_window hwp_epp - /// hwp_pkg_req` and `epb`, and no `hybrid_cpu`. - #[test] - fn the_t14_is_declared_with_its_notification() { - let flags = include_str!("../fixtures/t14/cpuinfo.txt") - .lines() - .find_map(|l| l.strip_prefix("flags\t\t: ")) - .expect("a flags line"); - for flag in ["hwp", "hwp_notify", "hwp_epp", "hwp_pkg_req", "epb"] { - assert!(flags.split(' ').any(|f| f == flag), "{flag}"); - } - assert!(!flags.split(' ').any(|f| f == "hybrid_cpu")); - assert_eq!(hwp(&t14()), Ok(Hwp { notify: true })); - assert_eq!(hwp(&HwpFacts { cpuid_6_eax: t14().cpuid_6_eax & !CPUID_6_EAX_HWP_NOTIFY, ..t14() }), Ok(Hwp { notify: false })); - } - - /// QEMU's `qemu64`, the TCG model: `AuthenticAMD`, and no HWP in leaf 6. - #[test] - fn the_tcg_model_is_refused() { - let tcg = HwpFacts { vendor: Vendor::Amd, signature: 0x0006_0fb1, cpuid_6_eax: 1 << 2, cpuid_6_ecx: 0, cpuid_7_0_edx: 0 }; - assert_eq!(hwp(&tcg), Err(HwpRefusal::NoHwp)); - } - - #[test] - fn a_missing_register_refuses_the_whole_request_by_name() { - let without = |eax: u32, ecx: u32| hwp(&HwpFacts { cpuid_6_eax: t14().cpuid_6_eax & !eax, cpuid_6_ecx: t14().cpuid_6_ecx & !ecx, ..t14() }); - assert_eq!(without(CPUID_6_EAX_HWP, 0), Err(HwpRefusal::NoHwp)); - assert_eq!(without(CPUID_6_EAX_HWP_EPP, 0), Err(HwpRefusal::NoEpp)); - assert_eq!(without(CPUID_6_EAX_HWP_PKG_REQ, 0), Err(HwpRefusal::NoPackageRequest)); - assert_eq!(without(0, CPUID_6_ECX_EPB), Err(HwpRefusal::NoEnergyPerfBias)); - assert_eq!(hwp(&HwpFacts { cpuid_7_0_edx: CPUID_7_0_EDX_HYBRID_CPU, ..t14() }), Err(HwpRefusal::Hybrid)); - assert_eq!(hwp(&HwpFacts { vendor: Vendor::Amd, ..t14() }), Err(HwpRefusal::NotIntel)); - } - - /// The family is `x86_family`'s: 0xF plus the extended field, never the - /// extended field beside family 6. - #[test] - fn a_cpu_outside_family_6_is_refused_by_name() { - let of = |signature: u32| hwp(&HwpFacts { signature, ..t14() }); - assert_eq!(of(0x0000_0f41), Err(HwpRefusal::NotFamily6)); - assert_eq!(of(0x0030_0f00), Err(HwpRefusal::NotFamily6)); - assert_eq!(of(0x0ff0_0600), Ok(Hwp { notify: true })); - } - - /// Each field where `msr-index.h` puts it, from the bits its source names - /// and no other. - #[test] - fn the_request_is_the_minimum_ratio_the_highest_level_and_the_preference() { - assert_eq!(hwp_request(0, 0), 0x8000_0000); - assert_eq!(hwp_request(0xffff_ff2a, 0), 0x8000_2a00); - assert_eq!(hwp_request(0, 0x04 << 40), 0x8000_0004); - assert_eq!(hwp_request(0, !(0xff << 40)), 0x8000_0000); - assert_eq!(hwp_request(u64::MAX, u64::MAX), 0x8000_ffff); - } -} diff --git a/kernel/cpuvuln/src/lib.rs b/kernel/cpuvuln/src/lib.rs deleted file mode 100644 index 1ad1af1b297..00000000000 --- a/kernel/cpuvuln/src/lib.rs +++ /dev/null @@ -1,1105 +0,0 @@ -//! Linux's decision about a CPU's speculative-execution vulnerabilities, pure. -//! -//! [`decide`] maps what a CPU reports about itself ([`Facts`]) to the line the -//! pinned Linux prints in each `/sys/devices/system/cpu/vulnerabilities/*` file -//! ([`Decision::line`]) and to the mitigation state it selects ([`Decision`]'s -//! fields), under the default command line and the pinned config. That Linux is -//! tag `Ubuntu-6.8.0-142.142` (commit 53e5d07aac02) of Ubuntu's noble kernel -//! with `/boot/config-6.8.0-142-generic`; every `path:line` here is read there, -//! and `common.c`, `bugs.c`, `intel.c`, `amd.c`, `hygon.c`, `feat_ctl.c` and -//! `tsx.c` are in `arch/x86/kernel/cpu/`. The config's `CONFIG_CPU_UNRET_ENTRY`, -//! `CONFIG_CPU_IBPB_ENTRY`, `CONFIG_CPU_SRSO`, `CONFIG_MITIGATION_TSA` and -//! `CONFIG_MITIGATION_VMSCAPE` are `y`, and `CONFIG_KVM_INTEL` is `m` -//! (`debian.master/config/annotations:3348,3319,3339,8048,411,7262`). -//! -//! Parity is exact: a line that rests on something the facts do not carry is -//! [`Unmodelled`], never approximated. -//! -//! [`counters`] is the same tag's other verdict on a CPU: which of its -//! model-specific counters exist. [`hwp`] is the third: whether its -//! performance request is declared, and [`hwp_request`] the request. -//! [`erratum_1386`] is AMD's, not Linux's: whether its `XSAVES` erratum names -//! the CPU. -//! -//! Pure: no I/O, no allocation, no `unsafe`. The caller reads the facts. - -#![no_std] -#![forbid(unsafe_code)] - -use core::fmt; - -mod counters; -mod erratum; -mod hwp; -mod table; - -pub use counters::{counters, CounterFacts, Counters}; -pub use erratum::erratum_1386; -pub use hwp::{hwp, hwp_request, Hwp, HwpFacts, HwpRefusal, HWP_EPP}; -#[cfg(test)] -mod tests; - -use table::{ - blacklisted, whitelisted, GDS, ITS, MMIO, MMIO_SBDS, MSBDS_ONLY, NO_BHI, - NO_EIBRS_PBRSB, NO_ITLB_MULTIHIT, NO_L1TF, NO_MDS, NO_MELTDOWN, NO_MMIO, NO_SPECTRE_V2, - NO_SPECULATION, NO_SSB, RETBLEED, RFDS, SRBDS, SRSO, TSA, VMSCAPE, -}; - -/// The vendors Linux's tables name, by CPUID.0's identification string -/// (`get_cpu_vendor`, `common.c:912-936`, over each `cpu_dev`'s `c_ident`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Vendor { - Intel, - Amd, - Hygon, - Centaur, - Zhaoxin, - Unknown, -} - -impl Vendor { - /// CPUID.0's EBX, EDX and ECX bytes, in that order. - pub const fn from_id(id: &[u8; 12]) -> Self { - match id { - b"GenuineIntel" => Self::Intel, - b"AuthenticAMD" => Self::Amd, - b"HygonGenuine" => Self::Hygon, - b"CentaurHauls" => Self::Centaur, - b" Shanghai " => Self::Zhaoxin, - _ => Self::Unknown, - } - } -} - -/// What the boot CPU reports, read the way Linux reads it. -#[derive(Clone, Copy, Debug)] -pub struct Facts { - pub vendor: Vendor, - /// CPUID.1:EAX; family, model and stepping are derived from it as - /// `arch/x86/lib/cpu.c` derives them. - pub signature: u32, - /// The microcode revision as `/proc/cpuinfo` reports it. - pub microcode: u32, - pub cpuid_1_ecx: u32, - pub cpuid_7_0_ebx: u32, - pub cpuid_7_0_edx: u32, - /// Read wherever leaf 7 exists (`scattered.c:59-79`). - pub cpuid_7_2_edx: u32, - /// 0 unless CPUID.0x80000000:EAX reaches 0x80000008 (`common.c:1055-1084`). - pub cpuid_8000_0008_ebx: u32, - /// 0 unless CPUID.0x80000000:EAX reaches 0x80000021 (`common.c:1055-1084`). - pub cpuid_8000_0021_eax: u32, - /// 0 unless CPUID.0x80000000:EAX reaches 0x80000021 (`scattered.c:52-53`). - pub cpuid_8000_0021_ecx: u32, - /// `IA32_ARCH_CAPABILITIES` (0x10A), 0 unless CPUID.(7,0):EDX[29] - /// enumerates it (`common.c:1353-1361`): the read is `#GP` there. - pub arch_capabilities: u64, - /// `IA32_MCU_OPT_CTRL` (0x123), present exactly where - /// `ARCH_CAPABILITIES.GDS_CTRL` enumerates it. - pub mcu_opt_ctrl: Option, - /// `IA32_FEAT_CTL` (0x3A) where `init_ia32_feat_ctl` reads it, for Intel, - /// Centaur and Zhaoxin (`intel.c:715`, `centaur.c:217`, `zhaoxin.c:97`), and - /// the read completes; `None` where it is `#GP` (`feat_ctl.c:119-123`). - pub feat_ctl: Option, - /// Whether `rdmsr` of `MSR_AMD64_LS_CFG` (0xC0011020) completes without - /// `#GP`, present exactly where it is probed: AMD family 0x15 to 0x17 - /// (`amd.c:576-596`) and Hygon (`hygon.c:228-239`), enumerating neither - /// `AMD_SSBD` nor `VIRT_SSBD`. - pub ls_cfg_readable: Option, - /// Whether `wrmsr` of `IA32_PRED_CMD` with `SBPB` (bit 7) completes without - /// `#GP`, present exactly where `early_init_amd` probes it - /// (`amd.c:799-806`): AMD from family 0x19, outside a hypervisor, without - /// CPUID's `IBPB_BRTYPE`. - pub sbpb_write_accepted: Option, - /// More than one thread per core, every sibling online: `sched_smt_active()`, - /// which under the default command line is also `cpu_smt_possible()`. - pub smt: bool, -} - -// Linux's feature bits (`arch/x86/include/asm/cpufeatures.h`, -// `scattered.c:26-57`) at their CPUID positions. -const CPUID_1_ECX_VMX: u32 = 1 << 5; -const CPUID_1_ECX_AVX: u32 = 1 << 28; -const CPUID_1_ECX_RDRAND: u32 = 1 << 30; -const CPUID_1_ECX_HYPERVISOR: u32 = 1 << 31; -const CPUID_7_0_EBX_RTM: u32 = 1 << 11; -const CPUID_7_0_EBX_RDSEED: u32 = 1 << 18; -const CPUID_7_0_EDX_SRBDS_CTRL: u32 = 1 << 9; -const CPUID_7_0_EDX_MD_CLEAR: u32 = 1 << 10; -const CPUID_7_0_EDX_RTM_ALWAYS_ABORT: u32 = 1 << 11; -const CPUID_7_0_EDX_SPEC_CTRL: u32 = 1 << 26; -const CPUID_7_0_EDX_INTEL_STIBP: u32 = 1 << 27; -const CPUID_7_0_EDX_FLUSH_L1D: u32 = 1 << 28; -const CPUID_7_0_EDX_ARCH_CAPABILITIES: u32 = 1 << 29; -const CPUID_7_0_EDX_SPEC_CTRL_SSBD: u32 = 1 << 31; -const CPUID_7_2_EDX_RRSBA_CTRL: u32 = 1 << 2; -const CPUID_7_2_EDX_BHI_CTRL: u32 = 1 << 4; -const CPUID_8000_0008_EBX_AMD_IBPB: u32 = 1 << 12; -const CPUID_8000_0008_EBX_AMD_IBRS: u32 = 1 << 14; -const CPUID_8000_0008_EBX_AMD_STIBP: u32 = 1 << 15; -const CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON: u32 = 1 << 17; -const CPUID_8000_0008_EBX_AMD_SSBD: u32 = 1 << 24; -const CPUID_8000_0008_EBX_VIRT_SSBD: u32 = 1 << 25; -const CPUID_8000_0008_EBX_AMD_SSB_NO: u32 = 1 << 26; -const CPUID_8000_0008_EBX_BTC_NO: u32 = 1 << 29; -const CPUID_8000_0021_EAX_VERW_CLEAR: u32 = 1 << 5; -const CPUID_8000_0021_EAX_AUTOIBRS: u32 = 1 << 8; -const CPUID_8000_0021_EAX_SBPB: u32 = 1 << 27; -const CPUID_8000_0021_EAX_IBPB_BRTYPE: u32 = 1 << 28; -const CPUID_8000_0021_EAX_SRSO_NO: u32 = 1 << 29; -const CPUID_8000_0021_EAX_SRSO_USER_KERNEL_NO: u32 = 1 << 30; -const CPUID_8000_0021_EAX_SRSO_BP_SPEC_REDUCE: u32 = 1 << 31; -const CPUID_8000_0021_ECX_TSA_SQ_NO: u32 = 1 << 1; -const CPUID_8000_0021_ECX_TSA_L1_NO: u32 = 1 << 2; - -// `arch/x86/include/asm/msr-index.h:46-54,101-183,215,821-823`. -const ARCH_CAP_RDCL_NO: u64 = 1 << 0; -const ARCH_CAP_IBRS_ALL: u64 = 1 << 1; -const ARCH_CAP_RSBA: u64 = 1 << 2; -const ARCH_CAP_SSB_NO: u64 = 1 << 4; -const ARCH_CAP_MDS_NO: u64 = 1 << 5; -const ARCH_CAP_PSCHANGE_MC_NO: u64 = 1 << 6; -const ARCH_CAP_TSX_CTRL_MSR: u64 = 1 << 7; -const ARCH_CAP_TAA_NO: u64 = 1 << 8; -const ARCH_CAP_SBDR_SSDP_NO: u64 = 1 << 13; -const ARCH_CAP_FBSDP_NO: u64 = 1 << 14; -const ARCH_CAP_PSDP_NO: u64 = 1 << 15; -const ARCH_CAP_FB_CLEAR: u64 = 1 << 17; -const ARCH_CAP_RRSBA: u64 = 1 << 19; -const ARCH_CAP_PBRSB_NO: u64 = 1 << 24; -const ARCH_CAP_GDS_CTRL: u64 = 1 << 25; -const ARCH_CAP_GDS_NO: u64 = 1 << 26; -const ARCH_CAP_RFDS_NO: u64 = 1 << 27; -const ARCH_CAP_RFDS_CLEAR: u64 = 1 << 28; -const ARCH_CAP_ITS_NO: u64 = 1 << 62; -const GDS_MITG_LOCKED: u64 = 1 << 5; -const FEAT_CTL_LOCKED: u64 = 1 << 0; -const FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX: u64 = 1 << 2; -pub const SPEC_CTRL_IBRS: u64 = 1 << 0; -pub const SPEC_CTRL_STIBP: u64 = 1 << 1; -pub const SPEC_CTRL_RRSBA_DIS_S: u64 = 1 << 6; -pub const SPEC_CTRL_BHI_DIS_S: u64 = 1 << 10; - -/// `boot_cpu_data`'s identity: `x86_vendor`, `x86`, `x86_model`, -/// `x86_stepping`. -pub(crate) struct Ident { - pub(crate) vendor: Vendor, - pub(crate) family: u32, - pub(crate) model: u32, - pub(crate) stepping: u32, -} - -impl Ident { - /// `x86_family`, `x86_model` and `x86_stepping` (`arch/x86/lib/cpu.c:6-37`). - fn new(vendor: Vendor, sig: u32) -> Self { - let mut family = (sig >> 8) & 0xf; - if family == 0xf { - family += (sig >> 20) & 0xff; - } - let mut model = (sig >> 4) & 0xf; - if family >= 0x6 { - model += ((sig >> 16) & 0xf) << 4; - } - Self { vendor, family, model, stepping: sig & 0xf } - } -} - -/// An affected CPU's line that rests on state the facts do not carry. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Unmodelled { - /// The e820 map against `x86_cache_bits` (`bugs.c:2538-2583`) and - /// `kvm_intel`'s state (`bugs.c:3074-3089`). - L1tf, -} - -/// The vulnerabilities files, in the order `drivers/base/cpu.c:615-631` lists -/// them. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Vuln { - Meltdown, - SpectreV1, - SpectreV2, - SpecStoreBypass, - L1tf, - Mds, - TsxAsyncAbort, - ItlbMultihit, - Srbds, - MmioStaleData, - Retbleed, - SpecRstackOverflow, - GatherDataSampling, - RegFileDataSampling, - IndirectTargetSelection, - Tsa, - Vmscape, -} - -impl Vuln { - pub const ALL: [Self; 17] = [ - Self::Meltdown, - Self::SpectreV1, - Self::SpectreV2, - Self::SpecStoreBypass, - Self::L1tf, - Self::Mds, - Self::TsxAsyncAbort, - Self::ItlbMultihit, - Self::Srbds, - Self::MmioStaleData, - Self::Retbleed, - Self::SpecRstackOverflow, - Self::GatherDataSampling, - Self::RegFileDataSampling, - Self::IndirectTargetSelection, - Self::Tsa, - Self::Vmscape, - ]; - - /// The file's name under `/sys/devices/system/cpu/vulnerabilities/`. - pub const fn name(self) -> &'static str { - match self { - Self::Meltdown => "meltdown", - Self::SpectreV1 => "spectre_v1", - Self::SpectreV2 => "spectre_v2", - Self::SpecStoreBypass => "spec_store_bypass", - Self::L1tf => "l1tf", - Self::Mds => "mds", - Self::TsxAsyncAbort => "tsx_async_abort", - Self::ItlbMultihit => "itlb_multihit", - Self::Srbds => "srbds", - Self::MmioStaleData => "mmio_stale_data", - Self::Retbleed => "retbleed", - Self::SpecRstackOverflow => "spec_rstack_overflow", - Self::GatherDataSampling => "gather_data_sampling", - Self::RegFileDataSampling => "reg_file_data_sampling", - Self::IndirectTargetSelection => "indirect_target_selection", - Self::Tsa => "tsa", - Self::Vmscape => "vmscape", - } - } -} - -/// `tsx_ctrl_state` after `tsx_init` (`tsx.c:158-245`) under -/// `CONFIG_X86_INTEL_TSX_MODE_OFF=y`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Tsx { - /// No `IA32_TSX_CTRL`: RTM stays as CPUID enumerates it. - NotSupported, - /// RTM always aborts; its enumeration is cleared. - RtmAlwaysAbort, - /// `IA32_TSX_CTRL` disables RTM and clears its enumeration. - Disable, -} - -/// `spectre_v2_enabled` where the CPU has Spectre v2 (`bugs.c:1860-2045`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum SpectreV2 { - Retpoline, - Eibrs, - Ibrs, -} - -/// What `bhi_select_mitigation` (`bugs.c:1831-1858`) chose where the CPU has -/// BHI, as `spectre_bhi_state` (`bugs.c:3220-3236`) reads it. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Bhi { - /// `SPEC_CTRL.BHI_DIS_S`. - BhiDisS, - /// `clear_bhb_loop` on syscall entry and VM exit. - SwLoop, - /// Retpolines with RRSBA disabled. - Retpoline, -} - -/// `spectre_v2_user_stibp` (`bugs.c:1492-1591`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Stibp { - None, - Prctl, - StrictPreferred, -} - -/// `retbleed_mitigation` (`bugs.c:1054-1194`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Retbleed { - None, - Unret, - Ibrs, - Eibrs, -} - -/// `ssb_mode` (`bugs.c:2172-2238`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Ssb { - None, - Prctl, -} - -/// `mds_mitigation` where the CPU has MDS (`bugs.c:266-283`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Mds { - Full, - Vmwerv, -} - -/// `taa_mitigation` where the CPU has TAA (`bugs.c:327-382`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Taa { - UcodeNeeded, - Verw, - TsxDisabled, -} - -/// `mmio_mitigation` where the CPU has MMIO Stale Data (`bugs.c:424-478`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Mmio { - UcodeNeeded, - Verw, -} - -/// `rfds_mitigation` where the CPU has RFDS (`bugs.c:520-533`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Rfds { - Verw, - UcodeNeeded, -} - -/// `srbds_mitigation` where the CPU has SRBDS (`bugs.c:678-700`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Srbds { - UcodeNeeded, - Full, - TsxOff, - Hypervisor, -} - -/// `gds_mitigation` where the CPU has GDS (`bugs.c:814-867`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Gds { - UcodeNeeded, - /// `IA32_MCU_OPT_CTRL.GDS_MITG_DIS` cleared on every CPU. - Full, - /// Firmware locked the mitigation on. - FullLocked, - Hypervisor, -} - -/// `vmscape_mitigation` (`bugs.c:2849-2869`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Vmscape { - None, - IbpbExitToUser, - IbpbOnVmexit, -} - -/// `srso_mitigation` where the CPU has SRSO (`bugs.c:2670-2807`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Srso { - /// `SRSO_NO` forced: Zen1/2 with the IBPB microcode and no SMT. - SmtDisabled, - UcodeNeeded, - /// The safe-RET return thunk without the IBPB microcode. - SafeRetUcodeNeeded, - /// The safe-RET return thunk: `srso_alias_return_thunk` on family 0x19, - /// `srso_return_thunk` otherwise. - SafeRet, - IbpbOnVmexit, - BpSpecReduce, -} - -/// `tsa_mitigation` where the CPU has TSA (`bugs.c:2918-2962`). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Tsa { - UcodeNeeded, - Full, -} - -/// The bugs `cpu_set_bug_bits` (`common.c:1414-1578`) sets that a line or a -/// selection reads. -#[derive(Clone, Copy, Debug, Default)] -struct Bugs { - itlb_multihit: bool, - spectre_v1: bool, - spectre_v2: bool, - ssb: bool, - ibrs_enhanced: bool, - eibrs_pbrsb: bool, - mds: bool, - msbds_only: bool, - taa: bool, - srbds: bool, - mmio_stale_data: bool, - mmio_unknown: bool, - retbleed: bool, - gds: bool, - rfds: bool, - bhi: bool, - vmscape: bool, - its: bool, - srso: bool, - tsa: bool, - meltdown: bool, - l1tf: bool, -} - -/// The features Linux has when it selects, after `init_speculation_control` -/// (`common.c:973-1012`), `early_init_intel`'s microcode check -/// (`intel.c:296-309`) and the `LS_CFG_SSBD` probe. -struct Caps { - hypervisor: bool, - ibrs: bool, - ibpb: bool, - stibp: bool, - ssbd: bool, - autoibrs: bool, -} - -/// Linux's selection for one machine. -#[derive(Clone, Copy, Debug)] -pub struct Decision { - pub tsx: Tsx, - pub spectre_v2: Option, - pub bhi: Option, - /// `USE_IBPB`, with `switch_mm` issuing it conditionally - /// (`bugs.c:1527-1550`): the CPU has IBPB and Spectre v2. - pub ibpb: bool, - /// `USE_IBRS_FW`: IBRS around firmware calls. - pub ibrs_fw: bool, - pub stibp: Stibp, - pub retbleed: Retbleed, - pub ssb: Ssb, - pub mds: Option, - pub taa: Option, - pub mmio: Option, - pub rfds: Option, - pub srbds: Option, - pub gds: Option, - /// ITS's aligned branch and return thunks (`bugs.c:1254-1335`), wherever - /// the CPU has ITS. - pub its: bool, - pub vmscape: Vmscape, - pub srso: Option, - pub tsa: Option, - /// `x86_pred_cmd` is `PRED_CMD_SBPB` rather than `PRED_CMD_IBPB` - /// (`bugs.c:2677-2678`). - pub sbpb: bool, - /// `CLEAR_CPU_BUF`: `verw` on every return to user. - pub clear_cpu_buf: bool, - /// `EFER.AUTOIBRS` in place of `SPEC_CTRL.IBRS`. - pub efer_autoibrs: bool, - /// The bits `bugs.c` ORs into `x86_spec_ctrl_base`; the MSR's others are - /// the hardware's. - pub spec_ctrl: u64, - bugs: Bugs, - hypervisor: bool, - autoibrs: bool, - smt: bool, - /// `X86_FEATURE_MSR_IA32_FEAT_CTL` and `X86_FEATURE_VMX`. - vmx: bool, -} - -/// `cpu_set_bug_bits` (`common.c:1414-1578`). `SWAPGS`, `SMT_RSB` and -/// `IBPB_NO_RET` reach no line and no selection here. -fn bug_bits(f: &Facts, id: &Ident, caps: &Caps, arch: u64) -> Bugs { - let mut b = Bugs { - itlb_multihit: !whitelisted(id, NO_ITLB_MULTIHIT) && arch & ARCH_CAP_PSCHANGE_MC_NO == 0, - ..Bugs::default() - }; - if whitelisted(id, NO_SPECULATION) { - return b; - } - b.spectre_v1 = true; - b.spectre_v2 = !whitelisted(id, NO_SPECTRE_V2); - b.ssb = !whitelisted(id, NO_SSB) - && arch & ARCH_CAP_SSB_NO == 0 - && f.cpuid_8000_0008_ebx & CPUID_8000_0008_EBX_AMD_SSB_NO == 0; - b.ibrs_enhanced = arch & ARCH_CAP_IBRS_ALL != 0 || caps.autoibrs; - b.eibrs_pbrsb = - b.ibrs_enhanced && !whitelisted(id, NO_EIBRS_PBRSB) && arch & ARCH_CAP_PBRSB_NO == 0; - b.mds = !whitelisted(id, NO_MDS) && arch & ARCH_CAP_MDS_NO == 0; - // `MSBDS_ONLY` is set within MDS (`common.c:1447-1451`), and only MDS's line - // reads it. - b.msbds_only = whitelisted(id, MSBDS_ONLY); - b.taa = arch & ARCH_CAP_TAA_NO == 0 - && (f.cpuid_7_0_ebx & CPUID_7_0_EBX_RTM != 0 || arch & ARCH_CAP_TSX_CTRL_MSR != 0); - b.srbds = (f.cpuid_1_ecx & CPUID_1_ECX_RDRAND != 0 || f.cpuid_7_0_ebx & CPUID_7_0_EBX_RDSEED != 0) - && blacklisted(id, SRBDS | MMIO_SBDS); - let mmio_immune = arch & (ARCH_CAP_FBSDP_NO | ARCH_CAP_PSDP_NO | ARCH_CAP_SBDR_SSDP_NO) - == ARCH_CAP_FBSDP_NO | ARCH_CAP_PSDP_NO | ARCH_CAP_SBDR_SSDP_NO; - if !mmio_immune { - if blacklisted(id, MMIO) { - b.mmio_stale_data = true; - } else if !whitelisted(id, NO_MMIO) { - b.mmio_unknown = true; - } - } - b.retbleed = f.cpuid_8000_0008_ebx & CPUID_8000_0008_EBX_BTC_NO == 0 - && (blacklisted(id, RETBLEED) || arch & ARCH_CAP_RSBA != 0); - b.gds = blacklisted(id, GDS) - && arch & ARCH_CAP_GDS_NO == 0 - && f.cpuid_1_ecx & CPUID_1_ECX_AVX != 0; - // `vulnerable_to_rfds` (`common.c:1370-1386`). - b.rfds = if arch & ARCH_CAP_RFDS_NO != 0 { - false - } else { - arch & ARCH_CAP_RFDS_CLEAR != 0 || blacklisted(id, RFDS) - }; - b.bhi = !whitelisted(id, NO_BHI) && (b.ibrs_enhanced || caps.hypervisor); - b.vmscape = blacklisted(id, VMSCAPE) && !caps.hypervisor; - // `vulnerable_to_its` (`common.c:1388-1412`). `ITS_NATIVE_ONLY` reaches no - // line under the default command line. - b.its = arch & ARCH_CAP_ITS_NO == 0 - && id.vendor == Vendor::Intel - && f.cpuid_7_2_edx & CPUID_7_2_EDX_BHI_CTRL == 0 - && (caps.hypervisor || blacklisted(id, ITS)); - b.srso = f.cpuid_8000_0021_eax & CPUID_8000_0021_EAX_SRSO_NO == 0 && blacklisted(id, SRSO); - // `tsa_init`'s forced `TSA_*_NO` (`amd.c:517-530`) reaches no family the - // TSA row names, and the Zen-guest clause reads `X86_FEATURE_ZEN`, which - // `init_amd` sets (`amd.c:1039`) from `identify_cpu` (`common.c:1997-1998`), - // after `early_identify_cpu` has run this (`common.c:1732`). Only AMD rows - // name TSA, so the AMD vendor test (`common.c:1555`) is the table's. - b.tsa = f.cpuid_8000_0021_ecx & (CPUID_8000_0021_ECX_TSA_SQ_NO | CPUID_8000_0021_ECX_TSA_L1_NO) - != CPUID_8000_0021_ECX_TSA_SQ_NO | CPUID_8000_0021_ECX_TSA_L1_NO - && blacklisted(id, TSA); - if whitelisted(id, NO_MELTDOWN) || arch & ARCH_CAP_RDCL_NO != 0 { - return b; - } - b.meltdown = true; - b.l1tf = !whitelisted(id, NO_L1TF); - b -} - -/// `spec_ctrl_disable_kernel_rrsba` (`bugs.c:1723-1739`), its first call: -/// whether the kernel's RET predictions are RSB-only after it. -fn disable_kernel_rrsba(f: &Facts, arch: u64, spec_ctrl: &mut u64) -> bool { - if arch & ARCH_CAP_RRSBA == 0 { - return true; - } - if f.cpuid_7_2_edx & CPUID_7_2_EDX_RRSBA_CTRL == 0 { - return false; - } - *spec_ctrl |= SPEC_CTRL_RRSBA_DIS_S; - true -} - -/// Linux's selection over `facts`: `cpu_select_mitigations` (`bugs.c:149-199`) -/// and what it reads. -/// -/// Panics where the facts contradict their own documentation: an -/// `arch_capabilities` read without CPUID.(7,0):EDX[29], or an `mcu_opt_ctrl`, -/// `feat_ctl`, `ls_cfg_readable` or `sbpb_write_accepted` present where Linux -/// does not read it, or one of the first, third or fourth absent where it -/// does. -pub fn decide(facts: &Facts) -> Decision { - let f = facts; - let id = Ident::new(f.vendor, f.signature); - let amd = id.vendor == Vendor::Amd; - // `X86_VENDOR_AMD || X86_VENDOR_HYGON` (`bugs.c:1103-1104,2030-2031,3268-3269`). - let amd_or_hygon = amd || id.vendor == Vendor::Hygon; - assert!( - f.cpuid_7_0_edx & CPUID_7_0_EDX_ARCH_CAPABILITIES != 0 || f.arch_capabilities == 0, - "ARCH_CAPABILITIES {:#x} without CPUID.(7,0):EDX[29]", - f.arch_capabilities - ); - let arch = f.arch_capabilities; - assert_eq!( - f.mcu_opt_ctrl.is_some(), - arch & ARCH_CAP_GDS_CTRL != 0, - "IA32_MCU_OPT_CTRL is read exactly where ARCH_CAPABILITIES.GDS_CTRL" - ); - assert!( - f.feat_ctl.is_none() - || matches!(id.vendor, Vendor::Intel | Vendor::Centaur | Vendor::Zhaoxin), - "IA32_FEAT_CTL is read only where intel.c:715, centaur.c:217 and zhaoxin.c:97 read it" - ); - - let edx7 = f.cpuid_7_0_edx; - let ebx8 = f.cpuid_8000_0008_ebx; - let eax21 = f.cpuid_8000_0021_eax; - let hypervisor = f.cpuid_1_ecx & CPUID_1_ECX_HYPERVISOR != 0; - let amd_ssbd = ebx8 & (CPUID_8000_0008_EBX_VIRT_SSBD | CPUID_8000_0008_EBX_AMD_SSBD) != 0; - assert_eq!( - f.ls_cfg_readable.is_some(), - !amd_ssbd && ((amd && (0x15..=0x17).contains(&id.family)) || id.vendor == Vendor::Hygon), - "MSR_AMD64_LS_CFG is probed exactly where amd.c:576-578 and hygon.c:228-229 probe it" - ); - let brtype_enumerated = eax21 & CPUID_8000_0021_EAX_IBPB_BRTYPE != 0; - assert_eq!( - f.sbpb_write_accepted.is_some(), - amd && id.family >= 0x19 && !hypervisor && !brtype_enumerated, - "PRED_CMD.SBPB is probed exactly where amd.c:799-802 probes it" - ); - let mut caps = Caps { - hypervisor, - ibrs: edx7 & CPUID_7_0_EDX_SPEC_CTRL != 0 || ebx8 & CPUID_8000_0008_EBX_AMD_IBRS != 0, - ibpb: edx7 & CPUID_7_0_EDX_SPEC_CTRL != 0 || ebx8 & CPUID_8000_0008_EBX_AMD_IBPB != 0, - stibp: edx7 & CPUID_7_0_EDX_INTEL_STIBP != 0 || ebx8 & CPUID_8000_0008_EBX_AMD_STIBP != 0, - ssbd: edx7 & CPUID_7_0_EDX_SPEC_CTRL_SSBD != 0 - || amd_ssbd - || f.ls_cfg_readable == Some(true), - autoibrs: eax21 & CPUID_8000_0021_EAX_AUTOIBRS != 0, - }; - // `early_init_amd` (`amd.c:799-806`). Only SRSO reads it, and of SRSO's rows - // (`common.c:1340-1342`) only AMD's names family 0x17. - let native_brtype = !hypervisor - && ((id.family == 0x17 && ebx8 & CPUID_8000_0008_EBX_AMD_IBPB != 0) - || f.sbpb_write_accepted == Some(true)); - let ibpb_brtype = brtype_enumerated || native_brtype; - let sbpb = eax21 & CPUID_8000_0021_EAX_SBPB != 0 || f.sbpb_write_accepted == Some(true); - // `SPEC_CTRL` and `INTEL_STIBP` are in the test at `intel.c:296-299` too, - // but each already set `IBRS` or `STIBP` (`common.c:981-988`). - if !hypervisor - && (caps.ibrs || caps.ibpb || caps.stibp) - && table::bad_spectre_microcode(&id, f.microcode) - { - caps.ibrs = false; - caps.ibpb = false; - caps.stibp = false; - caps.ssbd = false; - } - let bugs = bug_bits(f, &id, &caps, arch); - - let tsx = if edx7 & CPUID_7_0_EDX_RTM_ALWAYS_ABORT != 0 { - Tsx::RtmAlwaysAbort - } else if arch & ARCH_CAP_TSX_CTRL_MSR != 0 { - Tsx::Disable - } else { - Tsx::NotSupported - }; - let rtm = f.cpuid_7_0_ebx & CPUID_7_0_EBX_RTM != 0 && tsx == Tsx::NotSupported; - - // `X86_FEATURE_MSR_IA32_FEAT_CTL` and `X86_FEATURE_VMX` after - // `init_ia32_feat_ctl` (`feat_ctl.c:112-184`), which without tboot locks an - // unlocked MSR with VMX enabled for `CONFIG_KVM_INTEL`. - let vmx = f.cpuid_1_ecx & CPUID_1_ECX_VMX != 0 - && f.feat_ctl.is_some_and(|m| { - m & FEAT_CTL_LOCKED == 0 || m & FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX != 0 - }); - - let mut spec_ctrl = 0; - let mut efer_autoibrs = false; - - // `spectre_v2_select_mitigation` (`bugs.c:1860-2045`). Where the CPU is - // unaffected it returns before any of it, leaving `spectre_v2_cmd` at - // `SPECTRE_V2_CMD_NONE`. - let mut spectre_v2 = None; - let mut bhi = None; - let mut ibrs_fw = false; - if bugs.spectre_v2 { - let mode = if bugs.ibrs_enhanced { - SpectreV2::Eibrs - } else if bugs.retbleed && caps.ibrs && id.vendor == Vendor::Intel { - SpectreV2::Ibrs - } else { - SpectreV2::Retpoline - }; - let ibrs_mode = matches!(mode, SpectreV2::Eibrs | SpectreV2::Ibrs); - if ibrs_mode { - if caps.autoibrs { - efer_autoibrs = true; - } else { - spec_ctrl |= SPEC_CTRL_IBRS; - } - } - let rrsba_disabled = - mode == SpectreV2::Retpoline && disable_kernel_rrsba(f, arch, &mut spec_ctrl); - // `bhi_select_mitigation` (`bugs.c:1831-1858`); its own call of - // `spec_ctrl_disable_kernel_rrsba` repeats the one just above. - if bugs.bhi { - bhi = Some(if rrsba_disabled { - Bhi::Retpoline - } else if f.cpuid_7_2_edx & CPUID_7_2_EDX_BHI_CTRL != 0 { - spec_ctrl |= SPEC_CTRL_BHI_DIS_S; - Bhi::BhiDisS - } else { - Bhi::SwLoop - }); - } - ibrs_fw = !(bugs.retbleed && caps.ibpb && amd_or_hygon) && caps.ibrs && !ibrs_mode; - spectre_v2 = Some(mode); - } - - // `retbleed_select_mitigation` (`bugs.c:1054-1194`). - let retbleed = if !bugs.retbleed { - Retbleed::None - } else if id.vendor == Vendor::Intel { - match spectre_v2 { - Some(SpectreV2::Ibrs) => Retbleed::Ibrs, - Some(SpectreV2::Eibrs) => Retbleed::Eibrs, - Some(SpectreV2::Retpoline) | None => Retbleed::None, - } - } else if amd_or_hygon { - Retbleed::Unret - } else { - Retbleed::None - }; - - // `spectre_v2_user_select_mitigation` (`bugs.c:1492-1591`) and - // `cpu_bugs_smt_update` (`bugs.c:2964-3058`). Without Spectre v2 the user - // command is `NONE`, which skips both IBPB and STIBP (`bugs.c:1507-1508`). - let ibpb = caps.ibpb && bugs.spectre_v2; - let stibp = if !bugs.spectre_v2 - || !caps.stibp - || !f.smt - || (spectre_v2 == Some(SpectreV2::Eibrs) && !caps.autoibrs) - { - Stibp::None - } else if ebx8 & CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON != 0 || retbleed == Retbleed::Unret { - spec_ctrl |= SPEC_CTRL_STIBP; - Stibp::StrictPreferred - } else { - Stibp::Prctl - }; - - let ssb = if caps.ssbd && bugs.ssb { Ssb::Prctl } else { Ssb::None }; - - // `md_clear_select_mitigation` (`bugs.c:603-616`). Its update pass - // (`bugs.c:555-601`) changes nothing under the default command line: each - // bug it selects for again is already selected. - let md_clear = edx7 & CPUID_7_0_EDX_MD_CLEAR != 0; - let mut clear_cpu_buf = false; - let mds = bugs.mds.then(|| { - clear_cpu_buf = true; - if md_clear { Mds::Full } else { Mds::Vmwerv } - }); - // With RTM left on, `tsx_init` found no `TSX_CTRL` (`tsx.c:188-192`), so - // the `MDS_NO && !TSX_CTRL_MSR` test (`bugs.c:367-369`) is `MDS_NO` here. - let taa = bugs.taa.then(|| { - if !rtm { - Taa::TsxDisabled - } else { - clear_cpu_buf = true; - if md_clear && arch & ARCH_CAP_MDS_NO == 0 { Taa::Verw } else { Taa::UcodeNeeded } - } - }); - // `MMIO_UNKNOWN` is set only without `MMIO_STALE_DATA` (`common.c:1495-1500`), - // and the `CLEAR_CPU_BUF` MDS or TAA would set here (`bugs.c:440-442`) they - // have set already. - let mmio = bugs.mmio_stale_data.then_some( - if arch & ARCH_CAP_FB_CLEAR != 0 - || (md_clear && edx7 & CPUID_7_0_EDX_FLUSH_L1D != 0 && arch & ARCH_CAP_MDS_NO == 0) - { - Mmio::Verw - } else { - Mmio::UcodeNeeded - }, - ); - let rfds = bugs.rfds.then(|| { - if arch & ARCH_CAP_RFDS_CLEAR != 0 { - clear_cpu_buf = true; - Rfds::Verw - } else { - Rfds::UcodeNeeded - } - }); - - let srbds = bugs.srbds.then_some( - if arch & ARCH_CAP_MDS_NO != 0 && !rtm && !bugs.mmio_stale_data { - Srbds::TsxOff - } else if hypervisor { - Srbds::Hypervisor - } else if edx7 & CPUID_7_0_EDX_SRBDS_CTRL == 0 { - Srbds::UcodeNeeded - } else { - Srbds::Full - }, - ); - - let gds = bugs.gds.then(|| { - if hypervisor { - Gds::Hypervisor - } else if arch & ARCH_CAP_GDS_CTRL == 0 { - Gds::UcodeNeeded - } else if f.mcu_opt_ctrl.expect("asserted above with GDS_CTRL") & GDS_MITG_LOCKED != 0 { - Gds::FullLocked - } else { - Gds::Full - } - }); - - // `srso_select_mitigation` (`bugs.c:2670-2807`) under its default - // `SRSO_CMD_SAFE_RET`; `retbleed` is never `IBPB` here. - let srso = bugs.srso.then_some( - if ibpb_brtype && id.family < 0x19 && !f.smt { - Srso::SmtDisabled - } else if eax21 & CPUID_8000_0021_EAX_SRSO_USER_KERNEL_NO != 0 { - if eax21 & CPUID_8000_0021_EAX_SRSO_BP_SPEC_REDUCE != 0 { - Srso::BpSpecReduce - } else if ibpb_brtype { - Srso::IbpbOnVmexit - } else { - Srso::UcodeNeeded - } - } else if ibpb_brtype { - Srso::SafeRet - } else { - Srso::SafeRetUcodeNeeded - }, - ); - let sbpb = !bugs.srso && sbpb; - - // `tsa_select_mitigation` (`bugs.c:2918-2962`) and `tsa_init`'s - // `VERW_CLEAR` (`amd.c:517-530`). - let tsa = bugs.tsa.then(|| { - let verw_clear = eax21 & CPUID_8000_0021_EAX_VERW_CLEAR != 0 - || (!hypervisor && table::tsa_microcode(&id, f.microcode)); - if verw_clear { - clear_cpu_buf = true; - Tsa::Full - } else { - clear_cpu_buf |= hypervisor; - Tsa::UcodeNeeded - } - }); - - let vmscape = if !(bugs.vmscape && caps.ibpb) { - Vmscape::None - } else if srso == Some(Srso::IbpbOnVmexit) { - Vmscape::IbpbOnVmexit - } else { - Vmscape::IbpbExitToUser - }; - - Decision { - tsx, - spectre_v2, - bhi, - ibpb, - ibrs_fw, - stibp, - retbleed, - ssb, - mds, - taa, - mmio, - rfds, - srbds, - gds, - its: bugs.its, - vmscape, - srso, - tsa, - sbpb, - clear_cpu_buf, - efer_autoibrs, - spec_ctrl, - bugs, - hypervisor, - autoibrs: caps.autoibrs, - smt: f.smt, - vmx, - } -} - -impl Decision { - /// The file's line, without its newline. - pub fn line(&self, v: Vuln) -> Result, Unmodelled> { - match v { - Vuln::L1tf if self.bugs.l1tf => Err(Unmodelled::L1tf), - _ => Ok(Line { d: self, v }), - } - } -} - -/// One vulnerabilities file's line. -pub struct Line<'a> { - d: &'a Decision, - v: Vuln, -} - -impl Line<'_> { - fn smt(&self) -> &'static str { - if self.d.smt { "vulnerable" } else { "disabled" } - } - - /// `spectre_v2_show_state` (`bugs.c:3238-3257`); unprivileged eBPF is off - /// under `CONFIG_BPF_UNPRIV_DEFAULT_OFF=y` and no module is loaded. - fn spectre_v2(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let d = self.d; - let mode = d.spectre_v2.expect("set with the bug"); - f.write_str(match mode { - SpectreV2::Retpoline => "Mitigation: Retpolines", - SpectreV2::Eibrs => "Mitigation: Enhanced / Automatic IBRS", - SpectreV2::Ibrs => "Mitigation: IBRS", - })?; - if d.ibpb { - f.write_str("; IBPB: conditional")?; - } - if d.ibrs_fw { - f.write_str("; IBRS_FW")?; - } - // `stibp_state` (`bugs.c:3174-3193`); `Prctl` is selected only with SMT, - // which is what enables `switch_to_cond_stibp`. - if !(mode == SpectreV2::Eibrs && !d.autoibrs) { - f.write_str(match d.stibp { - Stibp::None => "; STIBP: disabled", - Stibp::StrictPreferred => "; STIBP: always-on", - Stibp::Prctl => "; STIBP: conditional", - })?; - } - // `RSB_CTXSW` (`bugs.c:1741-1789`). - if matches!(mode, SpectreV2::Retpoline | SpectreV2::Ibrs) { - f.write_str("; RSB filling")?; - } - // `pbrsb_eibrs_state` (`bugs.c:3207-3218`): every mode sets - // `RSB_VMEXIT` or `RSB_VMEXIT_LITE`. - f.write_str(if d.bugs.eibrs_pbrsb { - "; PBRSB-eIBRS: SW sequence" - } else { - "; PBRSB-eIBRS: Not affected" - })?; - f.write_str(match d.bhi { - None => "; BHI: Not affected", - Some(Bhi::BhiDisS) => "; BHI: BHI_DIS_S", - Some(Bhi::SwLoop) => "; BHI: SW loop, KVM: SW loop", - Some(Bhi::Retpoline) => "; BHI: Retpoline", - }) - } -} - -impl fmt::Display for Line<'_> { - /// `cpu_show_common` (`bugs.c:3305-3377`) and the `*_show_state` it calls. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let d = self.d; - let b = &d.bugs; - let affected = match self.v { - Vuln::Meltdown => b.meltdown, - Vuln::SpectreV1 => b.spectre_v1, - Vuln::SpectreV2 => b.spectre_v2, - Vuln::SpecStoreBypass => b.ssb, - Vuln::L1tf => b.l1tf, - Vuln::Mds => b.mds, - Vuln::TsxAsyncAbort => b.taa, - Vuln::ItlbMultihit => b.itlb_multihit, - Vuln::Srbds => b.srbds, - Vuln::MmioStaleData => b.mmio_stale_data || b.mmio_unknown, - Vuln::Retbleed => b.retbleed, - Vuln::GatherDataSampling => b.gds, - Vuln::RegFileDataSampling => b.rfds, - Vuln::IndirectTargetSelection => b.its, - Vuln::Vmscape => b.vmscape, - Vuln::SpecRstackOverflow => b.srso, - Vuln::Tsa => b.tsa, - }; - if !affected { - return f.write_str("Not affected"); - } - match self.v { - // `pti_check_boottime_disable` (`arch/x86/mm/pti.c:79-101`) sets PTI - // wherever Meltdown is; a Xen PV guest is no machine ToyOS boots. - Vuln::Meltdown => f.write_str("Mitigation: PTI"), - Vuln::SpectreV1 => { - f.write_str("Mitigation: usercopy/swapgs barriers and __user pointer sanitization") - } - Vuln::SpectreV2 => self.spectre_v2(f), - Vuln::SpecStoreBypass => f.write_str(match d.ssb { - Ssb::None => "Vulnerable", - Ssb::Prctl => "Mitigation: Speculative Store Bypass disabled via prctl", - }), - Vuln::Mds => { - f.write_str(match d.mds.expect("set with the bug") { - Mds::Full => "Mitigation: Clear CPU buffers", - Mds::Vmwerv => "Vulnerable: Clear CPU buffers attempted, no microcode", - })?; - if d.hypervisor { - f.write_str("; SMT Host state unknown") - } else if b.msbds_only { - f.write_str(if d.smt { "; SMT mitigated" } else { "; SMT disabled" }) - } else { - write!(f, "; SMT {}", self.smt()) - } - } - Vuln::TsxAsyncAbort => { - let taa = d.taa.expect("set with the bug"); - f.write_str(match taa { - Taa::UcodeNeeded => "Vulnerable: Clear CPU buffers attempted, no microcode", - Taa::Verw => "Mitigation: Clear CPU buffers", - Taa::TsxDisabled => return f.write_str("Mitigation: TSX disabled"), - })?; - if d.hypervisor { - f.write_str("; SMT Host state unknown") - } else { - write!(f, "; SMT {}", self.smt()) - } - } - // `itlb_multihit_show_state` under `CONFIG_KVM_INTEL` - // (`bugs.c:3091-3102`). No VM runs: KVM sets `CR4.VMXE` only while - // one exists (`arch/x86/kvm/vmx/vmx.c:2802`, from `KVM_CREATE_VM`, - // `virt/kvm/kvm_main.c:1272,5658-5660`). - Vuln::ItlbMultihit => f.write_str(if d.vmx { - "KVM: Mitigation: VMX disabled" - } else { - "KVM: Mitigation: VMX unsupported" - }), - Vuln::Srbds => f.write_str(match d.srbds.expect("set with the bug") { - Srbds::UcodeNeeded => "Vulnerable: No microcode", - Srbds::Full => "Mitigation: Microcode", - Srbds::TsxOff => "Mitigation: TSX disabled", - Srbds::Hypervisor => "Unknown: Dependent on hypervisor status", - }), - Vuln::MmioStaleData => { - let Some(mmio) = d.mmio else { - return f.write_str("Unknown: No mitigations"); - }; - f.write_str(match mmio { - Mmio::UcodeNeeded => "Vulnerable: Clear CPU buffers attempted, no microcode", - Mmio::Verw => "Mitigation: Clear CPU buffers", - })?; - if d.hypervisor { - f.write_str("; SMT Host state unknown") - } else { - write!(f, "; SMT {}", self.smt()) - } - } - Vuln::Retbleed => match d.retbleed { - Retbleed::None => f.write_str("Vulnerable"), - Retbleed::Ibrs => f.write_str("Mitigation: IBRS"), - Retbleed::Eibrs => f.write_str("Mitigation: Enhanced IBRS"), - // Selected on AMD and Hygon alone, so never "on non-AMD based - // uarch". - Retbleed::Unret => { - let smt = if !d.smt { - "disabled" - } else if d.stibp == Stibp::StrictPreferred { - "enabled with STIBP protection" - } else { - "vulnerable" - }; - write!(f, "Mitigation: untrained return thunk; SMT {smt}") - } - }, - Vuln::GatherDataSampling => f.write_str(match d.gds.expect("set with the bug") { - Gds::UcodeNeeded => "Vulnerable: No microcode", - Gds::Full => "Mitigation: Microcode", - Gds::FullLocked => "Mitigation: Microcode (locked)", - Gds::Hypervisor => "Unknown: Dependent on hypervisor status", - }), - Vuln::RegFileDataSampling => f.write_str(match d.rfds.expect("set with the bug") { - Rfds::Verw => "Mitigation: Clear Register File", - Rfds::UcodeNeeded => "Vulnerable: No microcode", - }), - Vuln::IndirectTargetSelection => { - f.write_str("Mitigation: Aligned branch/return thunks") - } - Vuln::Vmscape => f.write_str(match d.vmscape { - Vmscape::None => "Vulnerable", - Vmscape::IbpbExitToUser => "Mitigation: IBPB before exit to userspace", - Vmscape::IbpbOnVmexit => "Mitigation: IBPB on VMEXIT", - }), - Vuln::SpecRstackOverflow => f.write_str(match d.srso.expect("set with the bug") { - Srso::SmtDisabled => "Mitigation: SMT disabled", - Srso::UcodeNeeded => "Vulnerable: No microcode", - Srso::SafeRetUcodeNeeded => "Vulnerable: Safe RET, no microcode", - Srso::SafeRet => "Mitigation: Safe RET", - Srso::IbpbOnVmexit => "Mitigation: IBPB on VMEXIT only", - Srso::BpSpecReduce => "Mitigation: Reduced Speculation", - }), - Vuln::Tsa => f.write_str(match d.tsa.expect("set with the bug") { - Tsa::UcodeNeeded => "Vulnerable: Clear CPU buffers attempted, no microcode", - Tsa::Full => "Mitigation: Clear CPU buffers", - }), - Vuln::L1tf => unreachable!("l1tf is unmodelled wherever affected"), - } - } -} diff --git a/kernel/cpuvuln/src/table.rs b/kernel/cpuvuln/src/table.rs deleted file mode 100644 index 8b748b5fe73..00000000000 --- a/kernel/cpuvuln/src/table.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! Linux's five CPU tables, row for row: `cpu_vuln_whitelist` and -//! `cpu_vuln_blacklist` (`common.c:1182-1344`), `spectre_bad_microcodes` -//! (`intel.c:141-163`), `amd_check_tsa_microcode`'s (`amd.c:472-515`) and -//! `test_intel`'s (`arch/x86/events/msr.c:40-122`). A -//! lookup in the first two is `x86_match_cpu` (`match.c:36-57`): the -//! first row whose vendor, family, model and stepping all match, `0` matching -//! any. -//! -//! The whitelist's `NSC` and `VORTEX` rows are left out: x86-64 Linux builds -//! neither vendor (`CONFIG_CPU_SUP_CYRIX_32`, `CONFIG_CPU_SUP_VORTEX_32`), so -//! no CPU it identifies matches them. - -use crate::{Ident, Vendor}; - -/// A whitelist row's flags (`common.c:1157-1168`). -#[derive(Clone, Copy)] -pub(crate) struct Wl(u16); -/// A blacklist row's flags (`common.c:1260-1282`). -#[derive(Clone, Copy)] -pub(crate) struct Bl(u16); - -impl core::ops::BitOr for Bl { - type Output = Self; - fn bitor(self, o: Self) -> Self { - Self(self.0 | o.0) - } -} - -pub(crate) const NO_SPECULATION: Wl = Wl(1 << 0); -pub(crate) const NO_MELTDOWN: Wl = Wl(1 << 1); -pub(crate) const NO_SSB: Wl = Wl(1 << 2); -pub(crate) const NO_L1TF: Wl = Wl(1 << 3); -pub(crate) const NO_MDS: Wl = Wl(1 << 4); -pub(crate) const MSBDS_ONLY: Wl = Wl(1 << 5); -pub(crate) const NO_SWAPGS: Wl = Wl(1 << 6); -pub(crate) const NO_ITLB_MULTIHIT: Wl = Wl(1 << 7); -pub(crate) const NO_SPECTRE_V2: Wl = Wl(1 << 8); -pub(crate) const NO_MMIO: Wl = Wl(1 << 9); -pub(crate) const NO_EIBRS_PBRSB: Wl = Wl(1 << 10); -pub(crate) const NO_BHI: Wl = Wl(1 << 11); - -pub(crate) const SRBDS: Bl = Bl(1 << 0); -pub(crate) const MMIO: Bl = Bl(1 << 1); -pub(crate) const MMIO_SBDS: Bl = Bl(1 << 2); -pub(crate) const RETBLEED: Bl = Bl(1 << 3); -pub(crate) const SMT_RSB: Bl = Bl(1 << 4); -pub(crate) const SRSO: Bl = Bl(1 << 5); -pub(crate) const GDS: Bl = Bl(1 << 6); -pub(crate) const RFDS: Bl = Bl(1 << 7); -pub(crate) const ITS: Bl = Bl(1 << 8); -pub(crate) const ITS_NATIVE_ONLY: Bl = Bl(1 << 9); -pub(crate) const TSA: Bl = Bl(1 << 10); -pub(crate) const VMSCAPE: Bl = Bl(1 << 11); - -macro_rules! wl { ($($f:ident)|+) => { Wl(0 $(| $f.0)+) }; } -macro_rules! bl { ($($f:ident)|+) => { Bl(0 $(| $f.0)+) }; } - -/// `X86_FAMILY_ANY`, `X86_MODEL_ANY` and `X86_STEPPING_ANY` -/// (`include/linux/mod_devicetable.h:700-702`). -const ANY: u32 = 0; - -/// `X86_STEPPINGS(mins, maxs)`, `GENMASK(maxs, mins)` (`asm/cpu_device_id.h:59`). -const fn steppings(min: u32, max: u32) -> u32 { - ((1 << (max + 1)) - 1) & !((1 << min) - 1) -} - -pub(crate) struct Row { - vendor: Option, - family: u32, - model: u32, - steppings: u32, - flags: F, -} - -const fn row(vendor: Option, family: u32, model: u32, flags: F) -> Row { - Row { vendor, family, model, steppings: ANY, flags } -} -const fn intel(model: u32, flags: F) -> Row { - row(Some(Vendor::Intel), 6, model, flags) -} -const fn intel_steppings(model: u32, steppings: u32, flags: Bl) -> Row { - Row { vendor: Some(Vendor::Intel), family: 6, model, steppings, flags } -} -const fn amd(family: u32, flags: F) -> Row { - row(Some(Vendor::Amd), family, ANY, flags) -} -const fn hygon(family: u32, flags: F) -> Row { - row(Some(Vendor::Hygon), family, ANY, flags) -} - -fn lookup(rows: &[Row], id: &Ident) -> Option { - rows.iter() - .find(|r| { - r.vendor.is_none_or(|v| v == id.vendor) - && (r.family == ANY || r.family == id.family) - && (r.model == ANY || r.model == id.model) - && (r.steppings == ANY || r.steppings & (1 << id.stepping) != 0) - }) - .map(|r| r.flags) -} - -/// `cpu_matches(cpu_vuln_whitelist, which)` (`common.c:1346-1351`). -pub(crate) fn whitelisted(id: &Ident, which: Wl) -> bool { - lookup(WHITELIST, id).is_some_and(|f| f.0 & which.0 != 0) -} - -/// `cpu_matches(cpu_vuln_blacklist, which)` (`common.c:1346-1351`). -pub(crate) fn blacklisted(id: &Ident, which: Bl) -> bool { - lookup(BLACKLIST, id).is_some_and(|f| f.0 & which.0 != 0) -} - -// `arch/x86/include/asm/intel-family.h`, family 6, the models the tables name. -const CORE_YONAH: u32 = 0x0E; -const NEHALEM: u32 = 0x1E; -const NEHALEM_G: u32 = 0x1F; -const NEHALEM_EP: u32 = 0x1A; -const NEHALEM_EX: u32 = 0x2E; -const WESTMERE: u32 = 0x25; -const WESTMERE_EP: u32 = 0x2C; -const WESTMERE_EX: u32 = 0x2F; -const SANDYBRIDGE: u32 = 0x2A; -const SANDYBRIDGE_X: u32 = 0x2D; -const IVYBRIDGE: u32 = 0x3A; -const IVYBRIDGE_X: u32 = 0x3E; -const HASWELL: u32 = 0x3C; -const HASWELL_X: u32 = 0x3F; -const HASWELL_L: u32 = 0x45; -const HASWELL_G: u32 = 0x46; -const BROADWELL: u32 = 0x3D; -const BROADWELL_G: u32 = 0x47; -const BROADWELL_X: u32 = 0x4F; -const BROADWELL_D: u32 = 0x56; -const SKYLAKE_L: u32 = 0x4E; -const SKYLAKE: u32 = 0x5E; -const SKYLAKE_X: u32 = 0x55; -const KABYLAKE_L: u32 = 0x8E; -const KABYLAKE: u32 = 0x9E; -const COMETLAKE: u32 = 0xA5; -const COMETLAKE_L: u32 = 0xA6; -const CANNONLAKE_L: u32 = 0x66; -const ICELAKE_X: u32 = 0x6A; -const ICELAKE_D: u32 = 0x6C; -const ICELAKE_L: u32 = 0x7E; -const ICELAKE: u32 = 0x7D; -const ROCKETLAKE: u32 = 0xA7; -const TIGERLAKE_L: u32 = 0x8C; -const TIGERLAKE: u32 = 0x8D; -const SAPPHIRERAPIDS_X: u32 = 0x8F; -const EMERALDRAPIDS_X: u32 = 0xCF; -const GRANITERAPIDS_X: u32 = 0xAD; -const GRANITERAPIDS_D: u32 = 0xAE; -const LAKEFIELD: u32 = 0x8A; -const ALDERLAKE: u32 = 0x97; -const ALDERLAKE_L: u32 = 0x9A; -const RAPTORLAKE: u32 = 0xB7; -const RAPTORLAKE_P: u32 = 0xBA; -const RAPTORLAKE_S: u32 = 0xBF; -const METEORLAKE_L: u32 = 0xAA; -const METEORLAKE: u32 = 0xAC; -const ARROWLAKE_H: u32 = 0xC5; -const ARROWLAKE: u32 = 0xC6; -const ARROWLAKE_U: u32 = 0xB5; -const LUNARLAKE_M: u32 = 0xBD; -const ATOM_BONNELL: u32 = 0x1C; -const ATOM_BONNELL_MID: u32 = 0x26; -const ATOM_SALTWELL: u32 = 0x36; -const ATOM_SALTWELL_MID: u32 = 0x27; -const ATOM_SALTWELL_TABLET: u32 = 0x35; -const ATOM_SILVERMONT: u32 = 0x37; -const ATOM_SILVERMONT_D: u32 = 0x4D; -const ATOM_SILVERMONT_MID: u32 = 0x4A; -const ATOM_AIRMONT: u32 = 0x4C; -const ATOM_AIRMONT_MID: u32 = 0x5A; -const ATOM_AIRMONT_NP: u32 = 0x75; -const ATOM_GOLDMONT: u32 = 0x5C; -const ATOM_GOLDMONT_D: u32 = 0x5F; -const ATOM_GOLDMONT_PLUS: u32 = 0x7A; -const ATOM_TREMONT_D: u32 = 0x86; -const ATOM_TREMONT: u32 = 0x96; -const ATOM_TREMONT_L: u32 = 0x9C; -const ATOM_GRACEMONT: u32 = 0xBE; -const ATOM_CRESTMONT_X: u32 = 0xAF; -const XEON_PHI_KNL: u32 = 0x57; -const XEON_PHI_KNM: u32 = 0x85; - -/// `cpu_vuln_whitelist` (`common.c:1182-1244`). -const WHITELIST: &[Row] = &[ - row(None, 4, ANY, wl!(NO_SPECULATION)), - row(Some(Vendor::Centaur), 5, ANY, wl!(NO_SPECULATION)), - row(Some(Vendor::Intel), 5, ANY, wl!(NO_SPECULATION)), - - intel(TIGERLAKE, wl!(NO_MMIO)), - intel(TIGERLAKE_L, wl!(NO_MMIO)), - intel(ALDERLAKE, wl!(NO_MMIO)), - intel(ALDERLAKE_L, wl!(NO_MMIO)), - - intel(ATOM_SALTWELL, wl!(NO_SPECULATION | NO_ITLB_MULTIHIT)), - intel(ATOM_SALTWELL_TABLET, wl!(NO_SPECULATION | NO_ITLB_MULTIHIT)), - intel(ATOM_SALTWELL_MID, wl!(NO_SPECULATION | NO_ITLB_MULTIHIT)), - intel(ATOM_BONNELL, wl!(NO_SPECULATION | NO_ITLB_MULTIHIT)), - intel(ATOM_BONNELL_MID, wl!(NO_SPECULATION | NO_ITLB_MULTIHIT)), - - intel(ATOM_SILVERMONT, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - intel(ATOM_SILVERMONT_D, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - intel(ATOM_SILVERMONT_MID, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - intel(ATOM_AIRMONT, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - intel(XEON_PHI_KNL, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - intel(XEON_PHI_KNM, wl!(NO_SSB | NO_L1TF | MSBDS_ONLY | NO_SWAPGS | NO_ITLB_MULTIHIT)), - - intel(CORE_YONAH, wl!(NO_SSB)), - - intel(ATOM_AIRMONT_MID, wl!(NO_SSB | NO_L1TF | NO_SWAPGS | NO_ITLB_MULTIHIT | MSBDS_ONLY)), - intel(ATOM_AIRMONT_NP, wl!(NO_SSB | NO_L1TF | NO_SWAPGS | NO_ITLB_MULTIHIT)), - - intel(ATOM_GOLDMONT, wl!(NO_MDS | NO_L1TF | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO)), - intel(ATOM_GOLDMONT_D, wl!(NO_MDS | NO_L1TF | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO)), - intel(ATOM_GOLDMONT_PLUS, wl!(NO_MDS | NO_L1TF | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_EIBRS_PBRSB)), - - intel(ATOM_TREMONT, wl!(NO_EIBRS_PBRSB)), - intel(ATOM_TREMONT_L, wl!(NO_EIBRS_PBRSB)), - intel(ATOM_TREMONT_D, wl!(NO_ITLB_MULTIHIT | NO_EIBRS_PBRSB)), - - amd(0x0f, wl!(NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_BHI)), - amd(0x10, wl!(NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_BHI)), - amd(0x11, wl!(NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_BHI)), - amd(0x12, wl!(NO_MELTDOWN | NO_SSB | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_BHI)), - - amd(ANY, wl!(NO_MELTDOWN | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_EIBRS_PBRSB | NO_BHI)), - hygon(ANY, wl!(NO_MELTDOWN | NO_L1TF | NO_MDS | NO_SWAPGS | NO_ITLB_MULTIHIT | NO_MMIO | NO_EIBRS_PBRSB | NO_BHI)), - - row(Some(Vendor::Centaur), 7, ANY, wl!(NO_SPECTRE_V2 | NO_SWAPGS | NO_MMIO | NO_BHI)), - row(Some(Vendor::Zhaoxin), 7, ANY, wl!(NO_SPECTRE_V2 | NO_SWAPGS | NO_MMIO | NO_BHI)), -]; - -/// `cpu_vuln_blacklist` (`common.c:1284-1344`). -const BLACKLIST: &[Row] = &[ - intel_steppings(SANDYBRIDGE_X, ANY, bl!(VMSCAPE)), - intel_steppings(SANDYBRIDGE, ANY, bl!(VMSCAPE)), - intel_steppings(IVYBRIDGE_X, ANY, bl!(VMSCAPE)), - intel_steppings(IVYBRIDGE, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(HASWELL, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(HASWELL_L, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(HASWELL_G, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(HASWELL_X, ANY, bl!(MMIO | VMSCAPE)), - intel_steppings(BROADWELL_D, ANY, bl!(MMIO | VMSCAPE)), - intel_steppings(BROADWELL_X, ANY, bl!(MMIO | VMSCAPE)), - intel_steppings(BROADWELL_G, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(BROADWELL, ANY, bl!(SRBDS | VMSCAPE)), - intel_steppings(SKYLAKE_X, steppings(0x0, 0x5), bl!(MMIO | RETBLEED | GDS | VMSCAPE)), - intel_steppings(SKYLAKE_X, ANY, bl!(MMIO | RETBLEED | GDS | ITS | VMSCAPE)), - intel_steppings(SKYLAKE_L, ANY, bl!(MMIO | RETBLEED | GDS | SRBDS | VMSCAPE)), - intel_steppings(SKYLAKE, ANY, bl!(MMIO | RETBLEED | GDS | SRBDS | VMSCAPE)), - intel_steppings(KABYLAKE_L, steppings(0x0, 0xb), bl!(MMIO | RETBLEED | GDS | SRBDS | VMSCAPE)), - intel_steppings(KABYLAKE_L, ANY, bl!(MMIO | RETBLEED | GDS | SRBDS | ITS | VMSCAPE)), - intel_steppings(KABYLAKE, steppings(0x0, 0xc), bl!(MMIO | RETBLEED | GDS | SRBDS | VMSCAPE)), - intel_steppings(KABYLAKE, ANY, bl!(MMIO | RETBLEED | GDS | SRBDS | ITS | VMSCAPE)), - intel_steppings(CANNONLAKE_L, ANY, bl!(RETBLEED | VMSCAPE)), - intel_steppings(ICELAKE_L, ANY, bl!(MMIO | MMIO_SBDS | RETBLEED | GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(ICELAKE_D, ANY, bl!(MMIO | GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(ICELAKE_X, ANY, bl!(MMIO | GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(COMETLAKE, ANY, bl!(MMIO | MMIO_SBDS | RETBLEED | GDS | ITS | VMSCAPE)), - intel_steppings(COMETLAKE_L, steppings(0x0, 0x0), bl!(MMIO | RETBLEED | ITS | VMSCAPE)), - intel_steppings(COMETLAKE_L, ANY, bl!(MMIO | MMIO_SBDS | RETBLEED | GDS | ITS | VMSCAPE)), - intel_steppings(TIGERLAKE_L, ANY, bl!(GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(TIGERLAKE, ANY, bl!(GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(LAKEFIELD, ANY, bl!(MMIO | MMIO_SBDS | RETBLEED)), - intel_steppings(ROCKETLAKE, ANY, bl!(MMIO | RETBLEED | GDS | ITS | ITS_NATIVE_ONLY)), - intel_steppings(ALDERLAKE, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(ALDERLAKE_L, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(RAPTORLAKE, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(RAPTORLAKE_P, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(RAPTORLAKE_S, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(METEORLAKE_L, ANY, bl!(VMSCAPE)), - intel_steppings(ARROWLAKE_H, ANY, bl!(VMSCAPE)), - intel_steppings(ARROWLAKE, ANY, bl!(VMSCAPE)), - intel_steppings(ARROWLAKE_U, ANY, bl!(VMSCAPE)), - intel_steppings(LUNARLAKE_M, ANY, bl!(VMSCAPE)), - intel_steppings(SAPPHIRERAPIDS_X, ANY, bl!(VMSCAPE)), - intel_steppings(GRANITERAPIDS_X, ANY, bl!(VMSCAPE)), - intel_steppings(EMERALDRAPIDS_X, ANY, bl!(VMSCAPE)), - intel_steppings(ATOM_GRACEMONT, ANY, bl!(RFDS | VMSCAPE)), - intel_steppings(ATOM_TREMONT, ANY, bl!(MMIO | MMIO_SBDS | RFDS)), - intel_steppings(ATOM_TREMONT_D, ANY, bl!(MMIO | RFDS)), - intel_steppings(ATOM_TREMONT_L, ANY, bl!(MMIO | MMIO_SBDS | RFDS)), - intel_steppings(ATOM_GOLDMONT, ANY, bl!(RFDS)), - intel_steppings(ATOM_GOLDMONT_D, ANY, bl!(RFDS)), - intel_steppings(ATOM_GOLDMONT_PLUS, ANY, bl!(RFDS)), - intel_steppings(ATOM_CRESTMONT_X, ANY, bl!(VMSCAPE)), - - amd(0x15, bl!(RETBLEED)), - amd(0x16, bl!(RETBLEED)), - amd(0x17, bl!(RETBLEED | SMT_RSB | SRSO | VMSCAPE)), - hygon(0x18, bl!(RETBLEED | SMT_RSB | SRSO | VMSCAPE)), - amd(0x19, bl!(SRSO | TSA | VMSCAPE)), -]; - -/// `bad_spectre_microcode` (`intel.c:165-182`) over `spectre_bad_microcodes` -/// (`intel.c:141-163`), whose caller is Intel's (`intel.c:296-299`): a CPU -/// whose microcode is at or below its row's broke the speculation controls. -pub(crate) fn bad_spectre_microcode(id: &Ident, microcode: u32) -> bool { - const ROWS: &[(u32, u32, u32)] = &[ - (KABYLAKE, 0x0B, 0x80), - (KABYLAKE, 0x0A, 0x80), - (KABYLAKE, 0x09, 0x80), - (KABYLAKE_L, 0x0A, 0x80), - (KABYLAKE_L, 0x09, 0x80), - (SKYLAKE_X, 0x03, 0x0100013e), - (SKYLAKE_X, 0x04, 0x0200003c), - (BROADWELL, 0x04, 0x28), - (BROADWELL_G, 0x01, 0x1b), - (BROADWELL_D, 0x02, 0x14), - (BROADWELL_D, 0x03, 0x07000011), - (BROADWELL_X, 0x01, 0x0b000025), - (HASWELL_L, 0x01, 0x21), - (HASWELL_G, 0x01, 0x18), - (HASWELL, 0x03, 0x23), - (HASWELL_X, 0x02, 0x3b), - (HASWELL_X, 0x04, 0x10), - (IVYBRIDGE_X, 0x04, 0x42a), - (SANDYBRIDGE_X, 0x06, 0x61b), - (SANDYBRIDGE_X, 0x07, 0x712), - ]; - id.vendor == Vendor::Intel - && id.family == 6 - && ROWS - .iter() - .find(|&&(model, stepping, _)| model == id.model && stepping == id.stepping) - .is_some_and(|&(_, _, bad)| microcode <= bad) -} - -/// `amd_check_tsa_microcode` (`amd.c:472-515`): a CPU whose microcode is at or -/// above its row's. Every row keys a family 0x19 model `bsp_init_amd` names -/// Zen3 or Zen4 (`amd.c:619-632`), the only family TSA's row names -/// (`common.c:1342`). -pub(crate) fn tsa_microcode(id: &Ident, microcode: u32) -> bool { - const ROWS: &[(u32, u32)] = &[ - (0xa0011, 0x0a0011d7), - (0xa0012, 0x0a00123b), - (0xa0082, 0x0a00820d), - (0xa1011, 0x0a10114c), - (0xa1012, 0x0a10124c), - (0xa1081, 0x0a108109), - (0xa2010, 0x0a20102e), - (0xa2012, 0x0a201211), - (0xa4041, 0x0a404108), - (0xa5000, 0x0a500012), - (0xa6012, 0x0a60120a), - (0xa7041, 0x0a704108), - (0xa7052, 0x0a705208), - (0xa7080, 0x0a708008), - (0xa70c0, 0x0a70c008), - (0xaa002, 0x0aa00216), - ]; - // `union zen_patch_rev` (`asm/cpu.h:80-90`) above its `rev` byte. - let key = (id.family - 0xf) << 16 | (id.model >> 4) << 12 | (id.model & 0xf) << 4 | id.stepping; - ROWS.iter().find(|&&(k, _)| k == key).is_some_and(|&(_, min)| microcode >= min) -} - -/// `test_intel` for `PERF_MSR_SMI` (`arch/x86/events/msr.c:40-122`): an Intel -/// family 6 CPU of one of these models counts its SMIs in `MSR_SMI_COUNT`. -pub(crate) fn counts_smis(id: &Ident) -> bool { - const MODELS: &[u32] = &[ - NEHALEM, NEHALEM_G, NEHALEM_EP, NEHALEM_EX, - WESTMERE, WESTMERE_EP, WESTMERE_EX, - SANDYBRIDGE, SANDYBRIDGE_X, - IVYBRIDGE, IVYBRIDGE_X, - HASWELL, HASWELL_X, HASWELL_L, HASWELL_G, - BROADWELL, BROADWELL_D, BROADWELL_G, BROADWELL_X, - SAPPHIRERAPIDS_X, EMERALDRAPIDS_X, GRANITERAPIDS_X, GRANITERAPIDS_D, - ATOM_SILVERMONT, ATOM_SILVERMONT_D, ATOM_AIRMONT, ATOM_AIRMONT_NP, - ATOM_GOLDMONT, ATOM_GOLDMONT_D, ATOM_GOLDMONT_PLUS, - ATOM_TREMONT_D, ATOM_TREMONT, ATOM_TREMONT_L, - XEON_PHI_KNL, XEON_PHI_KNM, - SKYLAKE_L, SKYLAKE, SKYLAKE_X, KABYLAKE_L, KABYLAKE, COMETLAKE_L, COMETLAKE, - ICELAKE_L, ICELAKE, ICELAKE_X, ICELAKE_D, TIGERLAKE_L, TIGERLAKE, ROCKETLAKE, - ALDERLAKE, ALDERLAKE_L, ATOM_GRACEMONT, RAPTORLAKE, RAPTORLAKE_P, RAPTORLAKE_S, - METEORLAKE, METEORLAKE_L, - ]; - id.vendor == Vendor::Intel && id.family == 6 && MODELS.contains(&id.model) -} diff --git a/kernel/cpuvuln/src/tests.rs b/kernel/cpuvuln/src/tests.rs deleted file mode 100644 index 60d417c00ae..00000000000 --- a/kernel/cpuvuln/src/tests.rs +++ /dev/null @@ -1,1823 +0,0 @@ -//! Facts in, Linux's lines and selection out, the lines judged against -//! `grep .` over the vulnerabilities directory. Every expected value below the -//! two captures is read from the tag's source at the lines each test cites. - -extern crate std; - -use std::string::{String, ToString}; -use std::vec::Vec; - -use super::*; - -const PREFIX: &str = "/sys/devices/system/cpu/vulnerabilities/"; - -/// The T14's i5-1135G7, held to the pinned Linux's reading of it by -/// [`the_t14s_facts_are_linuxs_reading_of_it`]. -const T14: Facts = Facts { - vendor: Vendor::from_id(b"GenuineIntel"), - signature: 0x0008_06c1, - microcode: 0xbe, - cpuid_1_ecx: 0x7ffa_fbbf, - cpuid_7_0_ebx: 0xf3bf_a7eb, - cpuid_7_0_edx: 0xfc10_0710, - cpuid_7_2_edx: 0x0000_0001, - cpuid_8000_0008_ebx: 0, - // CPUID.0x80000000:EAX is 0x80000008. - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0x0a00_5c6b, - mcu_opt_ctrl: Some(0), - // `fixtures/t14/` holds no 0x3A. `cpuinfo.txt` lists `vmx` and its - // `vmx flags`, so Linux kept VMX (`feat_ctl.c:171-184`): these are the two - // bits that decide that, and no other bit is read. - feat_ctl: Some(FEAT_CTL_LOCKED | FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX), - ls_cfg_readable: None, - sbpb_write_accepted: None, - // `siblings 8`, `cpu cores 4`. - smt: true, -}; - -/// The TCG model: `qemu64`, `AuthenticAMD` family 0xF model 0x6B stepping 1; -/// the bits `decide` reads in leaves 7 and 0x80000008 are clear in that model, -/// and it has no leaf 0x80000021. -const TCG: Facts = Facts { - vendor: Vendor::from_id(b"AuthenticAMD"), - signature: 0x0006_0fb1, - microcode: 0, - cpuid_1_ecx: 0, - cpuid_7_0_ebx: 0, - cpuid_7_0_edx: 0, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: false, -}; - -/// An EPYC 7713 (Milan, family 0x19 model 1 stepping 1, the family and model -/// of the nightly's EPYC 7763) as a KVM guest: InstLatx64's bare-metal dump -/// `AuthenticAMD/AuthenticAMD0A00F11_K19_Milan_CPUID1.txt` (commit 2dc186e9) -/// with CPUID.1:ECX[31] set, `-smp cores=N`, and no microcode revision, which -/// no line reads under a hypervisor. -const MILAN_GUEST: Facts = Facts { - vendor: Vendor::from_id(b"AuthenticAMD"), - signature: 0x00a0_0f11, - microcode: 0, - cpuid_1_ecx: 0x7eda_320b | CPUID_1_ECX_HYPERVISOR, - cpuid_7_0_ebx: 0x219c_97a9, - cpuid_7_0_edx: 0x0000_0010, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0x91be_f75f, - cpuid_8000_0021_eax: 0x0000_204d, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: false, -}; - -/// An EPYC 9655 (Turin, family 0x1A model 2 stepping 1, the family of the -/// nightly's EPYC 9V45) as a KVM guest: InstLatx64's bare-metal dump -/// `AuthenticAMD/AuthenticAMD0B00F21_K20_Turin_01_CPUID.txt` (commit b499237d), -/// shaped as [`MILAN_GUEST`] is. -const TURIN_GUEST: Facts = Facts { - vendor: Vendor::from_id(b"AuthenticAMD"), - signature: 0x00b0_0f21, - microcode: 0, - cpuid_1_ecx: 0x7efa_320b | CPUID_1_ECX_HYPERVISOR, - cpuid_7_0_ebx: 0xf1bf_97ab, - cpuid_7_0_edx: 0x1000_0110, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0x79be_f25f, - cpuid_8000_0021_eax: 0xd93f_ffcf, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: false, -}; - -/// [`Decision`]'s selection, every public field: the destructuring in -/// [`state`] names each, so a field added there fails to build here. -#[derive(Debug, PartialEq)] -struct State { - tsx: Tsx, - spectre_v2: Option, - bhi: Option, - ibpb: bool, - ibrs_fw: bool, - stibp: Stibp, - retbleed: Retbleed, - ssb: Ssb, - mds: Option, - taa: Option, - mmio: Option, - rfds: Option, - srbds: Option, - gds: Option, - its: bool, - vmscape: Vmscape, - srso: Option, - tsa: Option, - sbpb: bool, - clear_cpu_buf: bool, - efer_autoibrs: bool, - spec_ctrl: u64, -} - -fn state(d: &Decision) -> State { - let Decision { - tsx, - spectre_v2, - bhi, - ibpb, - ibrs_fw, - stibp, - retbleed, - ssb, - mds, - taa, - mmio, - rfds, - srbds, - gds, - its, - vmscape, - srso, - tsa, - sbpb, - clear_cpu_buf, - efer_autoibrs, - spec_ctrl, - bugs: _, - hypervisor: _, - autoibrs: _, - smt: _, - vmx: _, - } = *d; - State { - tsx, - spectre_v2, - bhi, - ibpb, - ibrs_fw, - stibp, - retbleed, - ssb, - mds, - taa, - mmio, - rfds, - srbds, - gds, - its, - vmscape, - srso, - tsa, - sbpb, - clear_cpu_buf, - efer_autoibrs, - spec_ctrl, - } -} - -/// Nothing selected: a CPU no vulnerability reaches. -const NONE: State = State { - tsx: Tsx::NotSupported, - spectre_v2: None, - bhi: None, - ibpb: false, - ibrs_fw: false, - stibp: Stibp::None, - retbleed: Retbleed::None, - ssb: Ssb::None, - mds: None, - taa: None, - mmio: None, - rfds: None, - srbds: None, - gds: None, - its: false, - vmscape: Vmscape::None, - srso: None, - tsa: None, - sbpb: false, - clear_cpu_buf: false, - efer_autoibrs: false, - spec_ctrl: 0, -}; - -/// `facts`' line for every file `capture` names, and `capture` names each -/// file once. -fn assert_capture(facts: &Facts, capture: &str) -> Decision { - let d = decide(facts); - let mut named = Vec::new(); - for l in capture.lines() { - let (file, want) = l - .strip_prefix(PREFIX) - .and_then(|rest| rest.split_once(':')) - .unwrap_or_else(|| panic!("{l:?} is not `grep .` over {PREFIX}")); - let v = *Vuln::ALL - .iter() - .find(|v| v.name() == file) - .unwrap_or_else(|| panic!("{file}: a file Linux does not have")); - let got = d.line(v).map(|line| line.to_string()); - assert_eq!(got.as_deref(), Ok(want), "{file}"); - named.push(file); - } - named.sort_unstable(); - let mut all: Vec<_> = Vuln::ALL.iter().map(|v| v.name()).collect(); - all.sort_unstable(); - assert_eq!(named, all, "the capture names every file once"); - d -} - -fn line(facts: &Facts, v: Vuln) -> String { - decide(facts).line(v).expect("modelled").to_string() -} - -#[test] -fn the_t14s_facts_give_its_lines_and_its_spec_ctrl() { - let d = assert_capture(&T14, include_str!("../fixtures/t14.txt")); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Eibrs), - bhi: Some(Bhi::SwLoop), - ibpb: true, - ssb: Ssb::Prctl, - gds: Some(Gds::Full), - its: true, - spec_ctrl: 0x1, - ..NONE - } - ); -} - -/// [`T14`] against `fixtures/t14/`, each field read as [`Facts`] says Linux -/// reads it, and what `decide` makes of it against the VMX Linux kept and the -/// `IA32_SPEC_CTRL` it left. -#[test] -fn the_t14s_facts_are_linuxs_reading_of_it() { - let hex = |word: &str| u64::from_str_radix(word.trim_start_matches("0x"), 16).expect(word); - let leaves: Vec> = include_str!("../fixtures/t14/cpuid.txt") - .lines() - .map(|l| l.split(' ').map(hex).collect()) - .collect(); - // A leaf past its range's highest reads as 0, as Linux leaves it. - let leaf = |leaf: u32, subleaf: u32| { - let read = |leaf: u32, subleaf: u32| { - let l = leaves - .iter() - .find(|l| l[..2] == [u64::from(leaf), u64::from(subleaf)]) - .unwrap_or_else(|| panic!("CPUID {leaf:#x}.{subleaf} was not read")); - [l[2], l[3], l[4], l[5]].map(|r| r as u32) - }; - if leaf <= read(leaf & 0x8000_0000, 0)[0] { read(leaf, subleaf) } else { [0; 4] } - }; - let blocks: Vec> = include_str!("../fixtures/t14/cpuinfo.txt") - .split_terminator("\n\n") - .map(|b| { - b.lines().filter_map(|l| l.split_once(':')).map(|(k, v)| (k.trim(), v.trim())).collect() - }) - .collect(); - let cpuinfo = |key: &str| { - let mut each = blocks.iter().map(|b| b.iter().find(|f| f.0 == key).expect(key).1); - let first = each.next().expect("a CPU"); - assert!(each.all(|v| v == first), "{key} differs between CPUs"); - first - }; - let msrs: Vec<(u64, usize, u64)> = include_str!("../fixtures/t14/msr.txt") - .lines() - .map(|l| match l.split(' ').collect::>()[..] { - [index, cpu, value] => (hex(index), cpu.parse().expect(cpu), hex(value)), - _ => panic!("{l:?} is not `msr cpu value`"), - }) - .collect(); - let msr = |index: u64| { - let each: Vec<_> = msrs.iter().filter(|m| m.0 == index).collect(); - assert!(each.iter().map(|m| m.1).eq(0..blocks.len()), "MSR {index:#x} on every CPU"); - assert!(each.iter().all(|m| m.2 == each[0].2), "MSR {index:#x} differs between CPUs"); - each[0].2 - }; - - let [_, ebx0, ecx0, edx0] = leaf(0, 0); - let id: Vec = [ebx0, edx0, ecx0].iter().flat_map(|r| r.to_le_bytes()).collect(); - let [eax1, _, ecx1, _] = leaf(1, 0); - let [_, ebx7, _, edx7] = leaf(7, 0); - let [eax21, _, ecx21, _] = leaf(0x8000_0021, 0); - let arch = if edx7 & CPUID_7_0_EDX_ARCH_CAPABILITIES != 0 { msr(0x10a) } else { 0 }; - let Facts { - vendor, - signature, - microcode, - cpuid_1_ecx, - cpuid_7_0_ebx, - cpuid_7_0_edx, - cpuid_7_2_edx, - cpuid_8000_0008_ebx, - cpuid_8000_0021_eax, - cpuid_8000_0021_ecx, - arch_capabilities, - mcu_opt_ctrl, - // Stands in for 0x3A, which was not read: `decide`'s `vmx` is held below. - feat_ctl: _, - // Probed on AMD and Hygon alone. - ls_cfg_readable: _, - sbpb_write_accepted: _, - smt, - } = T14; - assert_eq!(cpuinfo("vendor_id").as_bytes(), &id[..]); - assert_eq!(vendor, Vendor::from_id(id[..].try_into().expect("12 bytes"))); - assert_eq!(signature, eax1); - let ident = Ident::new(vendor, signature); - assert_eq!( - [ident.family, ident.model, ident.stepping].map(|n| n.to_string()), - ["cpu family", "model", "stepping"].map(|key| cpuinfo(key).to_string()) - ); - assert_eq!(u64::from(microcode), hex(cpuinfo("microcode"))); - assert_eq!(u64::from(microcode), msr(0x8b) >> 32); - assert_eq!(cpuid_1_ecx, ecx1); - assert_eq!((cpuid_7_0_ebx, cpuid_7_0_edx), (ebx7, edx7)); - assert_eq!(cpuid_7_2_edx, leaf(7, 2)[3]); - assert_eq!(cpuid_8000_0008_ebx, leaf(0x8000_0008, 0)[1]); - assert_eq!((cpuid_8000_0021_eax, cpuid_8000_0021_ecx), (eax21, ecx21)); - assert_eq!(arch_capabilities, arch); - assert_eq!(mcu_opt_ctrl, (arch & ARCH_CAP_GDS_CTRL != 0).then(|| msr(0x123))); - let count = |key| cpuinfo(key).parse::().expect(key); - assert_eq!(smt, count("siblings") > count("cpu cores")); - - let d = decide(&T14); - assert_eq!(d.vmx, cpuinfo("flags").split(' ').any(|f| f == "vmx")); - assert_eq!(d.spec_ctrl, msr(0x48)); -} - -/// `fixtures/tcg/console.txt` verifies the TCG model's signature and nothing -/// else it could read, so its lines are held over either value of every other -/// fact they could rest on. -#[test] -fn the_tcg_models_signature_gives_its_lines() { - let console = include_str!("../fixtures/tcg/console.txt"); - // `print_cpu_info`: the vendor as its `cpu_dev` names it, the brand string, - // then the family, model and stepping. - let (name, identity) = console - .lines() - .find_map(|l| l.split_once("smpboot: CPU0: ")) - .and_then(|(_, cpu0)| cpu0.split_once(" (")) - .expect("Linux names CPU 0"); - let id = Ident::new(TCG.vendor, TCG.signature); - assert_eq!((TCG.vendor, name.split(' ').next()), (Vendor::Amd, Some("AMD"))); - assert_eq!( - identity, - std::format!("family: {:#x}, model: {:#x}, stepping: {:#x})", id.family, id.model, id.stepping) - ); - let lines: String = - console.lines().filter(|l| l.starts_with(PREFIX)).flat_map(|l| [l, "\n"]).collect(); - for hypervisor in [0, CPUID_1_ECX_HYPERVISOR] { - for rdrand in [0, CPUID_1_ECX_RDRAND] { - for microcode in [0, u32::MAX] { - for smt in [false, true] { - let facts = - Facts { cpuid_1_ecx: hypervisor | rdrand, microcode, smt, ..TCG }; - let d = assert_capture(&facts, &lines); - assert_eq!( - state(&d), - State { spectre_v2: Some(SpectreV2::Retpoline), ..NONE } - ); - } - } - } - } -} - -/// The track's exit control: the T14 with `IA32_ARCH_CAPABILITIES` read as 0. -/// Without `IBRS_ALL` it is in retpoline mode, without `GDS_CTRL` it has no GDS -/// microcode, without `MDS_NO` it clears CPU buffers, without `PSCHANGE_MC_NO` -/// its kept VMX gives ITLB multihit's "VMX disabled" (`bugs.c:3096-3097`), and -/// without `RDCL_NO` Linux's L1TF line turns on state the facts do not carry. -#[test] -fn the_t14_with_arch_capabilities_read_as_zero() { - let d = decide(&Facts { arch_capabilities: 0, mcu_opt_ctrl: None, ..T14 }); - let line = |v| d.line(v).map(|l| l.to_string()); - assert_eq!( - line(Vuln::SpectreV2).as_deref(), - Ok( - "Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: conditional; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ) - ); - assert_eq!(line(Vuln::GatherDataSampling).as_deref(), Ok("Vulnerable: No microcode")); - assert_eq!(line(Vuln::Meltdown).as_deref(), Ok("Mitigation: PTI")); - assert_eq!(line(Vuln::Mds).as_deref(), Ok("Mitigation: Clear CPU buffers; SMT vulnerable")); - // Without SMT, "disabled" (`bugs.c:3128-3129`). - let no_smt = Facts { arch_capabilities: 0, mcu_opt_ctrl: None, smt: false, ..T14 }; - assert_eq!( - decide(&no_smt).line(Vuln::Mds).expect("modelled").to_string(), - "Mitigation: Clear CPU buffers; SMT disabled" - ); - assert_eq!(line(Vuln::L1tf).err(), Some(Unmodelled::L1tf)); - assert_eq!(line(Vuln::ItlbMultihit).as_deref(), Ok("KVM: Mitigation: VMX disabled")); - assert_eq!(line(Vuln::TsxAsyncAbort).as_deref(), Ok("Not affected")); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - stibp: Stibp::Prctl, - ssb: Ssb::Prctl, - mds: Some(Mds::Full), - gds: Some(Gds::UcodeNeeded), - its: true, - clear_cpu_buf: true, - ..NONE - } - ); -} - -/// `x86_match_cpu` matches a row's steppings as a mask: KABYLAKE_L's first row -/// covers steppings 0 to 0xB and leaves ITS out. -#[test] -fn a_blacklist_row_matches_only_its_steppings() { - let at = |stepping: u32| { - line(&Facts { signature: 0x0008_06e0 | stepping, ..T14 }, Vuln::IndirectTargetSelection) - }; - assert_eq!(at(0xB), "Not affected"); - assert_eq!(at(0xC), "Mitigation: Aligned branch/return thunks"); -} - -/// COMETLAKE_L stepping 0 has its own row without MMIO_SBDS and GDS, ahead of -/// its any-stepping row that carries both (`common.c:1310-1311`); SRBDS and GDS -/// are forced by those flags (`common.c:1480-1483`, `1521-1523`). -#[test] -fn a_blacklist_row_for_stepping_zero_stands_before_its_any_stepping_row() { - let at = |stepping: u32| decide(&Facts { signature: 0x000a_0660 | stepping, ..COMETLAKE }); - let d = at(0); - assert_eq!((d.srbds, d.gds), (None, None)); - let d = at(1); - assert_eq!((d.srbds, d.gds), (Some(Srbds::Full), Some(Gds::UcodeNeeded))); -} - -/// The first matching row decides: SKYLAKE_X's steppings 0 to 5 row, without -/// ITS, stands before its any-stepping row, with it. -#[test] -fn the_first_matching_blacklist_row_decides() { - let at = |stepping: u32| { - line(&Facts { signature: 0x0005_0650 | stepping, ..T14 }, Vuln::IndirectTargetSelection) - }; - assert_eq!(at(5), "Not affected"); - assert_eq!(at(6), "Mitigation: Aligned branch/return thunks"); -} - -/// KABYLAKE (family 6 model 0x9E) stepping 0xA with [`T14`]'s CPUID and no -/// `ARCH_CAPABILITIES`, outside a hypervisor. -const KABYLAKE: Facts = Facts { signature: 0x0009_06ea, arch_capabilities: 0, mcu_opt_ctrl: None, ..T14 }; - -/// KABYLAKE stepping 0xA's bad microcode is 0x80 and below -/// (`intel.c:143,177-179`): there Linux drops IBRS, IBPB, STIBP and SSBD -/// (`intel.c:296-309`), so Spectre v2 falls to retpolines (`bugs.c:1884-1893`), -/// RETBleed to "Vulnerable" (`bugs.c:1175-1190`), STIBP to none -/// (`bugs.c:1564-1567`) and VMSCAPE to "Vulnerable" (`bugs.c:2851-2855`). -#[test] -fn spectre_bad_microcode_includes_its_bound() { - let d = decide(&Facts { microcode: 0x80, ..KABYLAKE }); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Retpoline), - mds: Some(Mds::Full), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Full), - gds: Some(Gds::UcodeNeeded), - clear_cpu_buf: true, - ..NONE - } - ); - let line = |v| d.line(v).expect("modelled").to_string(); - assert_eq!( - line(Vuln::SpectreV2), - "Mitigation: Retpolines; STIBP: disabled; RSB filling; PBRSB-eIBRS: Not affected; \ - BHI: Not affected" - ); - assert_eq!(line(Vuln::Retbleed), "Vulnerable"); - assert_eq!(line(Vuln::SpecStoreBypass), "Vulnerable"); - assert_eq!(line(Vuln::Vmscape), "Vulnerable"); - let d = decide(&Facts { microcode: 0x81, ..KABYLAKE }); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Ibrs), - ibpb: true, - stibp: Stibp::Prctl, - retbleed: Retbleed::Ibrs, - ssb: Ssb::Prctl, - mds: Some(Mds::Full), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Full), - gds: Some(Gds::UcodeNeeded), - vmscape: Vmscape::IbpbExitToUser, - clear_cpu_buf: true, - spec_ctrl: SPEC_CTRL_IBRS, - ..NONE - } - ); - assert_eq!( - d.line(Vuln::SpectreV2).expect("modelled").to_string(), - "Mitigation: IBRS; IBPB: conditional; STIBP: conditional; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ); - assert_eq!(d.line(Vuln::Retbleed).expect("modelled").to_string(), "Mitigation: IBRS"); -} - -/// `early_init_intel` drops the controls only where one of IBRS, IBPB or STIBP -/// is set (`intel.c:296-299`; `init_speculation_control` sets them from the -/// AMD bits too, `common.c:994-1005`): alone, `SPEC_CTRL_SSBD` keeps SSBD. -#[test] -fn bad_microcode_drops_the_controls_only_beside_one_of_them() { - let ssb = |ebx8| { - decide(&Facts { - microcode: 0x80, - cpuid_7_0_edx: CPUID_7_0_EDX_SPEC_CTRL_SSBD, - cpuid_8000_0008_ebx: ebx8, - ..KABYLAKE - }) - .ssb - }; - assert_eq!(ssb(0), Ssb::Prctl); - assert_eq!(ssb(CPUID_8000_0008_EBX_AMD_IBRS), Ssb::None); - assert_eq!(ssb(CPUID_8000_0008_EBX_AMD_IBPB), Ssb::None); - assert_eq!(ssb(CPUID_8000_0008_EBX_AMD_STIBP), Ssb::None); -} - -/// `spectre_bad_microcodes` keys `x86_vfm`, vendor and family with the model -/// (`intel.c:141-143,177`), and only `early_init_intel` reads it: neither an -/// unknown vendor's nor an Intel family 0xF's model 0x9E stepping 0xA drops -/// the controls at microcode 0x80. -#[test] -fn bad_microcode_is_intel_family_6s() { - let unknown = Facts { vendor: Vendor::from_id(b"GenuineIotel"), feat_ctl: None, ..KABYLAKE }; - let family_f = Facts { signature: 0x0009_0fea, ..KABYLAKE }; - assert_eq!(Ident::new(family_f.vendor, family_f.signature).model, 0x9E); - for facts in [unknown, family_f] { - let d = decide(&Facts { microcode: 0x80, ..facts }); - assert_eq!((d.ibpb, d.ssb), (true, Ssb::Prctl), "{:?}", facts.vendor); - } -} - -#[test] -fn a_guest_cannot_know_its_gds_mitigation() { - let guest = Facts { cpuid_1_ecx: T14.cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, ..T14 }; - assert_eq!(line(&guest, Vuln::GatherDataSampling), "Unknown: Dependent on hypervisor status"); -} - -/// `gds_select_mitigation` reads `GDS_MITG_LOCKED` (`bugs.c:849-861`); `gds_strings` -/// 766. -#[test] -fn firmware_can_lock_the_gds_mitigation() { - let locked = Facts { mcu_opt_ctrl: Some(GDS_MITG_LOCKED), ..T14 }; - assert_eq!(decide(&locked).gds, Some(Gds::FullLocked)); - assert_eq!(line(&locked, Vuln::GatherDataSampling), "Mitigation: Microcode (locked)"); -} - -/// `RTM_ALWAYS_ABORT` sets `TSX_CTRL_RTM_ALWAYS_ABORT` before `TSX_CTRL` is -/// looked at (`tsx.c:170-176`). -#[test] -fn rtm_always_abort_decides_tsx_first() { - let edx7 = T14.cpuid_7_0_edx | CPUID_7_0_EDX_RTM_ALWAYS_ABORT; - let d = decide(&Facts { cpuid_7_0_edx: edx7, ..T14 }); - assert_eq!(d.tsx, Tsx::RtmAlwaysAbort); - let arch = T14.arch_capabilities | ARCH_CAP_TSX_CTRL_MSR; - assert_eq!(decide(&Facts { cpuid_7_0_edx: edx7, arch_capabilities: arch, ..T14 }).tsx, Tsx::RtmAlwaysAbort); - assert_eq!(decide(&Facts { arch_capabilities: arch, ..T14 }).tsx, Tsx::Disable); -} - -// The two fixtures below are this crate's reading of the pinned Linux, awaiting -// a capture on a nightly runner: -// `issues/no-kvm-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md`. - -/// A guest's lines do not read its microcode: `tsa_init` returns under a -/// hypervisor (`amd.c:519-520`) before `amd_check_tsa_microcode`. -#[test] -fn a_milan_kvm_guest_gives_the_tags_lines() { - for microcode in [0, u32::MAX] { - let d = assert_capture( - &Facts { microcode, ..MILAN_GUEST }, - include_str!("../fixtures/awaiting-capture/milan-kvm.txt"), - ); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - ssb: Ssb::Prctl, - srso: Some(Srso::SafeRetUcodeNeeded), - tsa: Some(Tsa::UcodeNeeded), - clear_cpu_buf: true, - ..NONE - } - ); - } -} - -#[test] -fn a_turin_kvm_guest_gives_the_tags_lines() { - let d = assert_capture(&TURIN_GUEST, include_str!("../fixtures/awaiting-capture/turin-kvm.txt")); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Eibrs), - ibpb: true, - ssb: Ssb::Prctl, - sbpb: true, - efer_autoibrs: true, - ..NONE - } - ); -} - -/// A Rome (Zen2, family 0x17 model 0x31) outside a hypervisor: its IBPB -/// implies `IBPB_BRTYPE`, so without SMT it is SRSO-immune, and with SMT the -/// untrained return thunk forces STIBP always on. -#[test] -fn zen2_selects_by_smt() { - let rome = |smt| Facts { - signature: 0x0083_0f10, - cpuid_1_ecx: 0, - cpuid_8000_0021_eax: 0, - smt, - ..MILAN_GUEST - }; - let d = decide(&rome(false)); - assert_eq!( - d.line(Vuln::SpecRstackOverflow).expect("modelled").to_string(), - "Mitigation: SMT disabled" - ); - assert_eq!( - d.line(Vuln::Retbleed).expect("modelled").to_string(), - "Mitigation: untrained return thunk; SMT disabled" - ); - let d = decide(&rome(true)); - assert_eq!( - d.line(Vuln::Retbleed).expect("modelled").to_string(), - "Mitigation: untrained return thunk; SMT enabled with STIBP protection" - ); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - stibp: Stibp::StrictPreferred, - retbleed: Retbleed::Unret, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - srso: Some(Srso::SafeRet), - spec_ctrl: SPEC_CTRL_STIBP, - ..NONE - } - ); -} - -/// A native Zen1, family 0x17 model 1 stepping 1 (`amd.c:602-606`), with SMT -/// and no leaf 0x80000021; 0x80000008:EBX carries AMD_IBPB, AMD_IBRS and -/// AMD_SSBD, and not AMD_STIBP_ALWAYS_ON. -const ZEN1: Facts = Facts { - vendor: Vendor::from_id(b"AuthenticAMD"), - signature: 0x0080_0f11, - microcode: 0, - cpuid_1_ecx: 0, - cpuid_7_0_ebx: 0, - cpuid_7_0_edx: 0, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: CPUID_8000_0008_EBX_AMD_IBPB - | CPUID_8000_0008_EBX_AMD_IBRS - | CPUID_8000_0008_EBX_AMD_SSBD, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: true, -}; - -/// Zen1 with SMT: the untrained return thunk (`bugs.c:1103-1106`) forces STIBP -/// always on without AMD_STIBP_ALWAYS_ON (`bugs.c:1579-1585`), which sets -/// `SPEC_CTRL.STIBP` (`bugs.c:2054-2068,2976-2977`) and gives retbleed's "SMT -/// enabled with STIBP protection" (`bugs.c:3271-3275`); IBPB-extending microcode -/// from `IBPB_BRTYPE` (`amd.c:799-801`) gives safe RET (`bugs.c:2733-2734`). -/// Without AMD_STIBP there is no STIBP to force (`bugs.c:1564-1567`) and -/// retbleed's SMT is "vulnerable". -#[test] -fn zen1_forces_stibp_from_the_untrained_return_thunk() { - let with = Facts { cpuid_8000_0008_ebx: ZEN1.cpuid_8000_0008_ebx | CPUID_8000_0008_EBX_AMD_STIBP, ..ZEN1 }; - let d = decide(&with); - let expected = State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - stibp: Stibp::StrictPreferred, - retbleed: Retbleed::Unret, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - srso: Some(Srso::SafeRet), - spec_ctrl: SPEC_CTRL_STIBP, - ..NONE - }; - assert_eq!(state(&d), expected); - assert_eq!( - line(&with, Vuln::SpectreV2), - "Mitigation: Retpolines; IBPB: conditional; STIBP: always-on; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ); - assert_eq!( - line(&with, Vuln::Retbleed), - "Mitigation: untrained return thunk; SMT enabled with STIBP protection" - ); - assert_eq!(line(&with, Vuln::SpecRstackOverflow), "Mitigation: Safe RET"); - let d = decide(&ZEN1); - assert_eq!(state(&d), State { stibp: Stibp::None, spec_ctrl: 0, ..expected }); - assert_eq!( - line(&ZEN1, Vuln::SpectreV2), - "Mitigation: Retpolines; IBPB: conditional; STIBP: disabled; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ); - assert_eq!(line(&ZEN1, Vuln::Retbleed), "Mitigation: untrained return thunk; SMT vulnerable"); -} - -/// [`MILAN_GUEST`] outside a hypervisor, its `PRED_CMD.SBPB` probe answering -/// `sbpb`. -const fn native_milan(microcode: u32, sbpb: bool) -> Facts { - Facts { - microcode, - cpuid_1_ecx: MILAN_GUEST.cpuid_1_ecx & !CPUID_1_ECX_HYPERVISOR, - sbpb_write_accepted: Some(sbpb), - ..MILAN_GUEST - } -} - -/// What [`native_milan`] selects whatever its microcode and probe. -const NATIVE_MILAN: State = State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - ..NONE -}; - -/// A Milan outside a hypervisor: its `PRED_CMD.SBPB` probe decides SRSO's -/// microcode, and `amd_check_tsa_microcode`'s row for 0xA0011 decides TSA's. -#[test] -fn zen3_native_reads_its_probe_and_its_tsa_microcode() { - let d = decide(&native_milan(0x0a00_11d6, false)); - assert_eq!( - state(&d), - State { - srso: Some(Srso::SafeRetUcodeNeeded), - tsa: Some(Tsa::UcodeNeeded), - ..NATIVE_MILAN - } - ); - let d = decide(&native_milan(0x0a00_11d7, true)); - assert_eq!( - state(&d), - State { srso: Some(Srso::SafeRet), tsa: Some(Tsa::Full), clear_cpu_buf: true, ..NATIVE_MILAN } - ); - assert_eq!(d.line(Vuln::Tsa).expect("modelled").to_string(), "Mitigation: Clear CPU buffers"); -} - -/// A Genoa, family 0x19 model 0x11 stepping 1, which `bsp_init_amd` names Zen4 -/// (`amd.c:625-627`): `tsa_init` sets `VERW_CLEAR` (`amd.c:522-525`) from row -/// 0xa1011's microcode 0x0a10114c up (`amd.c:491,514`), and without it TSA's -/// microcode is missing (`bugs.c:2928-2929`; `tsa_strings`, 2889 and 2892). -#[test] -fn zen4_reads_its_row_of_the_tsa_microcode_table() { - let genoa = |microcode| Facts { signature: 0x00a1_0f11, ..native_milan(microcode, true) }; - let d = decide(&genoa(0x0a10_114c)); - assert_eq!((d.tsa, d.clear_cpu_buf), (Some(Tsa::Full), true)); - assert_eq!(line(&genoa(0x0a10_114c), Vuln::Tsa), "Mitigation: Clear CPU buffers"); - let d = decide(&genoa(0x0a10_114b)); - assert_eq!((d.tsa, d.clear_cpu_buf), (Some(Tsa::UcodeNeeded), false)); - assert_eq!( - line(&genoa(0x0a10_114b), Vuln::Tsa), - "Vulnerable: Clear CPU buffers attempted, no microcode" - ); -} - -/// A Zen3 model 0x08 stepping 2 (signature `0x00a00f82`) keys `amd_check_tsa_microcode`'s -/// row 0xa0082 by the whole model byte (`amd.c:479-490`, `ZEN3` from `bsp_init_amd`, `amd.c:621`): -/// microcode 0x0a00820d and up is mitigated, below it is missing. -#[test] -fn a_tsa_row_keyed_by_a_model_nibble_of_8_or_more_is_read() { - let at = |microcode| Facts { signature: 0x00a0_0f82, ..native_milan(microcode, true) }; - assert_eq!(decide(&at(0x0a00_820d)).tsa, Some(Tsa::Full)); - assert_eq!(decide(&at(0x0a00_820c)).tsa, Some(Tsa::UcodeNeeded)); -} - -/// An AMD CPU is TSA-affected unless it reports both `TSA_SQ_NO` and -/// `TSA_L1_NO` (`common.c:1555-1561`), CPUID.0x80000021:ECX bits 1 and 2 -/// (`scattered.c:52-53`). A guest's TSA microcode is missing -/// (`bugs.c:2928-2929`, `tsa_strings` 2889). -#[test] -fn tsa_is_ruled_out_only_by_both_its_no_bits() { - let at = |ecx| line(&Facts { cpuid_8000_0021_ecx: ecx, ..MILAN_GUEST }, Vuln::Tsa); - let affected = "Vulnerable: Clear CPU buffers attempted, no microcode"; - assert_eq!(at(CPUID_8000_0021_ECX_TSA_SQ_NO), affected); - assert_eq!(at(CPUID_8000_0021_ECX_TSA_L1_NO), affected); - assert_eq!(at(CPUID_8000_0021_ECX_TSA_SQ_NO | CPUID_8000_0021_ECX_TSA_L1_NO), "Not affected"); -} - -/// A native Milan with SMT: no retbleed row names family 0x19, so STIBP -/// always-on comes from `AMD_STIBP_ALWAYS_ON` alone (`bugs.c:1575-1577`); -/// `update_stibp_strict` sets `SPEC_CTRL.STIBP` for it (`bugs.c:2054-2068`, -/// 2976-2977) and `stibp_state` prints it (`bugs.c:3185-3186`). -#[test] -fn amd_stibp_always_on_is_strict_without_the_untrained_return_thunk() { - assert_ne!(MILAN_GUEST.cpuid_8000_0008_ebx & CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON, 0); - let milan = Facts { smt: true, ..native_milan(0x0a00_11d7, true) }; - let d = decide(&milan); - assert_eq!( - state(&d), - State { - stibp: Stibp::StrictPreferred, - srso: Some(Srso::SafeRet), - tsa: Some(Tsa::Full), - clear_cpu_buf: true, - spec_ctrl: SPEC_CTRL_STIBP, - ..NATIVE_MILAN - } - ); - assert_eq!( - line(&milan, Vuln::SpectreV2), - "Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: always-on; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ); -} - -/// AMD families 0x15 to 0x17 enumerating neither `AMD_SSBD` nor `VIRT_SSBD` -/// have SSBD exactly where `MSR_AMD64_LS_CFG` reads (`amd.c:576-596`), and SSB's -/// prctl mode needs SSBD (`bugs.c:2177-2178,2200-2202`; `ssb_strings`, 2123 and -/// 2125). Family 0x17 model 1 is Zen1 (`amd.c:602-607`). -#[test] -fn amd_0x15_to_0x17_take_ssbd_from_the_ls_cfg_probe() { - for signature in [0x0060_0f00, 0x0070_0f00, 0x0080_0f11] { - let at = |readable| Facts { - signature, - cpuid_1_ecx: 0, - cpuid_8000_0008_ebx: MILAN_GUEST.cpuid_8000_0008_ebx - & !(CPUID_8000_0008_EBX_AMD_SSBD | CPUID_8000_0008_EBX_VIRT_SSBD), - cpuid_8000_0021_eax: 0, - ls_cfg_readable: Some(readable), - ..MILAN_GUEST - }; - let d = decide(&at(true)); - assert_eq!(d.ssb, Ssb::Prctl, "{signature:#x}"); - assert_eq!( - line(&at(true), Vuln::SpecStoreBypass), - "Mitigation: Speculative Store Bypass disabled via prctl" - ); - let d = decide(&at(false)); - assert_eq!(d.ssb, Ssb::None, "{signature:#x}"); - assert_eq!(line(&at(false), Vuln::SpecStoreBypass), "Vulnerable"); - } -} - -/// A native family 0x19 reporting `SRSO_USER_KERNEL_NO` leaves safe RET for -/// the VM-exit arm (`bugs.c:2715-2716`): `SRSO_BP_SPEC_REDUCE` first -/// (2771-2775), then IBPB on VM exit given `IBPB_BRTYPE` (2777-2781), which -/// VMSCAPE takes up (2861-2863); without either the microcode is missing -/// (2703). `srso_strings` 2634, 2639 and 2640; `vmscape_strings` 2822-2823. -#[test] -fn srso_user_kernel_no_mitigates_only_the_vm_exit() { - let at = |bp_spec_reduce, sbpb| { - let facts = Facts { - cpuid_8000_0021_eax: MILAN_GUEST.cpuid_8000_0021_eax - | CPUID_8000_0021_EAX_SRSO_USER_KERNEL_NO - | bp_spec_reduce, - ..native_milan(0x0a00_11d7, sbpb) - }; - let d = decide(&facts); - (d.srso, d.vmscape, line(&facts, Vuln::SpecRstackOverflow), line(&facts, Vuln::Vmscape)) - }; - let exit_to_user = "Mitigation: IBPB before exit to userspace"; - assert_eq!( - at(CPUID_8000_0021_EAX_SRSO_BP_SPEC_REDUCE, true), - ( - Some(Srso::BpSpecReduce), - Vmscape::IbpbExitToUser, - "Mitigation: Reduced Speculation".into(), - exit_to_user.into() - ) - ); - assert_eq!( - at(0, true), - ( - Some(Srso::IbpbOnVmexit), - Vmscape::IbpbOnVmexit, - "Mitigation: IBPB on VMEXIT only".into(), - "Mitigation: IBPB on VMEXIT".into() - ) - ); - assert_eq!( - at(0, false), - ( - Some(Srso::UcodeNeeded), - Vmscape::IbpbExitToUser, - "Vulnerable: No microcode".into(), - exit_to_user.into() - ) - ); -} - -/// [`KABYLAKE`] as a guest at its bad microcode, which a guest does not check -/// (`intel.c:173-174`): the hypervisor bit alone gives BHI (`common.c:1533-1536`) -/// and ITS (`common.c:1405-1406`; this stepping's row, `common.c:1303`, has no -/// ITS). IBRS stays, so Spectre v2 is IBRS (`bugs.c:1884-1891`) with the SW -/// BHB loop (`bugs.c:1851-1857`); SRBDS's and GDS's mitigations a guest cannot -/// know (`bugs.c:691-692,821-824`), nor its host's SMT (`bugs.c:3117-3119`, -/// 3155-3157). -#[test] -fn an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt() { - let guest = Facts { - microcode: 0x80, - cpuid_1_ecx: T14.cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, - ..KABYLAKE - }; - let d = decide(&guest); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Ibrs), - bhi: Some(Bhi::SwLoop), - ibpb: true, - stibp: Stibp::Prctl, - retbleed: Retbleed::Ibrs, - ssb: Ssb::Prctl, - mds: Some(Mds::Full), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Hypervisor), - gds: Some(Gds::Hypervisor), - its: true, - clear_cpu_buf: true, - spec_ctrl: SPEC_CTRL_IBRS, - ..NONE - } - ); - let line = |v| d.line(v).expect("modelled").to_string(); - assert_eq!( - line(Vuln::SpectreV2), - "Mitigation: IBRS; IBPB: conditional; STIBP: conditional; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: SW loop, KVM: SW loop" - ); - assert_eq!(line(Vuln::Srbds), "Unknown: Dependent on hypervisor status"); - assert_eq!(line(Vuln::Mds), "Mitigation: Clear CPU buffers; SMT Host state unknown"); - assert_eq!(line(Vuln::MmioStaleData), "Mitigation: Clear CPU buffers; SMT Host state unknown"); - assert_eq!(line(Vuln::IndirectTargetSelection), "Mitigation: Aligned branch/return thunks"); - assert_eq!(line(Vuln::Vmscape), "Not affected"); -} - -/// An Alder Lake (family 6 model 0x97) with [`T14`]'s CPUID and -/// `ARCH_CAPABILITIES` and with `BHI_CTRL`: eIBRS sets `SPEC_CTRL.IBRS` -/// (`bugs.c:1930-1936`) and BHI is mitigated in hardware by `BHI_DIS_S` -/// (`bugs.c:1795-1805,1847-1849`; `spectre_bhi_state` 3224-3225). STIBP is not -/// printed under eIBRS (`bugs.c:3176-3178`); its row gives VMSCAPE -/// (`common.c:1316`). -#[test] -fn alder_lake_mitigates_bhi_in_hardware() { - let adl = Facts { - signature: 0x0009_0672, - cpuid_7_2_edx: T14.cpuid_7_2_edx | CPUID_7_2_EDX_BHI_CTRL, - ..T14 - }; - let d = decide(&adl); - assert_eq!( - state(&d), - State { - spectre_v2: Some(SpectreV2::Eibrs), - bhi: Some(Bhi::BhiDisS), - ibpb: true, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - spec_ctrl: SPEC_CTRL_IBRS | SPEC_CTRL_BHI_DIS_S, - ..NONE - } - ); - assert_eq!( - line(&adl, Vuln::SpectreV2), - "Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; PBRSB-eIBRS: SW sequence; \ - BHI: BHI_DIS_S" - ); - assert_eq!(line(&adl, Vuln::IndirectTargetSelection), "Not affected"); -} - -/// [`T14`] as a guest without `IBRS_ALL` and with `RRSBA`: retpolines -/// (`bugs.c:1893`) disable RRSBA through `RRSBA_DIS_S` where `RRSBA_CTRL` is -/// (`bugs.c:1723-1739,1966-1969`), which leaves BHI to them -/// (`bugs.c:1837-1841`; `spectre_bhi_state` 3228-3231); without `RRSBA_CTRL` -/// BHI takes the SW loop (`bugs.c:1851-1857`). -#[test] -fn a_retpoline_guest_leaves_bhi_to_retpolines_only_with_rrsba_disabled() { - let guest = |edx72| Facts { - cpuid_1_ecx: T14.cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, - cpuid_7_2_edx: edx72, - arch_capabilities: (T14.arch_capabilities & !ARCH_CAP_IBRS_ALL) | ARCH_CAP_RRSBA, - ..T14 - }; - let with = guest(T14.cpuid_7_2_edx | CPUID_7_2_EDX_RRSBA_CTRL); - let expected = State { - spectre_v2: Some(SpectreV2::Retpoline), - bhi: Some(Bhi::Retpoline), - ibpb: true, - ibrs_fw: true, - stibp: Stibp::Prctl, - ssb: Ssb::Prctl, - gds: Some(Gds::Hypervisor), - its: true, - spec_ctrl: SPEC_CTRL_RRSBA_DIS_S, - ..NONE - }; - assert_eq!(state(&decide(&with)), expected); - assert_eq!( - line(&with, Vuln::SpectreV2), - "Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: conditional; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: Retpoline" - ); - let without = guest(T14.cpuid_7_2_edx); - assert_eq!(state(&decide(&without)), State { bhi: Some(Bhi::SwLoop), spec_ctrl: 0, ..expected }); - assert_eq!( - line(&without, Vuln::SpectreV2), - "Mitigation: Retpolines; IBPB: conditional; IBRS_FW; STIBP: conditional; RSB filling; \ - PBRSB-eIBRS: Not affected; BHI: SW loop, KVM: SW loop" - ); -} - -/// A native Comet Lake (family 6 model 0xA5) with RTM and `MD_CLEAR`, -/// `FLUSH_L1D` and `SRBDS_CTRL`, whose `ARCH_CAPABILITIES` the fixtures below -/// vary; its row (`common.c:1309`) gives MMIO, MMIO_SBDS, RETBLEED, GDS, ITS -/// and VMSCAPE. -const COMETLAKE: Facts = Facts { - vendor: Vendor::from_id(b"GenuineIntel"), - signature: 0x000a_0655, - microcode: 0xf8, - cpuid_1_ecx: CPUID_1_ECX_VMX | CPUID_1_ECX_AVX | CPUID_1_ECX_RDRAND, - cpuid_7_0_ebx: CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED, - cpuid_7_0_edx: CPUID_7_0_EDX_SRBDS_CTRL - | CPUID_7_0_EDX_MD_CLEAR - | CPUID_7_0_EDX_SPEC_CTRL - | CPUID_7_0_EDX_INTEL_STIBP - | CPUID_7_0_EDX_FLUSH_L1D - | CPUID_7_0_EDX_ARCH_CAPABILITIES - | CPUID_7_0_EDX_SPEC_CTRL_SSBD, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: Some(FEAT_CTL_LOCKED | FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX), - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: true, -}; - -/// What [`COMETLAKE`] selects outside its buffer-clearing bugs: IBRS for -/// RETBleed (`bugs.c:1884-1891,1175-1178`), no GDS microcode -/// (`bugs.c:831-842`), ITS's thunks and VMSCAPE's IBPB. -const COMETLAKE_STATE: State = State { - spectre_v2: Some(SpectreV2::Ibrs), - ibpb: true, - stibp: Stibp::Prctl, - retbleed: Retbleed::Ibrs, - ssb: Ssb::Prctl, - gds: Some(Gds::UcodeNeeded), - its: true, - vmscape: Vmscape::IbpbExitToUser, - spec_ctrl: SPEC_CTRL_IBRS, - ..NONE -}; - -/// Without `MDS_NO`, `MD_CLEAR` mitigates TAA (`bugs.c:353-356`) and, with -/// `FLUSH_L1D`, MMIO (`bugs.c:468-472`). With `MDS_NO` and without -/// `TSX_CTRL_MSR` TAA's microcode is missing (`bugs.c:367-369`), and so is -/// MMIO's unless `FB_CLEAR` (`bugs.c:468-474`). RTM on keeps SRBDS's -/// microcode mitigation (`bugs.c:688-695`). -#[test] -fn comet_lake_clears_cpu_buffers_by_mds_no_and_fb_clear() { - let at = |arch| Facts { arch_capabilities: arch, ..COMETLAKE }; - let d = decide(&at(0)); - assert_eq!( - state(&d), - State { - mds: Some(Mds::Full), - taa: Some(Taa::Verw), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Full), - clear_cpu_buf: true, - ..COMETLAKE_STATE - } - ); - let line = |facts: &Facts, v| line(facts, v); - assert_eq!(line(&at(0), Vuln::TsxAsyncAbort), "Mitigation: Clear CPU buffers; SMT vulnerable"); - assert_eq!(line(&at(0), Vuln::MmioStaleData), "Mitigation: Clear CPU buffers; SMT vulnerable"); - assert_eq!(line(&at(0), Vuln::Srbds), "Mitigation: Microcode"); - let mds_no = at(ARCH_CAP_MDS_NO); - assert_eq!( - state(&decide(&mds_no)), - State { - taa: Some(Taa::UcodeNeeded), - mmio: Some(Mmio::UcodeNeeded), - srbds: Some(Srbds::Full), - clear_cpu_buf: true, - ..COMETLAKE_STATE - } - ); - let no_microcode = "Vulnerable: Clear CPU buffers attempted, no microcode; SMT vulnerable"; - assert_eq!(line(&mds_no, Vuln::TsxAsyncAbort), no_microcode); - assert_eq!(line(&mds_no, Vuln::MmioStaleData), no_microcode); - assert_eq!(line(&mds_no, Vuln::Mds), "Not affected"); - let fb_clear = at(ARCH_CAP_MDS_NO | ARCH_CAP_FB_CLEAR); - assert_eq!(decide(&fb_clear).mmio, Some(Mmio::Verw)); -} - -/// SRBDS is "TSX disabled" on an `MDS_NO` CPU only where RTM is off and MMIO -/// Stale Data is not (`bugs.c:688-690`): `TSX_CTRL` disables RTM under -/// `TSX_MODE_OFF` (`tsx.c:188-227`), as `RTM_ALWAYS_ABORT` does -/// (`tsx.c:170-176`), and TAA is then "TSX disabled" (`bugs.c:335-338`) -/// without clearing buffers; the three `*_NO` bits make MMIO immune -/// (`common.c:1363-1368,1495`). -#[test] -fn srbds_is_tsx_disabled_only_without_rtm_and_mmio() { - let immune = ARCH_CAP_MDS_NO | ARCH_CAP_FBSDP_NO | ARCH_CAP_PSDP_NO | ARCH_CAP_SBDR_SSDP_NO; - let tsx_ctrl = Facts { arch_capabilities: immune | ARCH_CAP_TSX_CTRL_MSR, ..COMETLAKE }; - let off = State { - tsx: Tsx::Disable, - taa: Some(Taa::TsxDisabled), - srbds: Some(Srbds::TsxOff), - ..COMETLAKE_STATE - }; - assert_eq!(state(&decide(&tsx_ctrl)), off); - assert_eq!(line(&tsx_ctrl, Vuln::Srbds), "Mitigation: TSX disabled"); - assert_eq!(line(&tsx_ctrl, Vuln::TsxAsyncAbort), "Mitigation: TSX disabled"); - assert_eq!(line(&tsx_ctrl, Vuln::MmioStaleData), "Not affected"); - let always_abort = Facts { - cpuid_7_0_edx: COMETLAKE.cpuid_7_0_edx | CPUID_7_0_EDX_RTM_ALWAYS_ABORT, - arch_capabilities: immune, - ..COMETLAKE - }; - assert_eq!(state(&decide(&always_abort)), State { tsx: Tsx::RtmAlwaysAbort, ..off }); - let rtm = Facts { arch_capabilities: immune, ..COMETLAKE }; - assert_eq!( - state(&decide(&rtm)), - State { - taa: Some(Taa::UcodeNeeded), - srbds: Some(Srbds::Full), - clear_cpu_buf: true, - ..COMETLAKE_STATE - } - ); - let mmio = Facts { arch_capabilities: ARCH_CAP_MDS_NO | ARCH_CAP_TSX_CTRL_MSR, ..COMETLAKE }; - assert_eq!( - state(&decide(&mmio)), - State { - tsx: Tsx::Disable, - taa: Some(Taa::TsxDisabled), - mmio: Some(Mmio::UcodeNeeded), - srbds: Some(Srbds::Full), - ..COMETLAKE_STATE - } - ); -} - -/// An affected CPU's ITLB multihit line reads `X86_FEATURE_MSR_IA32_FEAT_CTL` -/// and `X86_FEATURE_VMX` (`bugs.c:3093-3095`) as `init_ia32_feat_ctl` left them: -/// a faulting read clears VMX (`feat_ctl.c:119-123`), an unlocked MSR is locked -/// with VMX enabled (`feat_ctl.c:139-166`), and a locked one keeps VMX only -/// with `VMX_ENABLED_OUTSIDE_SMX` (`feat_ctl.c:174-179`). No VM runs, so a kept -/// VMX is "disabled" (`bugs.c:3096-3097`). -#[test] -fn itlb_multihit_reads_what_feat_ctl_left_of_vmx() { - let at = |ecx, feat_ctl| line(&Facts { cpuid_1_ecx: ecx, feat_ctl, ..COMETLAKE }, Vuln::ItlbMultihit); - let ecx = COMETLAKE.cpuid_1_ecx; - let (disabled, unsupported) = ("KVM: Mitigation: VMX disabled", "KVM: Mitigation: VMX unsupported"); - assert_eq!(at(ecx, Some(0)), disabled); - assert_eq!(at(ecx, Some(FEAT_CTL_LOCKED | FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX)), disabled); - assert_eq!(at(ecx, Some(FEAT_CTL_LOCKED)), unsupported); - assert_eq!(at(ecx, Some(FEAT_CTL_LOCKED | 1 << 1)), unsupported); - assert_eq!(at(ecx, None), unsupported); - assert_eq!(at(ecx & !CPUID_1_ECX_VMX, Some(0)), unsupported); - assert_eq!(line(&COMETLAKE, Vuln::ItlbMultihit), disabled); - assert_eq!( - line(&Facts { arch_capabilities: ARCH_CAP_PSCHANGE_MC_NO, ..COMETLAKE }, Vuln::ItlbMultihit), - "Not affected" - ); -} - -#[test] -#[should_panic(expected = "IA32_FEAT_CTL is read only where")] -fn an_amd_cpu_with_feat_ctl_is_a_contradiction() { - decide(&Facts { feat_ctl: Some(0), ..TCG }); -} - -/// A Silvermont (family 6 model 0x37) is `MSBDS_ONLY` (`common.c:1202`), whose -/// SMT line is "mitigated" or "disabled" (`bugs.c:3122-3126`). Neither MMIO -/// row names it, so its MMIO is unknown (`common.c:1498-1499`). -#[test] -fn an_msbds_only_cpu_reports_its_smt_as_mitigated() { - let silvermont = |smt| Facts { - vendor: Vendor::from_id(b"GenuineIntel"), - signature: 0x0003_0678, - microcode: 0, - cpuid_1_ecx: 0, - cpuid_7_0_ebx: 0, - cpuid_7_0_edx: CPUID_7_0_EDX_MD_CLEAR | CPUID_7_0_EDX_SPEC_CTRL | CPUID_7_0_EDX_INTEL_STIBP, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt, - }; - assert_eq!(line(&silvermont(true), Vuln::Mds), "Mitigation: Clear CPU buffers; SMT mitigated"); - assert_eq!(line(&silvermont(false), Vuln::Mds), "Mitigation: Clear CPU buffers; SMT disabled"); - assert_eq!(line(&silvermont(true), Vuln::MmioStaleData), "Unknown: No mitigations"); - assert_eq!(line(&silvermont(true), Vuln::L1tf), "Not affected"); - assert_eq!( - state(&decide(&silvermont(true))), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - stibp: Stibp::Prctl, - mds: Some(Mds::Full), - clear_cpu_buf: true, - ..NONE - } - ); -} - -/// A native Hygon Dhyana, family 0x18 model 0 stepping 1, with SMT: its -/// whitelist row is AMD's (`common.c:1238`) and its blacklist row gives -/// RETBLEED, SRSO and VMSCAPE (`common.c:1341`). Hygon probes `LS_CFG` for -/// SSBD in every family (`hygon.c:228-239`), takes the untrained return thunk -/// (`bugs.c:1103-1106`) and IBPB rather than IBRS around firmware -/// (`bugs.c:2028-2036`), and has no `early_init_amd` to set `IBPB_BRTYPE`, so -/// safe RET lacks its microcode (`bugs.c:2672,2735-2736`). -#[test] -fn hygon_decides_as_linux_does() { - let dhyana = |readable| Facts { - vendor: Vendor::from_id(b"HygonGenuine"), - signature: 0x0090_0f01, - cpuid_8000_0008_ebx: CPUID_8000_0008_EBX_AMD_IBPB - | CPUID_8000_0008_EBX_AMD_IBRS - | CPUID_8000_0008_EBX_AMD_STIBP, - ls_cfg_readable: Some(readable), - ..ZEN1 - }; - let d = assert_capture( - &dhyana(true), - "/sys/devices/system/cpu/vulnerabilities/gather_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/indirect_target_selection:Not affected -/sys/devices/system/cpu/vulnerabilities/itlb_multihit:Not affected -/sys/devices/system/cpu/vulnerabilities/l1tf:Not affected -/sys/devices/system/cpu/vulnerabilities/mds:Not affected -/sys/devices/system/cpu/vulnerabilities/meltdown:Not affected -/sys/devices/system/cpu/vulnerabilities/mmio_stale_data:Not affected -/sys/devices/system/cpu/vulnerabilities/reg_file_data_sampling:Not affected -/sys/devices/system/cpu/vulnerabilities/retbleed:Mitigation: untrained return thunk; SMT enabled with STIBP protection -/sys/devices/system/cpu/vulnerabilities/spec_rstack_overflow:Vulnerable: Safe RET, no microcode -/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl -/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization -/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Retpolines; IBPB: conditional; STIBP: always-on; RSB filling; PBRSB-eIBRS: Not affected; BHI: Not affected -/sys/devices/system/cpu/vulnerabilities/srbds:Not affected -/sys/devices/system/cpu/vulnerabilities/tsa:Not affected -/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Not affected -/sys/devices/system/cpu/vulnerabilities/vmscape:Mitigation: IBPB before exit to userspace", - ); - let expected = State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - stibp: Stibp::StrictPreferred, - retbleed: Retbleed::Unret, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - srso: Some(Srso::SafeRetUcodeNeeded), - spec_ctrl: SPEC_CTRL_STIBP, - ..NONE - }; - assert_eq!(state(&d), expected); - assert_eq!(state(&decide(&dhyana(false))), State { ssb: Ssb::None, ..expected }); -} - -/// Family 0x18 is below 0x19, so a Dhyana without SMT has SRSO ruled out by -/// `SRSO_NO` once `IBPB_BRTYPE` is set (`bugs.c:2689-2691`), shown as "SMT -/// disabled" (`bugs.c:3284-3285`). -#[test] -fn hygon_without_smt_and_with_ibpb_brtype_has_srso_smt_disabled() { - let dhyana = Facts { - vendor: Vendor::from_id(b"HygonGenuine"), - signature: 0x0090_0f01, - cpuid_8000_0008_ebx: CPUID_8000_0008_EBX_AMD_IBPB - | CPUID_8000_0008_EBX_AMD_IBRS - | CPUID_8000_0008_EBX_AMD_STIBP, - cpuid_8000_0021_eax: CPUID_8000_0021_EAX_IBPB_BRTYPE, - ls_cfg_readable: Some(true), - smt: false, - ..ZEN1 - }; - assert_eq!(decide(&dhyana).srso, Some(Srso::SmtDisabled)); - assert_eq!(line(&dhyana, Vuln::SpecRstackOverflow), "Mitigation: SMT disabled"); -} - -#[test] -#[should_panic(expected = "MSR_AMD64_LS_CFG is probed exactly where")] -fn a_hygon_cpu_without_its_ls_cfg_probe_is_a_contradiction() { - decide(&Facts { - vendor: Vendor::from_id(b"HygonGenuine"), - signature: 0x0090_0f01, - cpuid_8000_0008_ebx: 0, - ..ZEN1 - }); -} - -/// A Zhaoxin, family 7 model 0x1B, with SMT: its whitelist row rules out -/// Spectre v2 but not v1 (`common.c:1242,1426`), so neither IBPB, STIBP nor IBRS -/// around firmware is set up (`bugs.c:1869-1871,1507-1508`); it reads -/// `IA32_FEAT_CTL` (`zhaoxin.c:97`). An unknown vendor's family 7 matches no -/// row and has Spectre v2. -#[test] -fn zhaoxin_without_spectre_v2_sets_up_no_ibpb() { - let zhaoxin = Facts { - vendor: Vendor::from_id(b" Shanghai "), - signature: 0x0001_07b5, - microcode: 0, - cpuid_1_ecx: CPUID_1_ECX_VMX, - cpuid_7_0_ebx: 0, - cpuid_7_0_edx: CPUID_7_0_EDX_SPEC_CTRL - | CPUID_7_0_EDX_INTEL_STIBP - | CPUID_7_0_EDX_SPEC_CTRL_SSBD, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: 0, - mcu_opt_ctrl: None, - feat_ctl: Some(0), - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: true, - }; - let d = decide(&zhaoxin); - assert_eq!( - state(&d), - State { ssb: Ssb::Prctl, mds: Some(Mds::Vmwerv), clear_cpu_buf: true, ..NONE } - ); - assert_eq!(line(&zhaoxin, Vuln::SpectreV2), "Not affected"); - assert_eq!( - line(&zhaoxin, Vuln::SpectreV1), - "Mitigation: usercopy/swapgs barriers and __user pointer sanitization" - ); - assert_eq!( - line(&zhaoxin, Vuln::Mds), - "Vulnerable: Clear CPU buffers attempted, no microcode; SMT vulnerable" - ); - assert_eq!(line(&zhaoxin, Vuln::ItlbMultihit), "KVM: Mitigation: VMX disabled"); - let unknown = Facts { vendor: Vendor::from_id(b"GenuineIotel"), feat_ctl: None, ..zhaoxin }; - assert_eq!(decide(&unknown).spectre_v2, Some(SpectreV2::Retpoline)); -} - -/// A Centaur family 5 is `NO_SPECULATION` (`common.c:1184`) and reads -/// `IA32_FEAT_CTL` (`centaur.c:217`); ITLB multihit, set before that return -/// (`common.c:1419-1424`), is all it has. -#[test] -fn centaur_family_5_has_only_itlb_multihit() { - let centaur = Facts { - vendor: Vendor::from_id(b"CentaurHauls"), - signature: 0x0000_0540, - feat_ctl: Some(0), - cpuid_1_ecx: CPUID_1_ECX_VMX, - ..TCG - }; - assert_eq!(state(&decide(¢aur)), NONE); - assert_eq!(line(¢aur, Vuln::ItlbMultihit), "KVM: Mitigation: VMX disabled"); - assert_eq!(line(¢aur, Vuln::SpectreV1), "Not affected"); -} - -#[test] -#[should_panic(expected = "PRED_CMD.SBPB is probed exactly where")] -fn a_native_zen3_without_its_probe_is_a_contradiction() { - decide(&Facts { cpuid_1_ecx: 0, ..MILAN_GUEST }); -} - -/// `x86_family` adds the extended family only to family 0xF, and `x86_model` -/// the extended model only from family 6 (`arch/x86/lib/cpu.c:6-37`). -#[test] -fn family_and_model_are_derived_as_arch_x86_lib_derives_them() { - let at = |sig| { - let id = Ident::new(Vendor::Intel, sig); - (id.family, id.model, id.stepping) - }; - assert_eq!(at(0x00a0_0f11), (0x19, 0x01, 1)); - assert_eq!(at(0x0ff0_06c1), (6, 0x0c, 1)); - assert_eq!(at(0x000f_0521), (5, 0x02, 1)); -} - -/// Each `*_NO` bit and feature test in `cpu_set_bug_bits` rules its bug out on -/// [`T14`]: `SSB_NO` (`common.c:1431-1434`), `PBRSB_NO` (`common.c:1440-1445`), -/// `GDS_NO` and a missing AVX (`common.c:1521-1523`), `ITS_NO` and `BHI_CTRL` -/// (`common.c:1391-1398`); `RSBA` gives RETBleed (`common.c:1502-1505`), which -/// eIBRS mitigates (`bugs.c:1180-1183`, `retbleed_strings` 1003). -#[test] -fn the_t14s_no_bits_each_rule_out_their_bug() { - let t14 = state(&decide(&T14)); - let arch = |bit| Facts { arch_capabilities: T14.arch_capabilities | bit, ..T14 }; - assert_eq!(line(&arch(ARCH_CAP_SSB_NO), Vuln::SpecStoreBypass), "Not affected"); - assert_eq!(state(&decide(&arch(ARCH_CAP_SSB_NO))), State { ssb: Ssb::None, ..t14 }); - assert_eq!( - line(&arch(ARCH_CAP_PBRSB_NO), Vuln::SpectreV2), - "Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; PBRSB-eIBRS: Not affected; \ - BHI: SW loop, KVM: SW loop" - ); - assert_eq!(line(&arch(ARCH_CAP_GDS_NO), Vuln::GatherDataSampling), "Not affected"); - let no_avx = Facts { cpuid_1_ecx: T14.cpuid_1_ecx & !CPUID_1_ECX_AVX, ..T14 }; - assert_eq!(line(&no_avx, Vuln::GatherDataSampling), "Not affected"); - assert_eq!(line(&arch(ARCH_CAP_ITS_NO), Vuln::IndirectTargetSelection), "Not affected"); - let rsba = arch(ARCH_CAP_RSBA); - assert_eq!(state(&decide(&rsba)), State { retbleed: Retbleed::Eibrs, ..t14 }); - assert_eq!(line(&rsba, Vuln::Retbleed), "Mitigation: Enhanced IBRS"); - // `BHI_CTRL` also puts BHI in hardware (`bugs.c:1847-1849`). - let bhi_ctrl = Facts { cpuid_7_2_edx: T14.cpuid_7_2_edx | CPUID_7_2_EDX_BHI_CTRL, ..T14 }; - assert_eq!( - state(&decide(&bhi_ctrl)), - State { - bhi: Some(Bhi::BhiDisS), - its: false, - spec_ctrl: SPEC_CTRL_IBRS | SPEC_CTRL_BHI_DIS_S, - ..t14 - } - ); -} - -/// RFDS by `RFDS_CLEAR` where no row names it, whose `VERW` clears CPU buffers -/// (`common.c:1370-1386`; `bugs.c:529-532`, `rfds_strings` 516), and by -/// Alder Lake's row without `RFDS_NO` or `RFDS_CLEAR`, whose microcode is -/// missing (`common.c:1316`; `bugs.c:531-532`, `rfds_strings` 517). -#[test] -fn rfds_is_cleared_only_with_rfds_clear() { - let clear = Facts { - arch_capabilities: (T14.arch_capabilities & !ARCH_CAP_RFDS_NO) | ARCH_CAP_RFDS_CLEAR, - ..T14 - }; - assert_eq!( - state(&decide(&clear)), - State { rfds: Some(Rfds::Verw), clear_cpu_buf: true, ..state(&decide(&T14)) } - ); - assert_eq!(line(&clear, Vuln::RegFileDataSampling), "Mitigation: Clear Register File"); - let adl = Facts { - signature: 0x0009_0672, - arch_capabilities: T14.arch_capabilities & !ARCH_CAP_RFDS_NO, - ..T14 - }; - assert_eq!(decide(&adl).rfds, Some(Rfds::UcodeNeeded)); - assert_eq!(line(&adl, Vuln::RegFileDataSampling), "Vulnerable: No microcode"); - assert!(!decide(&adl).clear_cpu_buf); -} - -/// [`COMETLAKE`] without `ARCH_CAPABILITIES` bits, varied one fact at a time: -/// `TAA_NO` rules TAA out (`common.c:1467-1470`); without `MD_CLEAR` MDS is -/// `VMWERV` (`bugs.c:274-275`) and TAA's and MMIO's microcode is missing -/// (`bugs.c:353-356,468-474`); without `FLUSH_L1D` MMIO's is (`bugs.c:468-474`); -/// without `SRBDS_CTRL` SRBDS's is (`bugs.c:693-694`, `srbds_strings` 633); -/// either RDRAND or RDSEED gives SRBDS (`common.c:1480-1483`). -#[test] -fn comet_lake_varies_one_fact_at_a_time() { - let all = State { - mds: Some(Mds::Full), - taa: Some(Taa::Verw), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Full), - clear_cpu_buf: true, - ..COMETLAKE_STATE - }; - let taa_no = Facts { arch_capabilities: ARCH_CAP_TAA_NO, ..COMETLAKE }; - assert_eq!(state(&decide(&taa_no)), State { taa: None, ..all }); - assert_eq!(line(&taa_no, Vuln::TsxAsyncAbort), "Not affected"); - let edx7 = |clear: u32| Facts { cpuid_7_0_edx: COMETLAKE.cpuid_7_0_edx & !clear, ..COMETLAKE }; - assert_eq!( - state(&decide(&edx7(CPUID_7_0_EDX_MD_CLEAR))), - State { - mds: Some(Mds::Vmwerv), - taa: Some(Taa::UcodeNeeded), - mmio: Some(Mmio::UcodeNeeded), - ..all - } - ); - assert_eq!( - state(&decide(&edx7(CPUID_7_0_EDX_FLUSH_L1D))), - State { mmio: Some(Mmio::UcodeNeeded), ..all } - ); - let no_srbds_ctrl = edx7(CPUID_7_0_EDX_SRBDS_CTRL); - assert_eq!(state(&decide(&no_srbds_ctrl)), State { srbds: Some(Srbds::UcodeNeeded), ..all }); - assert_eq!(line(&no_srbds_ctrl, Vuln::Srbds), "Vulnerable: No microcode"); - let rdseed = Facts { cpuid_1_ecx: COMETLAKE.cpuid_1_ecx & !CPUID_1_ECX_RDRAND, ..COMETLAKE }; - assert_eq!(decide(&rdseed).srbds, Some(Srbds::Full)); - let rdrand = Facts { cpuid_7_0_ebx: COMETLAKE.cpuid_7_0_ebx & !CPUID_7_0_EBX_RDSEED, ..COMETLAKE }; - assert_eq!(decide(&rdrand).srbds, Some(Srbds::Full)); - let neither = Facts { cpuid_7_0_ebx: rdrand.cpuid_7_0_ebx, ..rdseed }; - assert_eq!(decide(&neither).srbds, None); - assert_eq!(line(&neither, Vuln::Srbds), "Not affected"); -} - -/// [`COMETLAKE`] as a guest: BHI from the hypervisor bit (`common.c:1533-1536`) -/// takes the SW loop under IBRS (`bugs.c:1851-1857`), VMSCAPE is bare-metal -/// only (`common.c:1545-1548`), and TAA's host SMT state is unknown -/// (`bugs.c:3138-3141`). -#[test] -fn a_comet_lake_guest_cannot_know_its_hosts_smt() { - let guest = Facts { cpuid_1_ecx: COMETLAKE.cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, ..COMETLAKE }; - assert_eq!( - state(&decide(&guest)), - State { - bhi: Some(Bhi::SwLoop), - mds: Some(Mds::Full), - taa: Some(Taa::Verw), - mmio: Some(Mmio::Verw), - srbds: Some(Srbds::Hypervisor), - gds: Some(Gds::Hypervisor), - vmscape: Vmscape::None, - clear_cpu_buf: true, - ..COMETLAKE_STATE - } - ); - assert_eq!( - line(&guest, Vuln::TsxAsyncAbort), - "Mitigation: Clear CPU buffers; SMT Host state unknown" - ); -} - -/// TAA without RTM in CPUID comes from `TSX_CTRL_MSR` (`common.c:1467-1470`) -/// and is "TSX disabled" (`bugs.c:335-338`). Without RTM and without `TSX_CTRL` -/// there is no TAA, and SRBDS is "TSX disabled" on an `MDS_NO` CPU without MMIO -/// (`bugs.c:688-690`) and the microcode's otherwise (`bugs.c:693-695`). -#[test] -fn rtm_absent_from_cpuid_is_rtm_off() { - let no_rtm = |arch| Facts { - cpuid_7_0_ebx: COMETLAKE.cpuid_7_0_ebx & !CPUID_7_0_EBX_RTM, - arch_capabilities: arch, - ..COMETLAKE - }; - assert_eq!( - state(&decide(&no_rtm(ARCH_CAP_MDS_NO | ARCH_CAP_TSX_CTRL_MSR))), - State { - tsx: Tsx::Disable, - taa: Some(Taa::TsxDisabled), - mmio: Some(Mmio::UcodeNeeded), - srbds: Some(Srbds::Full), - ..COMETLAKE_STATE - } - ); - let immune = ARCH_CAP_FBSDP_NO | ARCH_CAP_PSDP_NO | ARCH_CAP_SBDR_SSDP_NO; - assert_eq!( - state(&decide(&no_rtm(ARCH_CAP_MDS_NO | immune))), - State { srbds: Some(Srbds::TsxOff), ..COMETLAKE_STATE } - ); - assert_eq!( - state(&decide(&no_rtm(immune))), - State { - mds: Some(Mds::Full), - srbds: Some(Srbds::Full), - clear_cpu_buf: true, - ..COMETLAKE_STATE - } - ); -} - -/// The bad-microcode rows key model and stepping together (`intel.c:177-178`): -/// KABYLAKE stepping 8 has no row, and Comet Lake stepping 0xA is not -/// KABYLAKE's stepping 0xA. -#[test] -fn a_bad_microcode_row_is_its_model_and_its_stepping() { - for signature in [0x0009_06e8, 0x000a_065a] { - let d = decide(&Facts { signature, microcode: 0x80, ..KABYLAKE }); - assert_eq!((d.ibpb, d.ssb), (true, Ssb::Prctl), "{signature:#x}"); - } -} - -/// An unknown vendor's family 6 matches no row (`match.c:36-57`): `RSBA` gives -/// RETBleed (`common.c:1502-1505`), which neither the AMD nor the Intel -/// selection takes up (`bugs.c:1101-1106,1175`), and IBRS guards firmware -/// (`bugs.c:2038-2041`). Its MMIO is unknown (`common.c:1498-1499`). -#[test] -fn an_unknown_vendors_retbleed_is_unmitigated() { - let unknown = Facts { - vendor: Vendor::from_id(b"GenuineIotel"), - signature: 0x0009_06ea, - microcode: 0, - cpuid_1_ecx: 0, - cpuid_7_0_ebx: 0, - cpuid_7_0_edx: CPUID_7_0_EDX_SPEC_CTRL | CPUID_7_0_EDX_ARCH_CAPABILITIES, - cpuid_7_2_edx: 0, - cpuid_8000_0008_ebx: 0, - cpuid_8000_0021_eax: 0, - cpuid_8000_0021_ecx: 0, - arch_capabilities: ARCH_CAP_RSBA, - mcu_opt_ctrl: None, - feat_ctl: None, - ls_cfg_readable: None, - sbpb_write_accepted: None, - smt: false, - }; - assert_eq!( - state(&decide(&unknown)), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - mds: Some(Mds::Vmwerv), - clear_cpu_buf: true, - ..NONE - } - ); - assert_eq!(line(&unknown, Vuln::Retbleed), "Vulnerable"); - assert_eq!(line(&unknown, Vuln::MmioStaleData), "Unknown: No mitigations"); -} - -/// [`ZEN1`] with `BTC_NO` has no RETBleed (`common.c:1502-1505`), so IBRS, not -/// IBPB, guards firmware (`bugs.c:2028-2041`); with `AMD_SSB_NO` it has no SSB -/// (`common.c:1431-1434`). Without AMD_IBPB there is no `IBPB_BRTYPE` -/// (`amd.c:800-801`), so safe RET lacks its microcode (`bugs.c:2735-2736`), -/// VMSCAPE has no IBPB (`bugs.c:2851-2855`), and IBRS guards firmware. -#[test] -fn zen1_by_btc_no_ssb_no_and_ibpb() { - let ebx8 = |set, clear: u32| Facts { - cpuid_8000_0008_ebx: (ZEN1.cpuid_8000_0008_ebx | set) & !clear, - ..ZEN1 - }; - let expected = State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - ibrs_fw: true, - ssb: Ssb::Prctl, - vmscape: Vmscape::IbpbExitToUser, - srso: Some(Srso::SafeRet), - ..NONE - }; - let btc_no = ebx8(CPUID_8000_0008_EBX_BTC_NO, 0); - assert_eq!(state(&decide(&btc_no)), expected); - assert_eq!(line(&btc_no, Vuln::Retbleed), "Not affected"); - assert_eq!(line(&ebx8(CPUID_8000_0008_EBX_AMD_SSB_NO, 0), Vuln::SpecStoreBypass), "Not affected"); - let no_ibpb = ebx8(0, CPUID_8000_0008_EBX_AMD_IBPB); - assert_eq!( - state(&decide(&no_ibpb)), - State { - ibpb: false, - retbleed: Retbleed::Unret, - vmscape: Vmscape::None, - srso: Some(Srso::SafeRetUcodeNeeded), - ..expected - } - ); - assert_eq!( - line(&no_ibpb, Vuln::SpectreV2), - "Mitigation: Retpolines; IBRS_FW; STIBP: disabled; RSB filling; PBRSB-eIBRS: Not affected; \ - BHI: Not affected" - ); - assert_eq!(line(&no_ibpb, Vuln::Vmscape), "Vulnerable"); -} - -/// A Rome (Zen2) guest: `early_init_amd` sets no `IBPB_BRTYPE` under a -/// hypervisor (`amd.c:799`), so SRSO is safe RET without its microcode even -/// without SMT (`bugs.c:2687-2703,2735-2736`); `VIRT_SSBD` alone gives SSBD -/// (`common.c:990-992`) and leaves `LS_CFG` unprobed (`amd.c:576-578`). -#[test] -fn a_zen2_guest_has_no_ibpb_brtype() { - let rome = Facts { - signature: 0x0083_0f10, - cpuid_8000_0021_eax: 0, - ..MILAN_GUEST - }; - assert_eq!( - state(&decide(&rome)), - State { - spectre_v2: Some(SpectreV2::Retpoline), - ibpb: true, - retbleed: Retbleed::Unret, - ssb: Ssb::Prctl, - srso: Some(Srso::SafeRetUcodeNeeded), - ..NONE - } - ); - assert_eq!(line(&rome, Vuln::SpecRstackOverflow), "Vulnerable: Safe RET, no microcode"); - let virt_ssbd = Facts { - cpuid_8000_0008_ebx: (rome.cpuid_8000_0008_ebx & !CPUID_8000_0008_EBX_AMD_SSBD) - | CPUID_8000_0008_EBX_VIRT_SSBD, - ..rome - }; - assert_eq!(decide(&virt_ssbd).ssb, Ssb::Prctl); -} - -/// A native family 0x19 with `SRSO_NO` keeps SBPB (`bugs.c:2674-2678`); with -/// `IBPB_BRTYPE` enumerated it is not probed (`amd.c:799`) and has safe RET's -/// microcode (`bugs.c:2672,2733-2734`). -#[test] -fn zen3_by_srso_no_and_an_enumerated_ibpb_brtype() { - let srso_no = Facts { - cpuid_8000_0021_eax: MILAN_GUEST.cpuid_8000_0021_eax | CPUID_8000_0021_EAX_SRSO_NO, - ..native_milan(0x0a00_11d7, true) - }; - assert_eq!( - state(&decide(&srso_no)), - State { sbpb: true, tsa: Some(Tsa::Full), clear_cpu_buf: true, ..NATIVE_MILAN } - ); - assert_eq!(line(&srso_no, Vuln::SpecRstackOverflow), "Not affected"); - let brtype = Facts { - cpuid_8000_0021_eax: MILAN_GUEST.cpuid_8000_0021_eax | CPUID_8000_0021_EAX_IBPB_BRTYPE, - sbpb_write_accepted: None, - ..native_milan(0x0a00_11d7, false) - }; - assert_eq!( - state(&decide(&brtype)), - State { srso: Some(Srso::SafeRet), tsa: Some(Tsa::Full), clear_cpu_buf: true, ..NATIVE_MILAN } - ); -} - -/// A native Turin with SMT: AutoIBRS leaves STIBP selectable -/// (`bugs.c:1564-1567`), and AMD_STIBP_ALWAYS_ON makes it strict -/// (`bugs.c:1575-1577`); `stibp_state` prints it (`bugs.c:3176-3186`). -#[test] -fn autoibrs_leaves_stibp_to_select() { - let turin = Facts { - cpuid_1_ecx: TURIN_GUEST.cpuid_1_ecx & !CPUID_1_ECX_HYPERVISOR, - smt: true, - ..TURIN_GUEST - }; - assert_eq!( - state(&decide(&turin)), - State { - spectre_v2: Some(SpectreV2::Eibrs), - ibpb: true, - stibp: Stibp::StrictPreferred, - ssb: Ssb::Prctl, - sbpb: true, - efer_autoibrs: true, - spec_ctrl: SPEC_CTRL_STIBP, - ..NONE - } - ); - assert_eq!( - line(&turin, Vuln::SpectreV2), - "Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; STIBP: always-on; \ - PBRSB-eIBRS: Not affected; BHI: Not affected" - ); -} - -/// `VERW_CLEAR` enumerated to a guest is TSA's microcode (`bugs.c:2928-2929`, -/// `tsa_strings` 2892). -#[test] -fn a_guest_with_verw_clear_mitigates_tsa() { - let guest = Facts { - cpuid_8000_0021_eax: MILAN_GUEST.cpuid_8000_0021_eax | CPUID_8000_0021_EAX_VERW_CLEAR, - ..MILAN_GUEST - }; - assert_eq!(decide(&guest).tsa, Some(Tsa::Full)); - assert_eq!(line(&guest, Vuln::Tsa), "Mitigation: Clear CPU buffers"); -} - -/// Without `RRSBA` there is nothing to disable (`bugs.c:1728-1731`), so a -/// retpoline guest leaves BHI to retpolines without `RRSBA_DIS_S`. -#[test] -fn a_retpoline_guest_without_rrsba_leaves_bhi_to_retpolines() { - let guest = Facts { - cpuid_1_ecx: T14.cpuid_1_ecx | CPUID_1_ECX_HYPERVISOR, - arch_capabilities: T14.arch_capabilities & !ARCH_CAP_IBRS_ALL, - ..T14 - }; - let d = decide(&guest); - assert_eq!((d.bhi, d.spec_ctrl), (Some(Bhi::Retpoline), 0)); -} - -/// The two probes are AMD's and Hygon's: an Intel family 0x17 or 0x19 is -/// probed for neither. -#[test] -fn only_amd_and_hygon_are_probed() { - for signature in [0x0080_0f00, 0x00a0_0f00] { - let d = decide(&Facts { signature, ..TCG_INTEL }); - assert_eq!(d.spectre_v2, Some(SpectreV2::Retpoline), "{signature:#x}"); - } -} - -/// [`TCG`]'s facts under Intel's name. -const TCG_INTEL: Facts = Facts { vendor: Vendor::from_id(b"GenuineIntel"), ..TCG }; diff --git a/kernel/microcode/Cargo.toml b/kernel/microcode/Cargo.toml index 3b44fb5d7c3..7122c7dbb78 100644 --- a/kernel/microcode/Cargo.toml +++ b/kernel/microcode/Cargo.toml @@ -9,5 +9,3 @@ edition = "2021" license = "MIT OR Apache-2.0" publish = false -[dependencies] -toyos-cpuvuln = { path = "../cpuvuln" } diff --git a/kernel/microcode/src/lib.rs b/kernel/microcode/src/lib.rs index c9afcd03120..e99eb0baacb 100644 --- a/kernel/microcode/src/lib.rs +++ b/kernel/microcode/src/lib.rs @@ -14,8 +14,8 @@ //! the CPU "may or may not reload" an equal one, and loading an update on one //! logical processor loads it on every other in its core. //! -//! Before any file is read, [`loader`] says whether ToyOS has a loader for a -//! CPU's vendor and [`may_load`] whether the CPU loads at all. +//! Before any file is read, [`may_load`] says whether an Intel CPU loads at +//! all; ToyOS has no loader for another vendor's, and its caller reads none. //! //! [`INTEL`] is the file the kernel embeds, as Intel publishes it and pinned //! by its digest in `NOTICE`. @@ -40,8 +40,6 @@ use core::fmt; -use toyos_cpuvuln::Vendor; - #[cfg(test)] mod tests; @@ -94,21 +92,9 @@ pub enum Choice<'a> { NoMatch, } -/// Whether ToyOS has a loader for a CPU of `vendor`: Intel's alone. -pub fn loader(vendor: Vendor) -> Result<(), Unloaded> { - match vendor { - Vendor::Intel => Ok(()), - Vendor::Amd | Vendor::Hygon | Vendor::Centaur | Vendor::Zhaoxin | Vendor::Unknown => { - Err(Unloaded::Vendor(vendor)) - } - } -} - /// Why a CPU loads nothing, said before any file is read. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Unloaded { - /// ToyOS has no loader for this vendor, so nothing of its CPU is read. - Vendor(Vendor), /// CPUID.01H:ECX[31], which a hypervisor sets for its guest (AMD's APM /// Vol. 3, 24594 rev 3.36, §E.3.2): the guest's microcode is the /// hypervisor's. @@ -120,14 +106,13 @@ pub enum Unloaded { impl fmt::Display for Unloaded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Vendor(vendor) => write!(f, "ToyOS has no {vendor:?} microcode loader"), Self::Hypervisor => f.write_str("a hypervisor owns this CPU's microcode"), Self::Disabled => f.write_str("revision 0xffffffff, which no update loads over"), } } } -/// Whether a CPU [`loader`] admits may load, from its +/// Whether an Intel CPU may load, from its /// CPUID.01H:ECX and the revision it reports. A guest loads nothing whatever /// it reports. pub fn may_load(cpuid_1_ecx: u32, found: Revision) -> Result<(), Unloaded> { diff --git a/kernel/microcode/src/tests.rs b/kernel/microcode/src/tests.rs index 2339ba4f922..114f80067d3 100644 --- a/kernel/microcode/src/tests.rs +++ b/kernel/microcode/src/tests.rs @@ -250,14 +250,6 @@ fn the_newest_update_naming_the_cpu_is_chosen_wherever_it_sits() { } } -#[test] -fn a_vendor_toyos_has_no_loader_for_loads_nothing() { - assert_eq!(loader(Vendor::Intel), Ok(())); - for vendor in [Vendor::Amd, Vendor::Hygon, Vendor::Centaur, Vendor::Zhaoxin, Vendor::Unknown] { - assert_eq!(loader(vendor), Err(Unloaded::Vendor(vendor))); - } -} - #[test] fn a_guest_loads_nothing_whatever_revision_it_reports() { for found in [Revision(0xbe), Revision::DISABLED] { diff --git a/kernel/pure/cpu.rs b/kernel/pure/cpu.rs new file mode 100644 index 00000000000..e2a29214d02 --- /dev/null +++ b/kernel/pure/cpu.rs @@ -0,0 +1,364 @@ +//! What an x86 CPU's CPUID words say it is and has: its vendor and +//! signature, whether its HWP performance request is declared and the +//! request, and which of its model-specific counters exist. +//! +//! Intel's facts are the SDM's (325462-093), AMD's its PPRs'. Where neither +//! states a value, the rule here is ToyOS's own and says so. + +#![forbid(unsafe_code)] + +use core::fmt; + +/// CPUID.0's identification string (EBX, EDX, ECX bytes in that order): +/// `"GenuineIntel"` (SDM Vol. 1 §21.1.1) and `"AuthenticAMD"` (PPR 55922, +/// `CPUID_Fn00000000`). Every other string is `Other`: ToyOS reads no other +/// vendor's feature flags. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Vendor { + Intel, + Amd, + Other([u8; 12]), +} + +impl Vendor { + pub const fn from_id(id: &[u8; 12]) -> Self { + match id { + b"GenuineIntel" => Self::Intel, + b"AuthenticAMD" => Self::Amd, + _ => Self::Other(*id), + } + } +} + +impl fmt::Debug for Vendor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Intel => f.write_str("Intel"), + Self::Amd => f.write_str("Amd"), + Self::Other(id) => { + f.write_str("Other(\"")?; + for &b in id { + if b.is_ascii_graphic() || b == b' ' { + write!(f, "{}", b as char)?; + } else { + write!(f, "\\x{b:02x}")?; + } + } + f.write_str("\")") + } + } + } +} + +/// A CPU's vendor and its CPUID.01H:EAX signature decoded by SDM Vol. 1 +/// §21.1.2's DisplayFamily/DisplayModel rule, which PPR 55922's +/// `CPUID_Fn00000001_EAX` states for AMD too. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Ident { + pub vendor: Vendor, + pub family: u32, + pub model: u32, + pub stepping: u32, +} + +impl Ident { + pub const fn new(vendor: Vendor, signature: u32) -> Self { + let base_family = (signature >> 8) & 0xf; + let base_model = (signature >> 4) & 0xf; + let family = if base_family == 0xf { base_family + ((signature >> 20) & 0xff) } else { base_family }; + let model = if base_family == 0x6 || base_family == 0xf { + ((signature >> 16) & 0xf) << 4 | base_model + } else { + base_model + }; + Self { vendor, family, model, stepping: signature & 0xf } + } +} + +/// CPUID.01H:ECX[31]: "Not Used … Allocated for use by software emulation" +/// (SDM Vol. 1, CPUID.01H:ECX), the bit a hypervisor sets for its guest +/// (APM Vol. 3 §E.3.2). +pub const CPUID_1_ECX_HYPERVISOR: u32 = 1 << 31; + +// CPUID.06H and CPUID.(07H,0) bits, SDM Vol. 1 Table 21 and Vol. 3B §17.4.1. +const CPUID_6_EAX_HWP: u32 = 1 << 7; +const CPUID_6_EAX_HWP_NOTIFY: u32 = 1 << 8; +const CPUID_6_EAX_HWP_EPP: u32 = 1 << 10; +const CPUID_6_EAX_HWP_PKG_REQ: u32 = 1 << 11; +const CPUID_6_ECX_APERFMPERF: u32 = 1 << 0; +const CPUID_6_ECX_EPB: u32 = 1 << 3; +const CPUID_7_0_EDX_HYBRID: u32 = 1 << 15; + +/// What one CPU says about its HWP, read on that CPU; a leaf CPUID.0 does +/// not reach reads as zero. +#[derive(Clone, Copy, Debug)] +pub struct HwpFacts { + pub vendor: Vendor, + pub cpuid_6_eax: u32, + pub cpuid_6_ecx: u32, + pub cpuid_7_0_edx: u32, +} + +/// A CPU whose request is declared. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Hwp { + /// `IA32_HWP_INTERRUPT` exists (CPUID.06H:EAX[8]); it is `#GP` where not. + pub notify: bool, +} + +/// Why a CPU's request is not declared, and its performance state stays what +/// firmware left. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HwpRefusal { + NoHwp, + NotIntel, + NoEpp, + NoPackageRequest, + NoEnergyPerfBias, + Hybrid, +} + +impl HwpRefusal { + pub const fn reason(self) -> &'static str { + match self { + Self::NoHwp => "no HWP (CPUID.06H:EAX[7])", + Self::NotIntel => "HWP's CPUID bits on a CPU that is not Intel's, where SDM Vol. 1 §21.1.1 gives them no meaning", + Self::NoEpp => "HWP without an energy/performance preference (CPUID.06H:EAX[10])", + Self::NoPackageRequest => "HWP without a package request (CPUID.06H:EAX[11])", + Self::NoEnergyPerfBias => "no energy/performance bias (CPUID.06H:ECX[3])", + Self::Hybrid => "a hybrid CPU (CPUID.(07H,0):EDX[15]), which no bench ToyOS has can measure the declaration on", + } + } +} + +/// **All or nothing**: a CPU lacking one register the declaration writes gets +/// none of them, so no reader takes firmware's value of one for the +/// declaration's. +pub fn hwp(facts: &HwpFacts) -> Result { + let eax = facts.cpuid_6_eax; + if eax & CPUID_6_EAX_HWP == 0 { + Err(HwpRefusal::NoHwp) + } else if facts.vendor != Vendor::Intel { + Err(HwpRefusal::NotIntel) + } else if eax & CPUID_6_EAX_HWP_EPP == 0 { + Err(HwpRefusal::NoEpp) + } else if eax & CPUID_6_EAX_HWP_PKG_REQ == 0 { + Err(HwpRefusal::NoPackageRequest) + } else if facts.cpuid_6_ecx & CPUID_6_ECX_EPB == 0 { + Err(HwpRefusal::NoEnergyPerfBias) + } else if facts.cpuid_7_0_edx & CPUID_7_0_EDX_HYBRID != 0 { + Err(HwpRefusal::Hybrid) + } else { + Ok(Hwp { notify: eax & CPUID_6_EAX_HWP_NOTIFY != 0 }) + } +} + +/// `Energy_Performance_Preference`: 80H, §17.4.4.1's default and the middle +/// of its scale. +pub const HWP_EPP: u64 = 0x80; + +/// `IA32_HWP_REQUEST` for a CPU whose `IA32_HWP_CAPABILITIES` reads +/// `capabilities`: SDM Vol. 3B §17.4.4.1's defaults, the minimum +/// `Lowest_Performance` and the maximum `Highest_Performance`, both static +/// (§17.4.3), with [`HWP_EPP`]; no desired level, no activity window and no +/// package control, so the CPU chooses between the two levels itself. +pub const fn hwp_request(capabilities: u64) -> u64 { + let highest = capabilities & 0xff; + let lowest = (capabilities >> 24) & 0xff; + lowest | highest << 8 | HWP_EPP << 24 +} + +/// What one CPU says about its counters, read on that CPU. +#[derive(Clone, Copy, Debug)] +pub struct CounterFacts { + pub vendor: Vendor, + /// CPUID.01H:EAX. + pub signature: u32, + pub cpuid_1_ecx: u32, + /// 0 unless CPUID.0 reaches leaf 6. + pub cpuid_6_ecx: u32, +} + +/// The counters a CPU has: a counter it lacks is `#GP` to read, and the +/// kernel has no fault fixup. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Counters { + /// `IA32_APERF` and `IA32_MPERF`. + pub aperf_mperf: bool, + /// `MSR_SMI_COUNT` (34H). + pub smi: bool, +} + +pub fn counters(facts: &CounterFacts) -> Counters { + let documented = matches!(facts.vendor, Vendor::Intel | Vendor::Amd); + Counters { + // SDM Vol. 1 CPUID.06H:ECX[0]; PPR 55922 `CPUID_Fn00000006_ECX[EffFreq]`. + aperf_mperf: documented && facts.cpuid_6_ecx & CPUID_6_ECX_APERFMPERF != 0, + // ToyOS's rule: under a hypervisor 34H is its emulation, not the + // firmware's count it is read for. + smi: counts_smis(&Ident::new(facts.vendor, facts.signature)) + && facts.cpuid_1_ecx & CPUID_1_ECX_HYPERVISOR == 0, + } +} + +/// The Intel family 6 DisplayModels a section of SDM Vol. 4 names as +/// supporting a table that carries `MSR_SMI_COUNT`: Table 2-6 (§2.4-§2.7), +/// 2-15 (§2.8-§2.10), 2-20 (§2.11-§2.17) and 2-61 (§2.18). A model Table 2-1 +/// lists and no section names is left out: admitting one wrongly is a `#GP` +/// at bring-up, leaving one out loses only its count. AMD documents no 34H. +const SMI_COUNT_MODELS: [u32; 57] = [ + // Table 2-6. + 0x37, 0x4a, 0x4d, 0x5a, 0x5d, 0x4c, 0x5c, 0x7a, 0x86, 0x96, 0x9c, + // Table 2-15. + 0x1a, 0x1e, 0x1f, 0x2e, 0x25, 0x2c, 0x2f, + // Table 2-20. + 0x2a, 0x2d, 0x3a, 0x3e, 0x3c, 0x45, 0x46, 0x3f, 0x3d, 0x47, 0x4f, 0x56, + 0x4e, 0x5e, 0x55, 0x8e, 0x9e, 0x66, 0xa5, 0xa6, 0x7e, 0x8c, 0x8d, 0x6a, + 0x6c, 0x97, 0x9a, 0xba, 0xb7, 0xbf, 0x8f, 0xcf, 0xaa, 0xad, 0xae, 0xaf, 0xbd, + // Table 2-61. + 0x57, 0x85, +]; + +fn counts_smis(id: &Ident) -> bool { + id.vendor == Vendor::Intel && id.family == 6 && SMI_COUNT_MODELS.contains(&id.model) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The T14's `control_regs:` line at its own boot: `hwp_capabilities=0x0104182a`, + /// Highest 2AH, Most_Efficient 04H, Lowest 01H. + #[test] + fn the_t14s_request_is_the_sdms_defaults_over_its_capabilities() { + assert_eq!(hwp_request(0x0104_182a), 0x8000_2a01); + } + + /// Each field where §17.4.4.1's Figure 17-6 puts it, from the + /// capabilities' fields Figure 17-5 names, and from no other bit. + #[test] + fn the_request_is_lowest_highest_and_the_preference() { + assert_eq!(hwp_request(0), 0x8000_0000); + assert_eq!(hwp_request(0x2a), 0x8000_2a00); + assert_eq!(hwp_request(0x04 << 24), 0x8000_0004); + assert_eq!(hwp_request(0x00ff_ff00), 0x8000_0000); + assert_eq!(hwp_request(u64::MAX), 0x8000_ffff); + } + + /// The T14's leaf 6 and leaf 7 by the bits its own `control_regs:` lines + /// prove: HWP, its interrupt, EPP, the package request, EPB, not hybrid. + fn t14() -> HwpFacts { + HwpFacts { + vendor: Vendor::Intel, + cpuid_6_eax: CPUID_6_EAX_HWP | CPUID_6_EAX_HWP_NOTIFY | CPUID_6_EAX_HWP_EPP | CPUID_6_EAX_HWP_PKG_REQ, + cpuid_6_ecx: CPUID_6_ECX_EPB | CPUID_6_ECX_APERFMPERF, + cpuid_7_0_edx: 0, + } + } + + #[test] + fn the_t14_is_declared_with_its_notification() { + assert_eq!(hwp(&t14()), Ok(Hwp { notify: true })); + let quiet = HwpFacts { cpuid_6_eax: t14().cpuid_6_eax & !CPUID_6_EAX_HWP_NOTIFY, ..t14() }; + assert_eq!(hwp(&quiet), Ok(Hwp { notify: false })); + } + + #[test] + fn a_missing_register_refuses_the_whole_request_by_name() { + let without = |eax: u32, ecx: u32| { + hwp(&HwpFacts { cpuid_6_eax: t14().cpuid_6_eax & !eax, cpuid_6_ecx: t14().cpuid_6_ecx & !ecx, ..t14() }) + }; + assert_eq!(without(1 << 7, 0), Err(HwpRefusal::NoHwp)); + assert_eq!(without(1 << 10, 0), Err(HwpRefusal::NoEpp)); + assert_eq!(without(1 << 11, 0), Err(HwpRefusal::NoPackageRequest)); + assert_eq!(without(0, 1 << 3), Err(HwpRefusal::NoEnergyPerfBias)); + assert_eq!(hwp(&HwpFacts { cpuid_7_0_edx: 1 << 15, ..t14() }), Err(HwpRefusal::Hybrid)); + } + + /// PPR 55922's `CPUID_Fn00000006_EAX` reads 4 (ARAT alone), so the AMD + /// bench has no HWP; and on any vendor but Intel the bits mean nothing. + #[test] + fn only_intel_declares_hwp() { + assert_eq!(hwp(&HwpFacts { vendor: Vendor::Amd, cpuid_6_eax: 0x4, cpuid_6_ecx: 0x1, cpuid_7_0_edx: 0 }), Err(HwpRefusal::NoHwp)); + assert_eq!(hwp(&HwpFacts { vendor: Vendor::Amd, ..t14() }), Err(HwpRefusal::NotIntel)); + assert_eq!(hwp(&HwpFacts { vendor: Vendor::Other(*b"HygonGenuine"), ..t14() }), Err(HwpRefusal::NotIntel)); + } + + const T14_SIGNATURE: u32 = 0x0008_06c1; + + fn intel(signature: u32) -> CounterFacts { + CounterFacts { vendor: Vendor::Intel, signature, cpuid_1_ecx: 0, cpuid_6_ecx: CPUID_6_ECX_APERFMPERF } + } + + /// 06_8CH, which §2.17 names as supporting Table 2-20. + #[test] + fn the_t14_counts_smis_and_has_aperf_and_mperf() { + assert_eq!(counters(&intel(T14_SIGNATURE)), Counters { aperf_mperf: true, smi: true }); + } + + /// The AMD bench, Family 17h Model 60h: PPR 55922 gives it `EffFreq` and + /// no `MSR0000_0034`. + #[test] + fn the_amd_bench_has_aperf_and_mperf_and_no_smi_count() { + let renoir = CounterFacts { vendor: Vendor::Amd, signature: 0x0086_0f01, cpuid_1_ecx: 0, cpuid_6_ecx: 1 }; + assert_eq!(counters(&renoir), Counters { aperf_mperf: true, smi: false }); + } + + #[test] + fn a_vendor_neither_intel_nor_amd_has_neither() { + let other = CounterFacts { vendor: Vendor::Other(*b"CentaurHauls"), ..intel(T14_SIGNATURE) }; + assert_eq!(counters(&other), Counters { aperf_mperf: false, smi: false }); + } + + #[test] + fn a_guest_counts_no_smis() { + let guest = CounterFacts { cpuid_1_ecx: CPUID_1_ECX_HYPERVISOR, ..intel(T14_SIGNATURE) }; + assert_eq!(counters(&guest), Counters { aperf_mperf: true, smi: false }); + } + + /// Family 6 signatures with the given DisplayModel, stepping 1. + fn model(m: u32) -> u32 { + 0x0000_0601 | (m >> 4) << 16 | (m & 0xf) << 4 + } + + /// One model from each table's sections, and every model Table 2-1 lists + /// that no section names (`smi-list.md` in #915's evidence). + #[test] + fn a_model_counts_smis_exactly_where_vol_4_names_it() { + for m in [0x37, 0x4c, 0x9c, 0x1a, 0x2f, 0x2a, 0x3a, 0x66, 0xbd, 0x57, 0x85] { + assert!(counters(&intel(model(m))).smi, "06_{m:02X}"); + } + for m in [0x5f, 0xa7, 0xbe, 0xb6, 0xb5, 0xc5, 0xc6, 0xcc, 0xd5, 0xdd] { + assert!(!counters(&intel(model(m))).smi, "06_{m:02X}"); + } + } + + #[test] + fn only_intel_family_6_counts_smis() { + assert!(!counters(&intel(0x0000_0f41)).smi); + assert!(!counters(&CounterFacts { vendor: Vendor::Amd, ..intel(T14_SIGNATURE) }).smi); + } + + /// §21.1.2: the extended model joins family 6 and 0FH, the extended + /// family 0FH alone. + #[test] + fn the_signature_decodes_by_the_display_rule() { + let t14 = Ident::new(Vendor::Intel, T14_SIGNATURE); + assert_eq!((t14.family, t14.model, t14.stepping), (6, 0x8c, 1)); + let renoir = Ident::new(Vendor::Amd, 0x0086_0f01); + assert_eq!((renoir.family, renoir.model, renoir.stepping), (0x17, 0x60, 1)); + let p4 = Ident::new(Vendor::Intel, 0x0000_0f41); + assert_eq!((p4.family, p4.model), (0xf, 4)); + let pentium = Ident::new(Vendor::Intel, 0x0001_0543); + assert_eq!((pentium.family, pentium.model), (5, 4)); + } + + #[test] + fn another_vendors_string_is_kept() { + let id = *b" Shanghai "; + assert_eq!(Vendor::from_id(&id), Vendor::Other(id)); + assert_eq!(std::format!("{:?}", Vendor::from_id(&id)), "Other(\" Shanghai \")"); + assert_eq!(Vendor::from_id(b"GenuineIntel"), Vendor::Intel); + assert_eq!(Vendor::from_id(b"AuthenticAMD"), Vendor::Amd); + } +} diff --git a/kernel/pure/lib.rs b/kernel/pure/lib.rs index f2ca5bdb127..f076c3c74df 100644 --- a/kernel/pure/lib.rs +++ b/kernel/pure/lib.rs @@ -1,14 +1,6 @@ -//! What the kernel decides without touching the machine: the scheduler core -//! ([`sched`]), the process and thread lifecycle ([`proclife`]), which PCID an -//! address space is handed ([`pcid`]), what type the range registers give a -//! range ([`mtrr`]), what a pipe's ends are told of each other ([`pipe`]), what -//! a CPU waiting on the boot processor's write to `SMI_CMD` decides -//! ([`bootwrite`]), what the clock computes from counter ticks ([`clock`]), -//! which number a kernel volume's directory answers ([`dirnodes`]), which -//! number a USB pointer's records carry ([`pointers`]) and which -//! state components a CPU's `XCR0` enables ([`xsave`]). -//! The kernel binary links it; the host runs -//! its tests, because none of it reads a register, a clock or a kernel lock. +//! What the kernel decides without touching the machine. The kernel binary +//! links it; the host runs its tests, because none of it reads a register, a +//! clock or a kernel lock. #![no_std] @@ -19,6 +11,7 @@ extern crate std; pub mod bootwrite; pub mod clock; +pub mod cpu; pub mod dirnodes; pub mod mtrr; pub mod pcid; diff --git a/kernel/pure/xsave.rs b/kernel/pure/xsave.rs index 4e264144cc7..3ddffc75a9a 100644 --- a/kernel/pure/xsave.rs +++ b/kernel/pure/xsave.rs @@ -7,13 +7,16 @@ //! components each want a decision of their own. //! //! The save is the best [`Form`] the CPU offers soundly: `XSAVES`, then -//! `XSAVEC`, then `XSAVEOPT`; a CPU without `XSAVEOPT` is refused. The two +//! `XSAVEC`, then `XSAVEOPT`; a CPU without `XSAVEOPT` is refused, and one +//! AMD erratum 1386 may name ([`erratum_1386`]) never saves with `XSAVES`. The two //! compacted forms size the area by the SDM's compaction rule over the CPU's //! own component sizes, which must equal the CPU's `CPUID.(0DH,1):EBX`; the //! standard form takes `CPUID.(0DH,0):EBX`. Nothing here addresses a component. #![forbid(unsafe_code)] +use crate::cpu::{Ident, Vendor}; + /// `XCR0` bits, SDM Vol. 1 §13.1. pub const X87: u64 = 1 << 0; pub const SSE: u64 = 1 << 1; @@ -33,9 +36,8 @@ pub const AVX512: u64 = OPMASK | ZMM_HI256 | HI16_ZMM; pub const COMPACTED_BASE: u32 = 576; /// The largest standard-form area any accepted CPU may report: the end of -/// Hi16_ZMM (`0xa88` on the T14 with PKRU beside it, -/// `kernel/cpuvuln/fixtures/t14/cpuid.txt`, less PKRU's 8 bytes). The -/// kernel's standard-form first-entry image is this long. +/// Hi16_ZMM, which the T14 reports for `XCR0` `0xe7`. The kernel's +/// standard-form first-entry image is this long. pub const IMAGE_BYTES: usize = 2688; /// `CPUID.(0DH,1):EAX` bits, SDM Vol. 2A Table 3-8. @@ -149,15 +151,14 @@ pub fn xcr0(facts: &Facts) -> Result { } } -/// The save this CPU takes. `erratum_1386` is `toyos_cpuvuln::erratum_1386`'s -/// verdict on it: a CPU it names saves with `XSAVEC`, which saves what -/// `XSAVES` would while `IA32_XSS` is 0. -pub fn form(facts: &Facts, erratum_1386: bool) -> Result { +/// The save the CPU `id` takes. One [`erratum_1386`] names saves with +/// `XSAVEC`, which saves what `XSAVES` would while `IA32_XSS` is 0. +pub fn form(facts: &Facts, id: &Ident) -> Result { let d1 = facts.cpuid_d1_eax; if d1 & D1_XSAVEOPT == 0 { return Err(Refusal::NoXsaveopt); } - Ok(if d1 & D1_XSAVES != 0 && !erratum_1386 { + Ok(if d1 & D1_XSAVES != 0 && !erratum_1386(id) { Form::Xsaves } else if d1 & D1_XSAVEC != 0 { Form::Xsavec @@ -166,6 +167,29 @@ pub fn form(facts: &Facts, erratum_1386: bool) -> Result { }) } +/// `(family, model, stepping)` of the revisions a public AMD revision guide +/// lists without erratum 1386: 55449 rev 1.21's ZP-B1 and PiR-B2, and 57095 +/// rev 1.01's Genoa-B1. +const CLEARED_1386: [(u32, u32, u32); 3] = [(0x17, 0x01, 0x1), (0x17, 0x08, 0x2), (0x19, 0x11, 0x1)]; + +/// Whether AMD erratum 1386, "XSAVES Instruction May Fail to Save XMM +/// Registers to the Provided State Save Area" ("No fix planned"), may name the +/// CPU: AMD's guides list it against ZP-B2 (55449), Rome-B0 (56323) and +/// Milan-B1 and -B2 (56683). Every other AMD family 17h and 19h part, and a +/// family 18h part of any vendor but Intel, is taken as named unless a guide +/// clears it: a wrong yes costs only `XSAVES`' modified optimisation, a wrong +/// no hands a thread an older XMM value of its own. +/// `issues/amd-erratum-1386-is-assumed-where-no-public-amd-revision-guide-clears-a-part.md` +/// owns that assumption. +pub fn erratum_1386(id: &Ident) -> bool { + let named = match id.vendor { + Vendor::Amd => (0x17..=0x19).contains(&id.family), + Vendor::Other(_) => id.family == 0x18, + Vendor::Intel => false, + }; + named && !CLEARED_1386.contains(&(id.family, id.model, id.stepping)) +} + /// The bytes one standard-form save writes, from `CPUID.(0DH,0):EBX` read once /// the declared `XCR0` is in place, and no more than [`IMAGE_BYTES`]. pub fn standard_bytes(cpuid_d0_ebx: u32) -> Result { @@ -200,30 +224,49 @@ mod tests { use super::*; use std::vec::Vec; - /// `(leaf, subleaf) -> [eax, ebx, ecx, edx]` off the T14's recorded CPUID, - /// whose provenance is `kernel/cpuvuln/fixtures/t14/SOURCE`. - fn t14(leaf: u32, subleaf: u32) -> [u32; 4] { - let rows: Vec<[u32; 6]> = include_str!("../cpuvuln/fixtures/t14/cpuid.txt") - .lines() - .map(|line| { - let words: Vec = - line.split_whitespace().map(|w| u32::from_str_radix(w, 16).expect("a hex word")).collect(); - words.try_into().expect("six words a line") + /// The T14's own `fpu:` line at its boot, with `XCR0` at `0xe7` and + /// `IA32_XSS` at 0 (`control_regs::self_check` asserts both). + const T14_FPU: &str = "fpu: cpu0 cpuid.1.eax=0x806c1 xsave=1 osxsave=1 xcr0=0xe7 cpuid.d.0=(0x2e7,2688,2696) \ + cpuid.d.1=(0xf,2432) cpuid.d.2..9.(eax,ebx,ecx)=[(256, 576, 0), (0, 0, 0), (0, 0, 0), (64, 1088, 0), \ + (512, 1152, 0), (1024, 1664, 0), (0, 0, 0), (0, 0, 0)] save=xsaves area=2432"; + + /// Every number in [`T14_FPU`] after `key`, in order, to the next field. + fn t14(key: &str) -> Vec { + let rest = T14_FPU.split_once(key).expect("a field the line carries").1; + let field = rest.split(" save=").next().expect("the line ends in save="); + let field = if key.starts_with("cpuid.d.2") { field } else { field.split(' ').next().expect("a word") }; + field + .split(|c: char| !c.is_ascii_hexdigit() && c != 'x') + .filter(|w| !w.is_empty()) + .map(|w| match w.strip_prefix("0x") { + Some(hex) => u32::from_str_radix(hex, 16).expect("a hex number"), + None => w.parse().expect("a decimal number"), }) - .collect(); - let row = rows.iter().find(|r| r[0] == leaf && r[1] == subleaf).expect("the leaf is recorded"); - [row[2], row[3], row[4], row[5]] + .collect() } + /// `CPUID.(0DH,0):EDX`, `XCR0`'s upper half, is not on the line; no + /// component this kernel enables lies there. fn t14_facts() -> Facts { Facts { - cpuid_1_ecx: t14(1, 0)[2], - cpuid_d0_eax: t14(0xd, 0)[0], - cpuid_d0_edx: t14(0xd, 0)[3], - cpuid_d1_eax: t14(0xd, 1)[0], + cpuid_1_ecx: if t14("xsave=") == [1] { 1 << 26 } else { 0 }, + cpuid_d0_eax: t14("cpuid.d.0=")[0], + cpuid_d0_edx: 0, + cpuid_d1_eax: t14("cpuid.d.1=")[0], } } + /// Component `i`'s `(size, offset, ecx)` as the T14 reported it. + fn t14_component(i: u32) -> Component { + let words = t14("cpuid.d.2..9.(eax,ebx,ecx)="); + let at = (i as usize - 2) * 3; + Component::from_cpuid(words[at], words[at + 2]) + } + + fn t14_ident() -> Ident { + Ident::new(Vendor::Intel, t14("cpuid.1.eax=")[0]) + } + /// The AMD bench, a Ryzen 7 4800U: leaf 0DH as this kernel logged it /// there (`cpuid.d.0=(0x207,…)`, `cpuid.d.1.eax=0xf`). fn amd_bench() -> Facts { @@ -251,19 +294,19 @@ mod tests { #[test] fn the_t14_saves_with_xsaves() { - assert_eq!(form(&t14_facts(), false), Ok(Form::Xsaves)); + assert_eq!(form(&t14_facts(), &t14_ident()), Ok(Form::Xsaves)); } /// Its CPUID enumerates `XSAVES`; the erratum is what moves it. #[test] fn the_amd_bench_named_by_the_erratum_saves_with_xsavec() { assert_eq!(amd_bench().cpuid_d1_eax & D1_XSAVES, D1_XSAVES); - assert_eq!(form(&amd_bench(), true), Ok(Form::Xsavec)); + assert_eq!(form(&amd_bench(), &Ident::new(Vendor::Amd, 0x0086_0f01)), Ok(Form::Xsavec)); } #[test] fn tcg_saves_with_xsaveopt() { - assert_eq!(form(&tcg(), false), Ok(Form::Xsaveopt)); + assert_eq!(form(&tcg(), &Ident::new(Vendor::Amd, 0x0006_0fb1)), Ok(Form::Xsaveopt)); } #[test] @@ -275,13 +318,58 @@ mod tests { #[test] fn a_cpu_without_xsaveopt_is_refused_whatever_else_it_offers() { let facts = Facts { cpuid_d1_eax: D1_XSAVES | D1_XSAVEC, ..t14_facts() }; - assert_eq!(form(&facts, false), Err(Refusal::NoXsaveopt)); + assert_eq!(form(&facts, &t14_ident()), Err(Refusal::NoXsaveopt)); } #[test] fn a_part_the_erratum_names_without_xsavec_saves_with_xsaveopt() { let facts = Facts { cpuid_d1_eax: D1_XSAVEOPT | D1_XSAVES, ..amd_bench() }; - assert_eq!(form(&facts, true), Ok(Form::Xsaveopt)); + assert_eq!(form(&facts, &Ident::new(Vendor::Amd, 0x0086_0f01)), Ok(Form::Xsaveopt)); + } + + fn amd(signature: u32) -> bool { + erratum_1386(&Ident::new(Vendor::Amd, signature)) + } + + /// Each revision AMD's guides list it against, by the raw signature + /// their CPUID tables give. + #[test] + fn each_revision_amds_guides_list_the_erratum_against_is_named() { + for signature in [0x0080_0f12, 0x0083_0f10, 0x00a0_0f11, 0x00a0_0f12] { + assert!(amd(signature), "{signature:#x}"); + } + } + + /// ZP-B1 and PiR-B2 (55449), and Genoa-B1, whose guide (57095) does not + /// list it. + #[test] + fn the_revisions_a_guide_lists_without_it_are_cleared() { + for signature in [0x0080_0f11, 0x0080_0f82, 0x00a1_0f11] { + assert!(!amd(signature), "{signature:#x}"); + } + } + + /// The AMD bench, family 17h model 60h, which no public guide covers, + /// every other family 17h and 19h model, and family 18h of any vendor but + /// Intel. + #[test] + fn a_part_no_guide_clears_is_named() { + for signature in [0x0086_0f01, 0x0080_0ff0, 0x008b_0f00, 0x00a1_0f12, 0x00af_0ff0] { + assert!(amd(signature), "{signature:#x}"); + } + assert!(amd(0x0090_0f02)); + assert!(erratum_1386(&Ident::new(Vendor::Other(*b"HygonGenuine"), 0x0090_0f02))); + } + + /// The T14, TCG's `qemu64` (AMD family 0FH), family 1AH, Intel at any + /// signature, and another vendor's family 17h. + #[test] + fn no_other_part_is_named() { + assert!(!erratum_1386(&t14_ident())); + assert!(!erratum_1386(&Ident::new(Vendor::Intel, 0x0080_0f12))); + assert!(!amd(0x0006_0fb1)); + assert!(!amd(0x00b0_0f20)); + assert!(!erratum_1386(&Ident::new(Vendor::Other(*b"CentaurHauls"), 0x0080_0f12))); } #[test] @@ -308,8 +396,9 @@ mod tests { #[test] fn a_standard_area_past_the_first_entry_image_is_refused() { - // The T14's own figure with PKRU in XCR0, which this kernel never declares. - let with_pkru = t14(0xd, 0)[1]; + // The T14's own figure with every component it supports, PKRU among + // them, which this kernel never declares. + let with_pkru = t14("cpuid.d.0=")[2]; assert_eq!(with_pkru, IMAGE_BYTES as u32 + 8); assert_eq!(standard_bytes(with_pkru), Err(Refusal::AreaTooLarge(with_pkru))); } @@ -328,13 +417,25 @@ mod tests { Component { size, aligned: false } } - /// The oracle: the T14's recorded `CPUID.(0DH,1):EBX`, `0x998`, read under - /// its Linux with `XCR0` = `0x2e7` and, by that kernel's configuration, - /// CET_U (bit 11) in `IA32_XSS`, is what the SDM's rule gives that set. + /// The oracle: the T14's own `CPUID.(0DH,1):EBX` at `XCR0` `0xe7` and + /// `IA32_XSS` 0, 2432, is what the SDM's rule gives over the sizes it + /// reported, and over the SDM's own. + #[test] + fn the_sdm_rule_gives_the_t14s_reported_compacted_size() { + let reported = t14("cpuid.d.1=")[1]; + assert_eq!(t14("xcr0=")[0], 0xe7); + assert_eq!(reported, 2432); + assert_eq!(compacted_bytes(0xe7, t14_component, reported), Ok(2432)); + assert_eq!(compacted_bytes(0xe7, sdm, reported), Ok(2432)); + } + + /// Hi16_ZMM's end at the T14's offsets is the standard area it reports + /// for `0xe7`, and the image's length. #[test] - fn the_sdm_rule_gives_the_t14s_recorded_compacted_size() { - assert_eq!(t14(0xd, 1)[1], 0x998); - assert_eq!(compacted_bytes(0x2e7 | 1 << 11, sdm, t14(0xd, 1)[1]), Ok(0x998)); + fn the_first_entry_image_is_the_t14s_standard_area() { + let hi16_zmm = t14_component(7); + assert_eq!(t14("cpuid.d.2..9.(eax,ebx,ecx)=")[5 * 3 + 1] + hi16_zmm.size, t14("cpuid.d.0=")[1]); + assert_eq!(t14("cpuid.d.0=")[1] as usize, IMAGE_BYTES); } #[test] diff --git a/kernel/src/arch/x86_64/control_regs.rs b/kernel/src/arch/x86_64/control_regs.rs index f228321d3ba..8ab158cad14 100644 --- a/kernel/src/arch/x86_64/control_regs.rs +++ b/kernel/src/arch/x86_64/control_regs.rs @@ -12,8 +12,8 @@ //! ([`Prot`](crate::mm::policy::Prot)). //! //! The performance request is HWP's: `IA32_HWP_INTERRUPT` where it exists, -//! `IA32_PM_ENABLE`, `IA32_HWP_REQUEST` as `toyos_cpuvuln::hwp_request` derives -//! it from this CPU's own registers, `IA32_HWP_REQUEST_PKG` and +//! `IA32_PM_ENABLE`, `IA32_HWP_REQUEST` as `kernel::cpu::hwp_request` derives +//! it from this CPU's own capabilities, `IA32_HWP_REQUEST_PKG` and //! `IA32_ENERGY_PERF_BIAS`, on a machine whose CPUs have every one of them, //! and none on one that does not: refused by name once, firmware's values //! standing. `IA32_MISC_ENABLE`'s turbo bit is not declared: that register's @@ -22,7 +22,7 @@ use core::sync::atomic::{AtomicU64, AtomicU8, Ordering}; -use toyos_cpuvuln::HwpFacts; +use kernel::cpu::HwpFacts; use super::cpu; use crate::log; @@ -110,7 +110,6 @@ static DECLARED_APIC: AtomicU8 = AtomicU8::new(0); /// The performance request's registers, SDM Vol. 4. mod hwp { - pub const PLATFORM_INFO: u32 = 0xCE; pub const ENERGY_PERF_BIAS: u32 = 0x1B0; pub const PM_ENABLE: u32 = 0x770; pub const HWP_CAPABILITIES: u32 = 0x771; @@ -129,11 +128,13 @@ const HWP_INTERRUPT: u64 = 0; /// `IA32_HWP_REQUEST_PKG` on every CPU: the widest range at the request's own /// preference. Inert, since no CPU's request sets package control, and /// declared so that firmware does not decide it either. -const HWP_REQUEST_PKG: u64 = 0x01 | 0xff << 8 | toyos_cpuvuln::HWP_EPP << 24; +const HWP_REQUEST_PKG: u64 = 0x01 | 0xff << 8 | kernel::cpu::HWP_EPP << 24; -/// `IA32_ENERGY_PERF_BIAS` on every CPU: Linux's `ENERGY_PERF_BIAS_NORMAL` -/// (`arch/x86/include/asm/msr-index.h:858`). -const ENERGY_PERF_BIAS: u64 = 6; +/// `IA32_ENERGY_PERF_BIAS` on every CPU: 7, which SDM Vol. 3B §17.3.4 says +/// "roughly translates into a hint to balance performance with energy +/// consumption". ToyOS's rule until it has a power policy: each hint at its +/// scale's documented balance point, as [`kernel::cpu::HWP_EPP`] is. +const ENERGY_PERF_BIAS: u64 = 7; /// `CR0` on every CPU: `TS` stays clear because lazy FP switching would leak /// a register file across `#NM`; `AM` stays clear because Ring 3 has no `#AC` path. @@ -208,11 +209,10 @@ const HWP_REFUSED: u8 = 2; /// Whether this CPU gets a performance request, from CPUID alone, and whether /// it has `IA32_HWP_INTERRUPT`: the machine's answer, a CPU that disagrees /// named. -fn hwp_declared(cpu_id: u32) -> Option { +fn hwp_declared(cpu_id: u32) -> Option { let (cpuid_6_eax, cpuid_6_ecx) = cpu::leaf_6(); - let verdict = toyos_cpuvuln::hwp(&HwpFacts { + let verdict = kernel::cpu::hwp(&HwpFacts { vendor: cpu::vendor(), - signature: cpu::cpuid(1, 0).0, cpuid_6_eax, cpuid_6_ecx, cpuid_7_0_edx: cpu::leaf_7().3, @@ -281,8 +281,7 @@ pub fn init(cpu_id: u32) { /// The interrupt is cleared and HWP enabled before any other HWP register is /// touched, the request's input included: SDM Vol. 3B §17.4.2 (253669-093US), /// "Additional MSRs associated with HWP may only be accessed after HWP is -/// enabled, with the exception of IA32_HWP_INTERRUPT and MSR_PPERF", and -/// `intel_pstate_hwp_enable`'s order. +/// enabled, with the exception of IA32_HWP_INTERRUPT and MSR_PPERF". pub fn init_performance(cpu_id: u32) { let Some(hwp) = hwp_declared(cpu_id) else { return }; // SAFETY: `hwp_declared` admitted this CPU only where CPUID enumerates HWP, @@ -294,7 +293,7 @@ pub fn init_performance(cpu_id: u32) { } cpu::wrmsr(hwp::PM_ENABLE, PM_ENABLE); } - let request = toyos_cpuvuln::hwp_request(cpu::rdmsr(hwp::HWP_CAPABILITIES), cpu::rdmsr(hwp::PLATFORM_INFO)); + let request = kernel::cpu::hwp_request(cpu::rdmsr(hwp::HWP_CAPABILITIES)); // SAFETY: HWP is enabled, so its registers are live; CPUID enumerated EPP, // the package request and EPB, and every value holds only defined bits. unsafe { @@ -476,7 +475,7 @@ fn xsave_declaration(cpu_id: u32) -> (u64, kernel::xsave::Form) { panic!("control_regs: cpu{cpu_id} cannot run user state: {} ({refusal:?})", refusal.reason()) }; let declared = kernel::xsave::xcr0(&facts).unwrap_or_else(|r| refused(r)); - let form = kernel::xsave::form(&facts, toyos_cpuvuln::erratum_1386(cpu::vendor(), eax1)) + let form = kernel::xsave::form(&facts, &kernel::cpu::Ident::new(cpu::vendor(), eax1)) .unwrap_or_else(|r| refused(r)); match DECLARED_XCR0.compare_exchange(0, declared, Ordering::Release, Ordering::Acquire) { Ok(_) => (declared, form), @@ -599,7 +598,7 @@ fn self_check(cpu_id: u32, declared_cr4: u64, declared_xcr0: u64, form: kernel:: } /// [`self_check`] for the performance request, logged first for the same -/// reason; the line carries the request's two inputs, so a reader can +/// reason; the line carries the request's input, so a reader can /// recompute it. fn hwp_check(cpu_id: u32, request: u64, notify: bool) { let pm_enable = cpu::rdmsr(hwp::PM_ENABLE); @@ -609,7 +608,7 @@ fn hwp_check(cpu_id: u32, request: u64, notify: bool) { let interrupt = notify.then(|| cpu::rdmsr(hwp::HWP_INTERRUPT)); log!( "control_regs: cpu{} pm_enable={} hwp_request={:#010x} hwp_request_pkg={:#010x} epb={} \ - hwp_interrupt={:x?} hwp_capabilities={:#010x} platform_info={:#018x}", + hwp_interrupt={:x?} hwp_capabilities={:#010x}", cpu_id, pm_enable, live, @@ -617,7 +616,6 @@ fn hwp_check(cpu_id: u32, request: u64, notify: bool) { epb, interrupt, cpu::rdmsr(hwp::HWP_CAPABILITIES), - cpu::rdmsr(hwp::PLATFORM_INFO), ); for (name, holds, declared) in [ ("pm_enable", Some(pm_enable), PM_ENABLE), diff --git a/kernel/src/arch/x86_64/counters.rs b/kernel/src/arch/x86_64/counters.rs index b5a26ee40fc..0328dadfc5b 100644 --- a/kernel/src/arch/x86_64/counters.rs +++ b/kernel/src/arch/x86_64/counters.rs @@ -1,10 +1,10 @@ //! Which of this CPU's model-specific counters it reads: decided on the CPU -//! itself at bring-up, by `toyos_cpuvuln::counters` over its own CPUID, and +//! itself at bring-up, by `kernel::cpu::counters` over its own CPUID, and //! the power envelope where `control_regs` declared it. use core::sync::atomic::{AtomicU8, Ordering::Relaxed}; -use toyos_cpuvuln::CounterFacts; +use kernel::cpu::CounterFacts; use super::{cpu, percpu}; use crate::counters::{Firmware, Hardware}; @@ -23,7 +23,7 @@ static ADMITTED: [AtomicU8; MAX_CPUS] = [const { AtomicU8::new(0) }; MAX_CPUS]; pub fn bring_up() { let (signature, _, cpuid_1_ecx, _) = cpu::cpuid(1, 0); let (_, cpuid_6_ecx) = cpu::leaf_6(); - let verdict = toyos_cpuvuln::counters(&CounterFacts { + let verdict = kernel::cpu::counters(&CounterFacts { vendor: cpu::vendor(), signature, cpuid_1_ecx, diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index 2f5dc664c7a..1ed9d6a96b1 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/kernel/src/arch/x86_64/cpu.rs @@ -30,8 +30,9 @@ pub unsafe fn wrmsr(msr: u32, value: u64) { } /// Every earlier load and store done before what follows, a `wrmsr` among it: -/// Linux's `weak_wrmsr_fence`. No `nomem`, so the compiler moves no access -/// across it either. +/// "the sequence MFENCE;LFENCE before the WRMSR" that SDM Vol. 3A §13.12.3 +/// names for a write to an x2APIC register. No `nomem`, so the compiler moves +/// no access across it either. #[inline] pub fn wrmsr_fence() { // SAFETY: two fences; no register, flag or memory changes. @@ -128,17 +129,16 @@ pub fn df_witness(site: &str) { ); } -/// CPUID.0's vendor string, as `toyos_cpuvuln` names it. -pub fn vendor() -> toyos_cpuvuln::Vendor { +/// CPUID.0's vendor string. +pub fn vendor() -> kernel::cpu::Vendor { let (_, ebx, ecx, edx) = cpuid(0, 0); - toyos_cpuvuln::Vendor::from_id(&core::array::from_fn(|i| [ebx, edx, ecx][i / 4].to_le_bytes()[i % 4])) + kernel::cpu::Vendor::from_id(&core::array::from_fn(|i| [ebx, edx, ecx][i / 4].to_le_bytes()[i % 4])) } /// CPUID.6's EAX and ECX, the thermal and power leaf; zero where CPUID.0 -/// does not reach it, as `init_scattered_cpuid_features` reads it: a level -/// past the range's own is no level. +/// does not reach it, for [`leaf_7`]'s reason. pub fn leaf_6() -> (u32, u32) { - if (6..=0xFFFF).contains(&cpuid(0, 0).0) { + if cpuid(0, 0).0 >= 6 { let (eax, _, ecx, _) = cpuid(6, 0); (eax, ecx) } else { diff --git a/kernel/src/arch/x86_64/microcode.rs b/kernel/src/arch/x86_64/microcode.rs index a650b0413e9..0ef4e1c8bd7 100644 --- a/kernel/src/arch/x86_64/microcode.rs +++ b/kernel/src/arch/x86_64/microcode.rs @@ -55,8 +55,8 @@ const UNPUBLISHED: u64 = u64::MAX; pub fn load(cpu_id: u32) { let vendor = cpu::vendor(); let (eax, _, ecx, _) = cpu::cpuid(1, 0); - if let Err(why) = toyos_microcode::loader(vendor) { - log!("microcode: cpu{cpu_id} {vendor:?} signature {eax:#x} loads nothing: {why}"); + if vendor != kernel::cpu::Vendor::Intel { + log!("microcode: cpu{cpu_id} {vendor:?} signature {eax:#x} loads nothing: ToyOS has no {vendor:?} microcode loader"); return; } let found = revision(); diff --git a/tests/metal/t14-linux/SOURCE b/tests/metal/t14-linux/SOURCE index 20e69eb8c43..d59763d1e66 100644 --- a/tests/metal/t14-linux/SOURCE +++ b/tests/metal/t14-linux/SOURCE @@ -12,13 +12,3 @@ beside them. sleep 2; turbostat --quiet --interval 10 --num_iterations 6 --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,TSC_MHz,IRQ,SMI,CPU%c1,CPU%c6,CPU%c7,CoreTmp,PkgTmp,Pkg%pc2,Pkg%pc6,Pk%pc10,PkgWatt,CorWatt; wait` - - hwp-request.txt IA32_HWP_REQUEST (0x774), the same on all eight CPUs, - read as root through /dev/cpu/N/msr under the same - Ubuntu with intel_pstate `active`, the `powersave` - governor and EPP `balance_performance`; recorded in - the message of commit 868378b87 (#568, branch - `wt/toyos-llvmbar`): "IA32_HWP_REQUEST 0x80002a04 on - all eight CPUs (min 4, max 42, desired 0, EPP 128)". - `counters_on_metal` holds every CPU's boot - `hwp_request` to it. diff --git a/tests/metal/t14-linux/hwp-request.txt b/tests/metal/t14-linux/hwp-request.txt deleted file mode 100644 index 3e9991a79bb..00000000000 --- a/tests/metal/t14-linux/hwp-request.txt +++ /dev/null @@ -1 +0,0 @@ -0x80002a04 diff --git a/tests/toyos.rs b/tests/toyos.rs index a02e7ac947e..3aa92da16e3 100644 --- a/tests/toyos.rs +++ b/tests/toyos.rs @@ -4975,9 +4975,8 @@ const SMI_SPAN_NS: u64 = 4_444_000_000; /// Held: a record per CPU the bring-up started, naming the local APIC id the /// bring-up gave that CPU and carrying the counters its `counters: cpuN /// reads` line names, and none stale; every CPU's performance request -/// declared at boot, `pm_enable=1`, the request Linux makes on this machine -/// (`tests/metal/t14-linux/hwp-request.txt`), and its power envelope in every read -/// the one its `control_regs:` line holds. No line, the kernel's or a +/// declared at boot, `pm_enable=1`, and its power envelope in every read the +/// one its `control_regs:` line holds. No line, the kernel's or a /// program's, is stamped in a millisecond from `idle0`'s to `idle1`'s, which /// `counters_metal` reads on the log's clock, either /// edge's included because a line stamped in it may follow the read: the @@ -5044,8 +5043,6 @@ fn counters_on_metal(back: &metal::Readback) -> Result<(), String> { if !inside.is_empty() { return Err(format!("the idle second {at0}..{at1} ns holds lines: {inside:?}")); } - let linux_request = u64::from_str_radix(include_str!("metal/t14-linux/hwp-request.txt").trim().trim_start_matches("0x"), 16) - .map_err(|e| format!("metal/t14-linux/hwp-request.txt: {e}"))?; let bsp = kernel.must_say("percpu: BSP cpu_id=0 lapic_id=")?; let mut roster = vec![bsp.rsplit("lapic_id=").next().unwrap_or_default().trim().to_string()]; for cpu in 1..cpus { @@ -5066,9 +5063,6 @@ fn counters_on_metal(back: &metal::Readback) -> Result<(), String> { return Err(format!("{name}: cpu{cpu} is stale or not lapic {}: {counters:?}", roster[*cpu])); } let declared = kernel.must_say(&format!("control_regs: cpu{cpu} pm_enable=1 "))?; - if !declared.contains(&format!(" hwp_request={linux_request:#010x} ")) { - return Err(format!("cpu{cpu} declared {declared:?}, and Linux requests {linux_request:#010x} on this machine")); - } for (counter, field, radix) in [("hwp_request", "hwp_request=0x", 16), ("hwp_request_pkg", "hwp_request_pkg=0x", 16), ("energy_perf_bias", "epb=", 10)] {