Skip to content

toyos-cpuvuln: Linux's speculation verdicts and mitigation choice, pure (S1) - #602

Merged
Japabu merged 9 commits into
mainfrom
wt/toyos-secs1
Sep 29, 2026
Merged

Japabu merged 9 commits into
mainfrom
wt/toyos-secs1

Conversation

@Japabu

@Japabu Japabu commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Stage S1 of issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md: the decision, as a pure function tested on the host. Nothing is wired into the kernel yet; stages S2–S6 of the track do that.

What changed

  • toyos-cpuvuln is a new host-workspace member: #![no_std], #![forbid(unsafe_code)], no dependencies. decide(&Facts) -> Decision gives two things:

    • Decision::line(Vuln): the exact line the pinned Linux prints in each of its 17 /sys/devices/system/cpu/vulnerabilities/* files;
    • Decision's public fields: the state bugs.c selects and tsx_init's state.
  • Facts holds the inputs:

    • the vendor, CPUID.1:EAX and the microcode revision;
    • the CPUID words Linux reads: 1:ECX, (7,0):EBX/EDX, (7,2):EDX, 0x80000008:EBX, and 0x80000021:EAX/ECX;
    • IA32_ARCH_CAPABILITIES, IA32_MCU_OPT_CTRL where GDS_CTRL enumerates it, and IA32_FEAT_CTL where Intel, Centaur and Zhaoxin read it;
    • two probe results: whether MSR_AMD64_LS_CFG reads (AMD 0x15–0x17 and Hygon), and whether PRED_CMD.SBPB writes (AMD from 0x19);
    • whether SMT is active.

    decide panics when a fact is present where Linux would not read it, or absent where it would. This is the one Facts that S0: commit the T14's and TCG model's captures, and read them over #601's reader is to produce.

  • Carried from the tag, row for row:

    • cpu_vuln_whitelist/cpu_vuln_blacklist with x86_match_cpu's stepping mask and first-match rule;
    • cpu_set_bug_bits, init_speculation_control, spectre_bad_microcodes, init_ia32_feat_ctl, and tsx_init under TSX_MODE_OFF;
    • AMD: bsp_init_amd's LS_CFG_SSBD, early_init_amd's IBPB_BRTYPE/SBPB, and tsa_init with amd_check_tsa_microcode's table; Hygon: bsp_init_hygon's LS_CFG_SSBD;
    • each *_select_mitigation, including SRSO under its default safe-RET, TSA, and VMSCAPE's IBPB-on-VMEXIT;
    • cpu_show_common.
  • Every vendor is decided. Hygon is decided: its LS_CFG probe is the same fact as AMD's, and bugs.c's AMD-or-Hygon tests (1103, 2030, 3268) are carried. Refused is deleted.

  • itlb_multihit is decided. The line reads X86_FEATURE_MSR_IA32_FEAT_CTL, X86_FEATURE_VMX and CR4.VMXE (bugs.c:3091-3102). The first two are what init_ia32_feat_ctl leaves from CPUID and IA32_FEAT_CTL. KVM sets CR4.VMXE only while a VM exists (vmx.c:2802, from KVM_CREATE_VM), and none runs, so a kept VMX is "VMX disabled".

  • Only an affected CPU's l1tf line is unmodelled. It rests on the memory map against x86_cache_bits and on kvm_intel's load state. issues/kernel/the-speculation-decision-does-not-model-an-affected-cpus-l1tf-line.md names both,.

  • No input reaches a state these types can't hold, and none they can hold is unreachable. spectre_v2, bhi, mds, taa, mmio and rfds are Options without a not-affected variant; ibpb and its are bools. The deleted code is dead: MMIO's CLEAR_CPU_BUF block, TAA's TSX_CTRL test (RTM on means no TSX_CTRL), the RRSBA helper's guard, the TSA table's vendor guard, the brtype vendor test, and the call site's copy of the table's Intel check.

  • Fixtures, one per machine:

    • fixtures/t14.txt is byte-identical to S0: commit the T14's and TCG model's captures, and read them over #601's toyos-t14linux/s0/t14/vulnerabilities.txt at 44eb3c2.
    • fixtures/tcg.txt is s0/tcg/console.txt lines 379–395, with the serial console's CRs stripped.
    • fixtures/awaiting-capture/{milan,turin}-kvm.txt are this crate's reading of the tag for a Milan guest and a Turin guest. issues/build/no-nightly-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md owes the capture that replaces them.
    • 52 tests. Every expected state and line past the two captures is read from the tag at the lines each test's doc cites: Zen1, Zen2 native and guest, Milan native and guest, Genoa, Turin native and guest, Hygon, KABYLAKE native and guest, Alder Lake, Comet Lake across its ARCH_CAPABILITIES and CPUID bits, Silvermont, Zhaoxin, Centaur, an unknown vendor, and the T14 with each *_NO bit.

Oracle

  • The pinned Linux. The tag is Ubuntu-6.8.0-142.142 of git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/noble, fetched as commit 53e5d07aac028a1523ab0b115f079d6d1bc831ef. It is read at common.c, bugs.c, intel.c, amd.c, hygon.c, feat_ctl.c, tsx.c, scattered.c, kvm/vmx/vmx.c and virt/kvm/kvm_main.c. The config values come from /boot/config-6.8.0-142-generic (sha256 3b8533dd…ca5890, the track's) and from the tag's debian.master/config/annotations: CPU_UNRET_ENTRY, CPU_IBPB_ENTRY, CPU_SRSO, MITIGATION_TSA and MITIGATION_VMSCAPE are y, and KVM_INTEL is m.
  • The T14, independent of this crate: S0's capture (S0: commit the T14's and TCG model's captures, and read them over #601, commit 44eb3c2).
    • The facts are literal constants from cpuid.txt, msr.txt and cpuinfo.txt: EAX 0x806c1, 1:ECX 0x7ffafbbf, (7,0) EBX 0xf3bfa7eb and EDX 0xfc100710, (7,2):EDX 0x1, 0x10A 0x0a005c6b, 0x123 0, microcode 0xbe, and SMT.
    • S0 did not read 0x3A. The T14's feat_ctl is LOCKED | VMX_ENABLED_OUTSIDE_SMX, the two bits init_ia32_feat_ctl reads, from cpuinfo.txt's vmx flag and vmx flags line, which say Linux kept VMX. The T14 has PSCHANGE_MC_NO, so none of its 17 lines reads it.
    • They give all 17 captured lines.
    • They give spec_ctrl = 0x1, which S0 reads from MSR 0x48 on all eight CPUs.
  • The TCG model: S0's console capture. S0 verifies only the model's signature, so its lines are asserted over both values of every other fact they could rest on: the hypervisor bit, RDRAND, the microcode and SMT.
  • AMD and the rest: no independent oracle yet. The two guest fixtures take their CPUID from InstLatx64's bare-metal dumps with the hypervisor bit set: EPYC 7713 AuthenticAMD0A00F11_K19_Milan_CPUID1.txt at 2dc186e9, and EPYC 9655 AuthenticAMD0B00F21_K20_Turin_01_CPUID.txt at b499237d. Every other fixture's facts are constructed, and its expected values are my reading of the tag.

Negative controls

Every mutation below was applied at e4d4014 as a checked patch (git apply --check exit 0). The mutated tree builds (cargo test -p toyos-cpuvuln --no-run exit 0). Then cargo test -p toyos-cpuvuln ran, and git apply -R left git diff --quiet at exit 0.

  • 33 mutations of the decision's arms, each exits 101; the tests that catch them:
    • Zen1 STIBP and retbleed's SMT line: zen1_forces_stibp_from_the_untrained_return_thunk;
    • the Milan guest's TSA microcode: a_milan_kvm_guest_gives_the_tags_lines at microcode 0 and u32::MAX;
    • the Intel guest's BHI, ITS and bad-microcode terms: an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt;
    • BHI_DIS_S, RRSBA_DIS_S and BHI: Retpoline: alder_lake_mitigates_bhi_in_hardware and a_retpoline_guest_leaves_bhi_to_retpolines_only_with_rrsba_disabled;
    • RtmAlwaysAbort, FullLocked, TAA/MMIO Verw and SRBDS TsxOff: rtm_always_abort_decides_tsx_first, firmware_can_lock_the_gds_mitigation, comet_lake_clears_cpu_buffers_by_mds_no_and_fb_clear and srbds_is_tsx_disabled_only_without_rtm_and_mmio.
  • A mutation sweep over lib.rs and table.rs (every &&/|| operand, if condition, comparison, unary !, | operand, assignment, bool literal, .then receiver, match arm and line string): 719 mutants at e4d4014. 718 build and exit 101; 0 survive. One does not build: dropping | None from an exhaustive match, which the compiler refuses.
  • Tests added past the sweep, each read from the tag: a_tsa_row_keyed_by_a_model_nibble_of_8_or_more_is_read (amd.c:479-490,621), a_blacklist_row_for_stepping_zero_stands_before_its_any_stepping_row (common.c:1310-1311,1480-1483,1521-1523) and hygon_without_smt_and_with_ibpb_brtype_has_srso_smt_disabled (bugs.c:2689-2691,3284-3285). Five patches, each shown to build, each exits 101 on its named test and leaves the tree clean: (id.model & 0x7) << 4 at table.rs:344, & !(1 << min) at table.rs:63, ANY 0 to 1 at table.rs:59, the COMETLAKE_L rows swapped at table.rs:251-252, and id.family < 0x18 at lib.rs:811.

The tests read Decision through an exhaustive destructuring, so a field added to it fails to build until a test asserts it.

Gates, at e767338 (origin/main merged)

  • cargo test -p toyos-cpuvuln: EXIT=0, 52 passed.
  • cargo clippy -p toyos-cpuvuln --target x86_64-unknown-none -- -D warnings: EXIT=0.
  • cargo run -- --ci host: EXIT=0, "54 step(s), all green".

What I am unsure of

  • The Milan guest's lines depend on whether the runner's host kernel passes IBPB_BRTYPE and VERW_CLEAR through (kvm/cpuid.c:797-800). Without them, the fixture's Vulnerable: Safe RET, no microcode and TSA Vulnerable: Clear CPU buffers attempted, no microcode hold. With them, the lines are Mitigation: Safe RET and Mitigation: Clear CPU buffers, and a_guest_with_verw_clear_mitigates_tsa holds the second. Only a real runner capture settles which, and issues/build/no-nightly-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md owes it.
  • For AMD and Hygon, sbpb_write_accepted and ls_cfg_readable are facts that only an MSR probe with exception fixup can produce, and so is feat_ctl's None. S2 has to perform the same probes Linux does.
  • "VMX disabled" holds for Linux only while no VM runs. ToyOS runs none, and the owner has not ruled on which Linux state is parity if that changes.
  • spec_ctrl holds only the bits bugs.c ORs in. Linux keeps the MSR's other bits as it read them (bugs.c:156-165).
  • smt stands for both sched_smt_active() and cpu_smt_possible(). They agree under the default command line with every sibling online.
  • Everything unreachable under the default command line is left out: the LFENCE and eIBRS+retpoline modes, retbleed IBPB/STUFF, SSB DISABLE/SECCOMP, STIBP STRICT, and SRSO's other commands. md_clear_update_mitigation changes no state or line here. Nor does tsx_dev_mode_disable: on the boot CPU it runs before tsx_init sets MSR_TSX_CTRL (tsx.c:145,163,190). That is argued from the source, not measured.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

Japabu and others added 2 commits September 29, 2026 08:58
…re (S1)

Stage S1 of issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md:
a no_std, unsafe-free crate the kernel can depend on later, mapping a CPU's
CPUID and MSR facts to the line Ubuntu-6.8.0-142.142 (commit 53e5d07aac02)
prints in each of its 17 /sys/devices/system/cpu/vulnerabilities files, and to
the mitigation state bugs.c selects under the default command line and the
pinned config (/boot/config-6.8.0-142-generic, sha256 3b8533dd...5890,
extracted from linux-modules-6.8.0-142-generic_6.8.0-142.142_amd64.deb and
checked).

Carried from the tag: cpu_vuln_whitelist and cpu_vuln_blacklist
(common.c:1182-1344) row for row with x86_match_cpu's first-match rule,
cpu_set_bug_bits (common.c:1414-1578), init_speculation_control
(common.c:973-1012), spectre_bad_microcodes (intel.c:141-180), tsx_init's
state under TSX_MODE_OFF (tsx.c:158-229), every *_select_mitigation in
cpu_select_mitigations (bugs.c:149-199) and cpu_show_common's strings
(bugs.c:3305-3371).

Refused rather than approximated: AMD family 0x15 on and Hygon, whose
amd.c/hygon.c derive speculation features from microcode tables and MSR probes
the facts do not carry; and the l1tf and itlb_multihit lines of an affected
CPU, which read the e820 map, kvm_intel and IA32_FEAT_CTL. Recorded in
issues/kernel/the-speculation-decision-refuses-amd-from-family-0x15-and-two-lines.md.

Both fixtures are provisional until S0 captures them: the T14's facts are its
documented identity (i5-1135G7, family 6 model 0x8C stepping 1) and the TCG
model's are read off QEMU v11.1.1's target/i386/cpu.c; the expected lines are
Linux's source read by hand.

Negative control, applied as a checked patch and restored: deleting GDS from
the TIGERLAKE_L blacklist row builds (exit 0) and reds the T14 test (exit 101)
with gather_data_sampling "Not affected" against "Mitigation: Microcode".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every bugs.c, common.c, amd.c, hygon.c, intel.c and tsx.c range the crate and
its issue cite now ends on the cited function's or block's closing line at
Ubuntu-6.8.0-142.142, checked by printing both boundary lines of each. The
previous commit's message cites intel.c:141-180 and tsx.c:158-229; the
functions end at intel.c:182 and tsx.c:245.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu marked this pull request as ready for review September 29, 2026 07:07
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #602 at a749acb. CI host is green at this head (run 36534811442, conclusion success, toyos-cpuvuln unit tests and clippy ran). Net +1521/−0: production 1318 (lib.rs 987, table.rs 317, manifests 14), tests 173 (tests.rs 139, fixtures 34), issue 30.

Oracle checks, measured by the reviewer in scratch. The tag was fetched from launchpad and resolves to 53e5d07a. S0 capture: /Users/jan/Dev/jan/toyos-s0/toyos-t14linux/s0/.

  • fixtures/provisional/t14.txt is byte-identical to S0 t14/vulnerabilities.txt (diff exit 0). fixtures/provisional/tcg.txt is identical to S0 tcg/console.txt lines 379-395 once the CRs are stripped (diff exit 0). No line mismatches.
  • decide over S0's raw T14 words gives all 17 captured lines and spec_ctrl = 0x1. That equals S0's 0x48 = 0x1 on all 8 CPUs. Test exit 0.
  • Negative control: the GDS deletion on table.rs:252 was applied as a checked patch (git apply --check exit 0). cargo test exits 101. It reds tests.rs:89 on gather_data_sampling (Not affected against Mitigation: Microcode) and tests.rs:126. git apply -R restored table.rs to sha1 cd349c87.
  • clippy --target x86_64-unknown-none -- -D warnings exits 0, and so does host clippy. The crate has #![no_std] and #![forbid(unsafe_code)], and std appears only under cfg(test).
  • Every Linux path:line in lib.rs, table.rs and the issue was checked at 53e5d07a, and each boundary line matches. The whitelist and blacklist match common.c:1182-1344 row for row. All 62 model constants match intel-family.h, and all 20 spectre_bad_microcodes rows match intel.c. intel-ucode-defs.h and old_microcode are absent at the tag. The QEMU target/i386/cpu.c line citations were not verified, because QEMU source was not fetched.

BLOCKER

  • toyos-cpuvuln/src/tests.rs:25-48 — The T14 constant does not describe the T14. It sets ARCH_CAP_TAA_NO, but S0's 0x10A is 0x0a005c6b, where bit 8 is clear. It omits FBSDP_NO and RFDS_NO, and it omits CPUID.(7,0):EDX[9] SRBDS_CTRL, which decide reads (S0 has 0xfc100710). It gives microcode 0 where S0 has 0xbe. S1's exit is "S0's T14 facts give S0's T14 lines". Replace the facts with S0's words: EAX 0x806c1, 1:ECX 0x7ffafbbf, (7,0) EBX 0xf3bfa7eb and EDX 0xfc100710, (7,2):EDX 0x1, 0x10A 0x0a005c6b, 0x123 0, microcode 0xbe. Replace fixtures/provisional/ with S0's files. Measured green in scratch.
  • toyos-cpuvuln/src/tests.rs — No test asserts any Decision field, although S2 and later apply them. Each of these patches passes all four tests: (a) delete lib.rs:622 spec_ctrl |= SPEC_CTRL_IBRS;; (b) at lib.rs:592, change arch & ARCH_CAP_TSX_CTRL_MSR != 0 to == 0; (c) delete lib.rs:692 clear_cpu_buf = true;; (d) delete || (matches!(spectre_v2, SpectreV2::Eibrs) && !caps.autoibrs) at lib.rs:672. the_t14s_facts_give_its_lines must assert spec_ctrl == 0x1 (oracle: S0 0x48), tsx == Tsx::NotSupported, stibp == Stibp::None and !clear_cpu_buf. the_t14_with_arch_capabilities_read_as_zero must assert clear_cpu_buf.
  • toyos-cpuvuln/src/table.rs:95,237-238,316 and lib.rs:742 — No test exercises the x86_match_cpu stepping rule, the first-match rule, the microcode bound or the hypervisor arms that the crate claims. Each of these passes: at table.rs:95, change r.steppings & (1 << id.stepping) != 0 to == 0; swap table.rs:237 and table.rs:238; at table.rs:316, change microcode <= bad to <; delete lib.rs:742-743 (Gds::Hypervisor). Add a test that turns each one red. For example: KABYLAKE_L stepping 0xB without ITS and stepping 0xC with ITS; SKYLAKE_X stepping 5 against stepping 6; KABYLAKE stepping 0xA at microcode 0x80 with its speculation controls cleared; the T14 with the hypervisor bit, giving Unknown: Dependent on hypervisor status.

NOTE

  • issues/kernel/the-speculation-decision-refuses-amd-from-family-0x15-and-two-lines.md — The refusal is sound for S1's exit, because neither the T14 nor the TCG model (family 0xF) is refused. Its consequence is understated. In nightly run 36496779560, all 14 KVM shards ran on AMD EPYC 7763, 9V74 or 9V45 (family 0x19/0x1A). So decide refuses on every KVM runner, and the KVM arms of S2, S3, S5 and S6 have no expectation anywhere. Also, for a guest none of the reasons the issue cites applies: LS_CFG_SSBD exists only on families 0x15-0x17 (amd.c:578), tsa_init returns under a hypervisor (amd.c:519), and the PRED_CMD probe is skipped under a hypervisor (amd.c:799). What is actually missing is SRSO and TSA selection. The issue should record the measurement and the real reason.
  • toyos-cpuvuln/src/lib.rs:576,601,619-621,641,675-677,831,952-961 — This code is unreachable on every input decide accepts. AUTOIBRS is enumerated only from family 0x19, which is refused. Retbleed::Unret needs an AMD retbleed row, and those rows are 0x15-0x17, all refused. AMD_STIBP_ALWAYS_ON gives StrictPreferred, and the AMD clause of ibrs_fw is likewise AMD-only. Stibp::Prctl => "" is unreachable because Prctl implies smt. Delete all of it.
  • toyos-cpuvuln/src/tests.rs:59-72 — Of the TCG facts, S0 verifies only family 0xf, model 0x6b and stepping 1. No TCG line depends on the unverified hypervisor, rdrand or microcode facts, so the TCG case cannot detect errors in them.

REMOVE

  • toyos-cpuvuln/src/tests.rs:4-9 — "provisional … S0 … replace" narrates a plan, and becomes false once the first BLOCKER lands.
  • toyos-cpuvuln/src/tests.rs:20-24 — "as documented … not affected by … TAA … Only the bits decide reads are set" is false against S0: the T14 has no TAA_NO, and SRBDS_CTRL is read but not set.
  • toyos-cpuvuln/src/tests.rs:50-58 — QEMU source line numbers record provenance and will rot. They belong in the commit message.
  • PR body, "Not yet independent" and "Waits on S0" — both are false once S0's facts are adopted, and this body becomes main's record.

SEND BACK

Japabu and others added 2 commits September 29, 2026 09:49
…/0x1A

Answers the review of #602 at a749acb.

The T14's facts are S0's words, from #601's toyos-t14linux/s0/t14/ at commit
44eb3c2: CPUID.1:EAX 0x806c1, 1:ECX 0x7ffafbbf, (7,0) EBX 0xf3bfa7eb and EDX
0xfc100710 (SRBDS_CTRL set), (7,2):EDX 0x1, 0x10A 0x0a005c6b (FBSDP_NO and
RFDS_NO set, TAA_NO clear), 0x123 0, microcode 0xbe. fixtures/provisional/ is
gone: fixtures/t14.txt is byte-identical to s0/t14/vulnerabilities.txt, and
fixtures/tcg.txt is s0/tcg/console.txt lines 379-395 with the serial
console's CRs stripped.

Every public Decision field is now asserted. The tests read them through an
exhaustive destructuring, so a field added to Decision does not build until a
test names it. The T14 asserts spec_ctrl == 0x1 against S0's MSR 0x48, which
reads 0x1 on all eight CPUs, and the arch_capabilities-zero control asserts
clear_cpu_buf.

New host tests hold the x86_match_cpu stepping mask (KABYLAKE_L stepping 0xB
against 0xC), the first-match rule (SKYLAKE_X stepping 5 against 6), the
bad-microcode bound (KABYLAKE stepping 0xA at 0x80 against 0x81) and the
hypervisor GDS answer.

AMD families 0x17, 0x19 and 0x1A are decided, per the owner's ruling. That
takes three new Facts fields. cpuid_8000_0021_ecx carries TSA_SQ_NO and
TSA_L1_NO. ls_cfg_readable and sbpb_write_accepted carry the results of
bsp_init_amd's LS_CFG read and early_init_amd's PRED_CMD.SBPB write; each is
asserted present exactly where Linux probes. The new selections are SRSO under
safe-ret, TSA with amd_check_tsa_microcode's table, VMSCAPE's IBPB-on-VMEXIT,
and x86_pred_cmd's SBPB. The code the review found unreachable (AUTOIBRS,
Retbleed Unret, STIBP always-on and the AMD IBRS_FW clause) is now reached, and
Stibp::Prctl's empty arm is gone.

X86_FEATURE_ZEN is set by init_amd from identify_cpu (amd.c:1039,
common.c:1997), after early_identify_cpu has run cpu_set_bug_bits
(common.c:1732). So the "Zen guest" TSA clause never fires on the boot CPU, and
a family 0x1A guest is not TSA-affected.

The Milan and Turin KVM-guest fixtures take their CPUID from InstLatx64's
bare-metal dumps (EPYC 7713 at 2dc186e9, EPYC 9655 at b499237d) with the
hypervisor bit set. Their lines are this crate's reading of the tag. They sit
in fixtures/awaiting-capture/, and
issues/build/no-nightly-runner-has-had-its-cpuid-and-vulnerability-lines-captured.md
owes the real capture. In nightly run 36496779560 the 14 KVM shards ran on EPYC
7763 (11), 9V74 (2) and 9V45 (1).

The refusal issue is renamed to what still stands:
issues/kernel/the-speculation-decision-refuses-amd-0x15-0x16-hygon-and-two-lines.md.

The TCG test now asserts its lines over both values of every fact S0 does not
verify: the hypervisor bit, RDRAND, the microcode and SMT. The QEMU source
reading behind the old constant was qemu64 at v11.1.1's target/i386/cpu.c:
xlevel 0x8000000A at 3545-3563, the hypervisor bit at 8486, no speculation bits
at 996-1001 and 1024-1034, and the microcode default at 10187-10197.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2 of #602 at ca476ba. CI host is green on this head: run 36541031702, headSha ca476ba, conclusion success. Net +2099/−0:

  • production 1487: lib.rs 1125, table.rs 348, manifests and lock 14;
  • tests 565: tests.rs 497, fixtures 68;
  • issues 47.

The reviewer measured every result below on a scratch copy of the crate at ca476ba, with host cargo test only. Each mutation passed git apply --check (exit 0) and was reverted, and git diff --quiet then exited 0. The pinned Linux was fetched as tag Ubuntu-6.8.0-142.142 and resolves to 53e5d07a.

Round-1 blockers

  • B1 CLOSED. fixtures/t14.txt is byte-identical to S0's t14/vulnerabilities.txt at 44eb3c2 (cmp exit 0). fixtures/tcg.txt is identical to tcg/console.txt lines 379–395 with the CRs stripped (cmp exit 0). The T14 constant equals S0's values:
    • cpuid.txt: leaf 1 EAX/ECX, (7,0) EBX/EDX, (7,2) EDX, 0x80000008 EBX, and 0x80000021;
    • msr.txt: 0x10A = 0x0a005c6b and 0x123 = 0 on all 8 CPUs;
    • cpuinfo.txt: microcode 0xbe, siblings 8, cpu cores 4.
  • B2 CLOSED. Each of these exits 101: delete spec_ctrl |= SPEC_CTRL_IBRS;, TSX != 0 → == 0, delete the MDS arm's clear_cpu_buf = true;, delete the eIBRS STIBP clause.
  • B3 CLOSED. Each of these exits 101 and reds the test the PR names: stepping != 0 → == 0, swap the SKYLAKE_X rows, <= → <, delete Gds::Hypervisor.
  • The implementer's mutations. TSA guest |= false, the SBPB gate and SRSO <= 0x19 each exit 101 on the tests the PR names.

Rulings asked for

  • InstLatx64 is acceptable as the pinned third-party source of bare-metal words for a placeholder that a capture replaces. It is not the input a KVM guest sees, so it is regression input only and never an oracle.

  • Is it said plainly? Yes, in four places: the PR's "AMD: no independent oracle yet", awaiting-capture/, tests.rs:366-368, and the capture issue.

  • Guest answers. The bare-metal dump with the hypervisor bit set does not establish them. What KVM presents at the tag:

    • it enumerates ARCH_CAPABILITIES and emulates the MSR (kvm/cpuid.c:689-691, kvm/x86.c:1623-1681);
    • it sets SPEC_CTRL, INTEL_STIBP and SPEC_CTRL_SSBD in (7,0):EDX (cpuid.c:693-698);
    • it masks 0x80000008:EBX (cpuid.c:751-755);
    • it passes 0x80000021's IBPB_BRTYPE, SBPB and VERW_CLEAR only from the host's own caps (cpuid.c:797-800).

    Measured:

    • With those words, MILAN_GUEST gives the same 17 lines (exit 0).
    • With IBPB_BRTYPE, SBPB and VERW_CLEAR added, spec_rstack_overflow becomes Mitigation: Safe RET and tsa becomes Mitigation: Clear CPU buffers.

    So the Milan fixture holds only if the runner's host kernel lacks IBPB_BRTYPE and VERW_CLEAR. A host kernel that is itself a guest skips both probes (amd.c:519, amd.c:799), so its hypervisor's CPUID decides. The Turin lines hold: family 0x1A is in no SRSO, TSA or VMSCAPE row, and AUTOIBRS passes KVM's mask.

  • Generality. lib.rs and table.rs carry no T14 case, and the tables are Linux's, row for row.

BLOCKER

The AMD arms and two hypervisor arms have no test that can fail. Every patch below passes all 13 tests (cargo test exit 0).

  • toyos-cpuvuln/src/lib.rs:757 — patch: } else if ebx8 & CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON != 0 || retbleed == Retbleed::Unret { → } else if retbleed == Retbleed::Unret {. No AMD test has SMT without Unret. Add a native Milan with smt: true, asserting Stibp::StrictPreferred, spec_ctrl == SPEC_CTRL_STIBP and STIBP: always-on.

  • toyos-cpuvuln/src/lib.rs:648-650,630-634 — patches: delete || f.ls_cfg_readable == Some(true); separately, change (0x15..=0x17).contains to (0x15..=0x16).contains. No test presents family 0x17 without AMD_SSBD/VIRT_SSBD. Add a Zen1 (signature 0x00800f11, 0x80000008:EBX bits 24 and 25 clear): ls_cfg_readable: Some(true) gives Ssb::Prctl, and Some(false) gives Ssb::None.

  • toyos-cpuvuln/src/lib.rs:840-847,874 — patches, applied separately:

    • swap the SRSO_BP_SPEC_REDUCE and ibpb_brtype tests;
    • change Srso::UcodeNeeded to Srso::SafeRetUcodeNeeded;
    • delete the srso == Some(Srso::IbpbOnVmexit) arm of vmscape.

    No test sets SRSO_USER_KERNEL_NO. Add a native family 0x19 with bit 30 set, in three cases:

    • with bit 31: BpSpecReduce;
    • with the SBPB probe accepted: IbpbOnVmexit together with Vmscape::IbpbOnVmexit;
    • with neither: UcodeNeeded.
  • toyos-cpuvuln/src/lib.rs:568-570 — patch: != CPUID_8000_0021_ECX_TSA_SQ_NO | CPUID_8000_0021_ECX_TSA_L1_NO → == 0. No test sets either bit. Add a Milan with TSA_SQ_NO only, which is TSA-affected, and one with both bits, which gives Not affected.

  • toyos-cpuvuln/src/table.rs:342 — patch: id.model <= 0xAF → id.model < 0x10. Only key 0xa0011 is tested, so every Zen4 row is unguarded. Add a native Genoa (0x00a10f11) at 0x0a10114c, which gives Tsa::Full, and at 0x0a10114b, which gives Tsa::UcodeNeeded.

  • toyos-cpuvuln/src/lib.rs:814,1025 — patches: delete } else if hypervisor { Srbds::Hypervisor; separately, in the MDS line change if d.hypervisor { → if false {. No affected Intel guest is tested. Add KABYLAKE (0x000906ea) with arch_capabilities: 0 and the hypervisor bit: srbds:Unknown: Dependent on hypervisor status, and mds ending ; SMT Host state unknown.

NOTE

  • toyos-cpuvuln/src/lib.rs:606 and the refusal issue — The refusal does not follow one rule.

    • Families 0x15, 0x16 and 0x1B+ are refused for having no fixture.
    • Families 0x10–0x14 and Zen1 also have no fixture, but they are decided.
    • decide already models 0x15–0x17's LS_CFG probe (lib.rs:630-634) and Unret.

    Either decide those families and delete the arm, or refuse by the same rule everywhere.

  • toyos-cpuvuln/src/table.rs:86-88,218,282 — hygon() and both Hygon rows are unreachable behind Refused::Hygon (lib.rs:609). Delete them, or decide Hygon.

  • toyos-cpuvuln/src/table.rs:342 — && id.model <= 0xAF changes no answer, because no row keys a model above 0xAA. Delete it.

REMOVE

  • toyos-cpuvuln/src/tests.rs:61 — "It does not enumerate ARCH_CAPABILITIES" is false of KVM at the tag (kvm/cpuid.c:691).
  • PR body, "One reading worth checking: …" — it repeats lib.rs:564-567 and the commit message, and "worth checking" is an invitation, not a record.
  • PR body, "I could not fetch AMD's PPRs" — it narrates an attempt.

SEND BACK

…ecide every AMD family

Review round 2 of #602 named nine mutations that passed every test. Each
now has a host test that goes red, built from the pinned tag's logic
(Ubuntu-6.8.0-142.142, 53e5d07a) and citing the lines behind each
expected line:

- STIBP always-on from AMD_STIBP_ALWAYS_ON without Unret: a native Milan
  with SMT (bugs.c:1575-1577, 2054-2068, 3185-3186).
- The LS_CFG probe as the source of SSBD, and the probe's family bound:
  AMD 0x15, 0x16 and Zen1 0x17 without AMD_SSBD/VIRT_SSBD (amd.c:576-596).
- SRSO_USER_KERNEL_NO's three branches and VMSCAPE's IBPB on VM exit
  (bugs.c:2703, 2715-2716, 2771-2781, 2861-2863).
- The TSA_SQ_NO/TSA_L1_NO decoding (common.c:1555-1561, scattered.c:52-53).
- A Zen4 row of the TSA microcode table: Genoa at 0x0a10114c and
  0x0a10114b (amd.c:491, 522-525).
- SRBDS and MDS inside an Intel guest: KABYLAKE without ARCH_CAPABILITIES
  (bugs.c:691-692, 3117-3119).

The refusals now follow the module's single rule: an input is refused only
when its answer rests on something the facts do not carry. AMD 0x15, 0x16,
0x18 and 0x1B and later rest on nothing beyond the facts: the LS_CFG probe
is carried, and bugs.c's only family tests are SRSO's. So they are decided,
and Refused::AmdFamily is deleted. Hygon stays refused because
bsp_init_hygon's own LS_CFG probe (hygon.c:228-239) is not carried. Its
table rows could never be reached and are deleted. The refusal issue is
renamed to match.

amd_check_tsa_microcode's `model <= 0xAF` bound changed no answer, because
every row keys a model that bsp_init_amd names Zen3 or Zen4, so it is
deleted.

The false "does not enumerate ARCH_CAPABILITIES" comment is deleted: KVM
enumerates it at the tag (kvm/cpuid.c:691).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 3 of #602 at 465b9e9. CI host is green on this head: run 36550788657, headSha 465b9e9, conclusion success. The log shows toyos_cpuvuln's 19 tests ran and "Host: 54 step(s), all green". Net +2240/−0:

  • production 1478: lib.rs 1120, table.rs 344, manifests and lock 14;
  • tests 717: tests.rs 649, fixtures 68;
  • issues 45.

The reviewer measured on a scratch copy of the crate at 465b9e9 with host cargo test only; the worktree was not touched. Each mutation was applied, run, and reverted, and git diff --quiet then exited 0. The patches are below. The oracle is tag Ubuntu-6.8.0-142.142, fetched from launchpad (resolves to 53e5d07a), and read directly: amd.c 472-656 and 799-806, common.c 973-1012, 1414-1578 and 1700-1732, and bugs.c 1054-1194, 1492-1591, 1831-2045, 2670-2962 and 3264-3288. Checked against it:

  • amd_check_tsa_microcode's 16 rows match.
  • tsa_init runs inside bsp_init_amd, before cpu_set_bug_bits, so the comment at lib.rs:563-566 holds.
  • Deleting model <= 0xAF changes no answer: ZEN3|ZEN4 covers exactly 0x19 models 0x00–0xAF, and no row keys a model above 0xAA.
  • bugs.c's only family tests are 2689 and 2726.

Round-2 blockers

  • lib.rs STIBP AMD_STIBP_ALWAYS_ON: CLOSED. Exit 101 on amd_stibp_always_on_is_strict_without_the_untrained_return_thunk.
  • LS_CFG: CLOSED. Dropping || f.ls_cfg_readable == Some(true) exits 101, and so does (0x15..=0x17) → (0x15..=0x16). Both red amd_0x15_to_0x17_take_ssbd_from_the_ls_cfg_probe.
  • SRSO_USER_KERNEL_NO: CLOSED. The swap, UcodeNeeded → SafeRetUcodeNeeded, and deleting the VMSCAPE IbpbOnVmexit arm each exit 101 on srso_user_kernel_no_mitigates_only_the_vm_exit.
  • TSA _NO bits: CLOSED. Exit 101 on tsa_is_ruled_out_only_by_both_its_no_bits.
  • Zen4 TSA row: CLOSED. The guard gaining && id.model < 0x10 exits 101 on zen4_reads_its_row_of_the_tsa_microcode_table.
  • Intel guest SRBDS/MDS: CLOSED. Both patches exit 101 on an_intel_guest_cannot_know_its_srbds_mitigation_or_its_hosts_smt.
  • The round-2 NOTEs (the refusal rule, the Hygon rows, the 0xAF bound) and all three REMOVEs are done.

BLOCKER

Each patch below leaves all 19 tests green (cargo test exit 0).

  • toyos-cpuvuln/src/lib.rs:752,1078 — STIBP for the families this round newly decides has no test that can fail. Patch A: } else if ebx8 & CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON != 0 || retbleed == Retbleed::Unret { → } else if ebx8 & CPUID_8000_0008_EBX_AMD_STIBP_ALWAYS_ON != 0 {. Patch B: in the Retbleed::Unret line, "vulnerable" → "enabled with STIBP protection". Every AMD test with Unret and SMT carries AMD_STIBP_ALWAYS_ON. Linux's bugs.c:1579-1585 forces STRICT_PREFERRED from Unret alone, and that is what writes SPEC_CTRL.STIBP on Zen1 and on families 0x15/0x16. Add a native Zen1 (0x00800f11) with SMT and 0x80000008:EBX carrying AMD_IBPB, AMD_IBRS, AMD_STIBP and AMD_SSBD but not bit 17. It must give Stibp::StrictPreferred, spec_ctrl == SPEC_CTRL_STIBP and retbleed:…; SMT enabled with STIBP protection. The same CPU without AMD_STIBP must give Stibp::None and …; SMT vulnerable.

  • toyos-cpuvuln/src/lib.rs:857 — patch: || (!hypervisor && table::tsa_microcode(&id, f.microcode)); → || (table::tsa_microcode(&id, f.microcode));. tsa_init returns under a hypervisor (amd.c:519-520), and MILAN_GUEST's doc says no line reads a guest's microcode, but every guest test uses microcode 0. Add assert_capture of Facts { microcode: u32::MAX, ..MILAN_GUEST } against the same fixture.

  • toyos-cpuvuln/src/lib.rs:554,561,656 — the Intel-guest hypervisor arms have no test that can fail:

    • bug_bits' BHI clause (b.ibrs_enhanced || caps.hypervisor) → b.ibrs_enhanced;
    • ITS && (caps.hypervisor || blacklisted(id, ITS)) → && blacklisted(id, ITS);
    • delete && !hypervisor from the bad-microcode gate.

    Extend an_intel_guest_cannot_know_its_srbds_mitigation_or_its_hosts_smt with microcode 0x80, KABYLAKE stepping 0xA's bad bound (intel.c:173-174 skips the check in a guest), and assert its whole state(&d) and its spectre_v2 line.

  • toyos-cpuvuln/src/lib.rs:590,708,711 — two of the three bits decide ORs into spec_ctrl are unguarded. This is round 1's SPEC_CTRL_IBRS finding for its sibling bits:

    • delete spec_ctrl |= SPEC_CTRL_BHI_DIS_S;;
    • delete *spec_ctrl |= SPEC_CTRL_RRSBA_DIS_S;;
    • separately, bhi = if spectre_v2 == SpectreV2::Retpoline && rrsba_disabled { → bhi = if false {.

    BHI_DIS_S is the BHI mitigation on every eIBRS part with BHI_CTRL, Alder Lake onward. Add a native Alder Lake (family 6 model 0x97) with IBRS_ALL, and with BHI_CTRL in (7,2):EDX, asserting spec_ctrl == SPEC_CTRL_IBRS | SPEC_CTRL_BHI_DIS_S and BHI: BHI_DIS_S. Add an Intel guest without IBRS_ALL but with ARCH_CAP_RRSBA and RRSBA_CTRL, asserting SPEC_CTRL_RRSBA_DIS_S in spec_ctrl and BHI: Retpoline.

  • toyos-cpuvuln/src/lib.rs:667-668,778,791,807,823-824 — these Intel selection arms have no test that can fail:

    • delete the Tsx::RtmAlwaysAbort arm;
    • TAA if md_clear && !(arch & ARCH_CAP_MDS_NO != 0 && arch & ARCH_CAP_TSX_CTRL_MSR == 0) → if md_clear;
    • MMIO … || (md_clear && flush_l1d && arch & ARCH_CAP_MDS_NO == 0) dropped;
    • SRBDS if arch & ARCH_CAP_MDS_NO != 0 && !rtm && !bugs.mmio_stale_data { → if false {;
    • delete the Gds::FullLocked arm.

    Each selects state S2 applies. Each needs a test that turns it red. For example: the T14 with CPUID.(7,0):EDX[11]; the T14 with mcu_opt_ctrl: Some(GDS_MITG_LOCKED); and a Comet Lake (0xA5) with and without FB_CLEAR, and with MDS_NO and RTM, for TAA, MMIO and SRBDS.

NOTE

  • toyos-cpuvuln/src/lib.rs:787-789 — if bugs.mds || (bugs.taa && rtm) { clear_cpu_buf = true; } is dead. The MDS arm already sets it on every bugs.mds, and the TAA arm on every bugs.taa && rtm. Deleting it leaves 19/19 green. Delete it.
  • toyos-cpuvuln/src/lib.rs:932,962 — SpectreV2::None => "Vulnerable" and (true, SpectreV2::None) are unreachable. The line prints only when bugs.spectre_v2 holds, and then spectre_v2 is never None. Delete them, or fold them into the neighbouring arm.
  • toyos-cpuvuln/src/lib.rs:655 and table.rs:308 — id.vendor == Vendor::Intel is tested twice on one path. Keep one.
  • toyos-cpuvuln/src/lib.rs:1023-1027 — "mitigated"→"vulnerable" in the MSBDS_ONLY arm passes. It is pre-2020 Atom only; test it or name it.
  • issues/kernel/the-speculation-decision-refuses-hygon-and-two-lines.md — itlb_multihit on an affected CPU rests on CPUID's VMX bit, IA32_FEAT_CTL, and a CR4.VMXE that ToyOS never sets (bugs.c:3091-3102). The kernel can read all three, so the refusal is a choice, and it refuses Comet Lake (2020, affected, no PSCHANGE_MC_NO). Hygon likewise: ls_cfg_readable already has the probe's shape, and extending it means widening the retbleed and IBPB_FW vendor tests to Hygon (bugs.c:1103,2030). Under Linux parity as the floor, both are for S1 or a named later stage, not an open-ended defect.

REMOVE

None.

SEND BACK

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

Japabu and others added 2 commits September 29, 2026 13:24
…s, a fixture for every sweep survivor

Round 3's review found decision arms no test could fail. This round closes its
five blocker groups with fixtures whose expected state and lines are read from
the tag's source, and then sweeps the crate for the rest.

- Hygon is decided rather than refused: its LS_CFG probe is the same fact as
  AMD's (hygon.c:228-239, every family), the untrained return thunk and IBPB
  around firmware take Hygon as bugs.c does, and its whitelist and blacklist
  rows are back. `decide` returns a `Decision`; `Refused` is gone.
- itlb_multihit is decided: `Facts::feat_ctl` carries IA32_FEAT_CTL where
  Intel, Centaur and Zhaoxin read it, and the line is "VMX unsupported" or,
  with VMX kept by init_ia32_feat_ctl and no VM running, "VMX disabled".
  Only an affected CPU's l1tf line stays unmodelled; its issue now names the
  two facts it lacks.
- States no input reaches are unrepresentable: `spectre_v2`, `bhi`, `mds`,
  `taa`, `mmio` and `rfds` are `Option`s without a "not affected" variant,
  `ibpb` is a bool (IBPB is conditional wherever Spectre v2 is), and `its`
  is a bool. MMIO's dead CLEAR_CPU_BUF block, TAA's always-true TSX_CTRL test,
  the RRSBA helper's dead guard, TSA microcode's dead vendor guard, the
  brtype probe's redundant vendor test and the doubled Intel check at the
  bad-microcode call site are deleted.
- New fixtures: Zen1 with and without AMD_STIBP, Milan guest at microcode
  u32::MAX, a KABYLAKE guest at its bad microcode, Alder Lake with BHI_CTRL,
  retpoline guests with and without RRSBA_CTRL and RRSBA, Comet Lake across
  MDS_NO, FB_CLEAR, TSX_CTRL, RTM, MD_CLEAR, FLUSH_L1D, SRBDS_CTRL, RDRAND and
  RDSEED, RTM_ALWAYS_ABORT, GDS locked, Silvermont's MSBDS_ONLY SMT line, the
  T14's *_NO bits, RFDS by RFDS_CLEAR and by row, Hygon, Zhaoxin, Centaur, an
  unknown vendor, native Turin, a Zen2 guest, and IA32_FEAT_CTL's cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Round 4 at e4d4014: round 3's mutations and this round's sweep.

Round 3's mutations

Each mutation was restated on e4d4014 where the code moved. Each ran as a checked patch through reviewer.sh: git apply --check exit 0, the mutated tree built (cargo test -p toyos-cpuvuln --no-run exit 0), then cargo test -p toyos-cpuvuln ran, and git apply -R left git diff --quiet at exit 0. All 33 exit 101.

mutation test exit red
stibp_unret 101 hygon_decides_as_linux_does, zen1_forces_stibp_from_the_untrained_return_thunk
retbleed_smt_vuln 101 zen1_forces_stibp_from_the_untrained_return_thunk
tsa_guest_ucode 101 a_milan_kvm_guest_gives_the_tags_lines
bhi_guest 101 a_comet_lake_guest_cannot_know_its_hosts_smt, a_retpoline_guest_leaves_bhi_to_retpolines_only_with_rrsba_disabled, a_retpoline_guest_without_rrsba_leaves_bhi_to_retpolines, an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
its_guest 101 an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
baducode_guest 101 an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
bhi_dis_s 101 alder_lake_mitigates_bhi_in_hardware, the_t14s_no_bits_each_rule_out_their_bug
rrsba_ctrl 101 a_retpoline_guest_leaves_bhi_to_retpolines_only_with_rrsba_disabled
bhi_retpoline 101 a_retpoline_guest_without_rrsba_leaves_bhi_to_retpolines, a_retpoline_guest_leaves_bhi_to_retpolines_only_with_rrsba_disabled
rtm_always_abort 101 rtm_always_abort_decides_tsx_first, srbds_is_tsx_disabled_only_without_rtm_and_mmio
taa_verw_cond 101 comet_lake_clears_cpu_buffers_by_mds_no_and_fb_clear, srbds_is_tsx_disabled_only_without_rtm_and_mmio
mmio_verw 101 a_comet_lake_guest_cannot_know_its_hosts_smt, an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt, comet_lake_clears_cpu_buffers_by_mds_no_and_fb_clear, comet_lake_varies_one_fact_at_a_time, spectre_bad_microcode_includes_its_bound
srbds_tsxoff 101 rtm_absent_from_cpuid_is_rtm_off, srbds_is_tsx_disabled_only_without_rtm_and_mmio
gds_locked 101 firmware_can_lock_the_gds_mitigation
msbds_smt 101 an_msbds_only_cpu_reports_its_smt_as_mitigated
brtype17 101 hygon_decides_as_linux_does, zen1_by_btc_no_ssb_no_and_ibpb, zen1_forces_stibp_from_the_untrained_return_thunk, zen2_selects_by_smt
efer_vs_ibrs 101 a_turin_kvm_guest_gives_the_tags_lines, autoibrs_leaves_stibp_to_select
ibrs_fw_amd 101 a_zen2_guest_has_no_ibpb_brtype, hygon_decides_as_linux_does, zen1_forces_stibp_from_the_untrained_return_thunk, zen2_selects_by_smt
ls_cfg_family 101 amd_0x15_to_0x17_take_ssbd_from_the_ls_cfg_probe
sbpb_eax 101 a_turin_kvm_guest_gives_the_tags_lines, autoibrs_leaves_stibp_to_select
srso_family 101 srso_user_kernel_no_mitigates_only_the_vm_exit, zen3_by_srso_no_and_an_enumerated_ibpb_brtype, zen3_native_reads_its_probe_and_its_tsa_microcode
vmscape_guest 101 a_comet_lake_guest_cannot_know_its_hosts_smt, a_milan_kvm_guest_gives_the_tags_lines, a_zen2_guest_has_no_ibpb_brtype, an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
zen4_guard 101 zen4_reads_its_row_of_the_tsa_microcode_table
r2_ls_cfg_or 101 amd_0x15_to_0x17_take_ssbd_from_the_ls_cfg_probe, hygon_decides_as_linux_does
r2_ls_cfg_range 101 amd_0x15_to_0x17_take_ssbd_from_the_ls_cfg_probe
r2_mds_hv 101 an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
r2_srbds_hv 101 a_comet_lake_guest_cannot_know_its_hosts_smt, an_intel_guest_cannot_know_its_mitigations_or_its_hosts_smt
r2_stibp_always_on 101 amd_stibp_always_on_is_strict_without_the_untrained_return_thunk, autoibrs_leaves_stibp_to_select
r2_tsa_bits 101 tsa_is_ruled_out_only_by_both_its_no_bits
r2_uk_order 101 srso_user_kernel_no_mitigates_only_the_vm_exit
r2_uk_swap 101 srso_user_kernel_no_mitigates_only_the_vm_exit
r2_vmscape 101 srso_user_kernel_no_mitigates_only_the_vm_exit
doubled_intel_check 101 an_unknown_vendors_retbleed_is_unmitigated, bad_microcode_is_intel_family_6s

Three have no code left to mutate: pbrsb (the SpectreV2::None arms: spectre_v2 is now Option<SpectreV2>), mmio_dead_clear (deleted), and rfds_clear (Rfds::Off is gone).

The sweep

sweep.rs (Rust, no dependencies) mutates lib.rs and table.rs in a copy of the crate. It drops each &&/|| operand, negates or forces each if condition and match guard, flips each ==/!=, shifts each </<=/>/>=/..= boundary, drops each unary ! and | operand and each assignment statement (every |= into spec_ctrl among them), flips each bool literal, forces each .then receiver, gives each single-line match arm its neighbour's value, and changes each line string. The constants and the table rows are out of scope. For each mutant it builds, runs cargo test, and restores the file byte for byte.

run tree mutants built, exit 101 survived does not build
1 blocker fixtures in 732 642 84 6
2 sweep fixtures and deletions in 719 716 2 1
3 e4d4014 719 718 0 1

How the 86 survivors of runs 1 and 2 ended:

  • 80 have a fixture, whose expected values are read from the tag at the lines its doc cites:
    • the T14 with each *_NO bit;
    • RFDS by RFDS_CLEAR and by row;
    • Comet Lake with one fact varied at a time, with RTM absent from CPUID, and as a guest;
    • Zen1 with BTC_NO, with AMD_SSB_NO and without AMD_IBPB;
    • a Zen2 guest;
    • Milan with SRSO_NO and with IBPB_BRTYPE enumerated;
    • native Turin;
    • a guest with VERW_CLEAR;
    • Zhaoxin with SMT, an unknown vendor, and the probes' vendor scope;
    • Ident::new's family and model;
    • the bad-microcode rows' model and stepping;
    • lines at the MSBDS/L1TF/TAA/retbleed/SMT-disabled arms.
  • 6 were in code now deleted as equivalent: msbds_only's b.mds && (only MDS's line reads it), native_brtype's amd && (only SRSO reads it, and only AMD's SRSO row is family 0x17), and the four mutants of tsa_microcode's vendor guard (its only caller has TSA, which only AMD 0x19's row names).
  • None was left as an equivalent mutant.

The one mutant that does not build drops | None from Some(SpectreV2::Retpoline) | None => Retbleed::None, and the exhaustive match refuses it. Run 1's other five unbuildable mutants were the tool lengthening a byte string, whose length is its type. It now replaces the last byte instead, and all five are red in run 3.

Files, in /Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/602/:

  • sweep.rs, sweep.md (every survivor and its end), and results-final.tsv (one row per mutant);
  • reviewer.sh, with patches/ and logs/ for the table above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 5 of #602 at e4d4014. CI host is green on this head: run 36570314156, headSha e4d4014, conclusion success. Its log runs toyos_cpuvuln's unit tests and ends "Host: 54 step(s), all green". Net +3244/−0:

  • production 1452: lib.rs 1092, table.rs 346, manifests and lock 14;
  • tests 1739: tests.rs 1671, fixtures 68;
  • issues 53.

The reviewer measured on a scratch copy of the crate at e4d4014 with host cargo test only. Every mutant was restored byte for byte, and diff -r against the worktree was empty after each batch. The oracle is tag Ubuntu-6.8.0-142.142 at 53e5d07a, read directly.

Round-3 blockers

All are closed on the implementer's reviewer.out: check 0, build 0, test 101, tree clean.

  • Zen1/0x15/0x16 STIBP (patches A and B): CLOSED. stibp_unret and retbleed_smt_vuln exit 101.
  • TSA guest microcode: CLOSED. tsa_guest_ucode exits 101.
  • Intel-guest BHI, ITS and bad-microcode arms: CLOSED. bhi_guest, its_guest and baducode_guest exit 101.
  • BHI_DIS_S, RRSBA_DIS_S and BHI: Retpoline: CLOSED. bhi_dis_s, rrsba_ctrl and bhi_retpoline exit 101.
  • RtmAlwaysAbort, TAA, MMIO, SRBDS TsxOff and FullLocked: CLOSED. rtm_always_abort, taa_verw_cond, mmio_verw, srbds_tsxoff and gds_locked exit 101.

The round-3 NOTEs are done: the dead CLEAR_CPU_BUF block, SpectreV2::None and the doubled Intel check are gone; msbds_smt exits 101; Hygon and itlb_multihit are decided.

Sweep

  • The implementer's sweep.rs, rebuilt and re-run over all 719 mutants. 718 build and exit 101, 0 survive, and 1 does not build (lib.rs:717 | None). This matches results-final.tsv.

  • lib.rs bit constants, 1 << N → 1 << N+1 (59 mutants). The sweep excludes this class. 26 are killed, 30 survive, and 3 do not build. All 59 positions match the tag's cpufeatures.h, scattered.c:30-31,52-53 and msr-index.h.

  • Table data (545 mutants). The sweep excludes this class too. The mutants delete each row, swap each adjacent pair, drop each flag, raise each model constant by 1, lower each bad-microcode bound by 1, raise each stepping by 1, raise each TSA key and minimum by 1, and move each flag bit up by 1. 78 are killed and 467 survive. Every table was checked against the tag:

    • the whitelist and blacklist (92 rows) diff clean against common.c:1182-1344, in order and with their stepping bounds;
    • the 62 model constants match intel-family.h;
    • the 20 bad-microcode rows match intel.c:141-163, and the 16 TSA rows match amd.c:488-503.

    The surviving data mutants are rows no test reaches, not transcription errors, except the row order below.

  • Numeric literals and operators in code (45 mutants). 37 are killed and 8 survive:

    • 3 change a decision on a CPU that exists (BLOCKER);
    • table.rs:59 ANY 0→1 is the COMETLAKE_L survivor again;
    • 4 are equivalent on every CPU that exists: lib.rs:184's extended-family mask 0xff→0x7f and →0x0f, lib.rs:611 =0x17→=0x18, and lib.rs:617 0x19→0x18 (AMD has no family 0x18).

Fixtures against the tag

Each new fixture's facts were walked through the tag's code. Every expected line and state matches, and none rests on running the crate:

  • Hygon's inline capture and state;
  • itlb_multihit over FEAT_CTL (feat_ctl.c:119-184, bugs.c:3091-3102);
  • Alder Lake BHI_DIS_S;
  • both RRSBA guests;
  • Zen1 STIBP with and without AMD_STIBP;
  • Comet Lake's TAA/MMIO/SRBDS variants and guest;
  • the KABYLAKE guest;
  • Zhaoxin, Centaur, Silvermont and the unknown vendor;
  • Rome native and guest, Genoa, native Milan and Turin;
  • milan-kvm.txt and turin-kvm.txt.

The T14's feat_ctl is labelled as inferred at tests.rs:31-34, and no captured T14 line reads it: PSCHANGE_MC_NO makes itlb_multihit "Not affected". Every pre-boot value consistent with S0's vmx flag gives the same vmx: unlocked, or LOCKED | VMX_ENABLED_OUTSIDE_SMX. So the inference cannot move an asserted line.

BLOCKER

Each patch below leaves 49/49 green. Each proposed test was run by the reviewer: green at e4d4014, red on its patch.

  • toyos-cpuvuln/src/table.rs:344 — the patch (id.model & 0xf) << 4 → (id.model & 0x7) << 4 survives. Four of the 16 TSA rows (0xa0082, 0xa1081, 0xa7080, 0xa70c0) key a model nibble ≥ 8, and no test has one. A wrong key drops VERW_CLEAR and CLEAR_CPU_BUF on those Zen3/Zen4 parts. Add a native family 0x19 model 0x08 stepping 2 (signature: 0x00a0_0f82, otherwise native_milan). Microcode 0x0a00820d gives Tsa::Full and 0x0a00820c gives Tsa::UcodeNeeded (amd.c:479,490,621).

  • toyos-cpuvuln/src/table.rs:63,251-252 — three patches survive. Each turns COMETLAKE_L stepping 0 into the any-stepping row, which carries MMIO_SBDS (so SRBDS) and GDS:

    • & !((1 << min) - 1) → & !(1 << min);
    • swapping rows 251 and 252;
    • table.rs:59 ANY: u32 = 0 → 1.

    a_blacklist_row_matches_only_its_steppings and the_first_matching_blacklist_row_decides never test stepping 0. Add COMETLAKE at signature: 0x000a_0660, giving srbds and gds None, and at 0x000a_0661, giving Some(Srbds::Full) and Some(Gds::UcodeNeeded) (common.c:1310-1311,1480-1483,1521-1523).

  • toyos-cpuvuln/src/lib.rs:811 — the patch id.family < 0x19 → id.family < 0x18 survives. Hygon 0x18, decided this round, is the only family between, and hygon_decides_as_linux_does runs only with SMT. Add the Dhyana (0x0090_0f01, AMD_IBPB, AMD_IBRS and AMD_STIBP, ls_cfg_readable: Some(true)) without SMT and with 0x80000021:EAX IBPB_BRTYPE. It must give Srso::SmtDisabled and "Mitigation: SMT disabled" (bugs.c:2689-2691,3284-3285).

NOTE

  • toyos-cpuvuln/src/tests.rs:31-34 — nothing on this branch owes the read of 0x3A. The S0 procedure (issues/kernel/the-kernel-mitigates-what-linux-mitigates-on-the-t14.md:112-118) reads 0x10a, 0x48, 0x123 and 0x122 only. Name rdmsr -a 0x3a in the owed capture, and replace the constant with its value when it lands.

REMOVE

  • PR body — "Run 1 of the sweep left 84 survivors. 78 got a fixture, and the other six were in code now deleted.", "Hygon, refused until round 3, is decided", "it replaces the round-3 refusal issue" and "Round 3's other three mutations have no code left to mutate…" are review chronology in what becomes main's merge record.
  • toyos-cpuvuln/Cargo.toml:1 — ", like toyos-userbound" compares against another crate and rots when that crate changes.

SEND BACK

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

Japabu and others added 2 commits September 29, 2026 15:45
…ng 0 and Hygon without SMT

Three tests, expected values from Ubuntu-6.8.0-142.142:
- Zen3 0x00a00f82: microcode 0x0a00820d is Tsa::Full, 0x0a00820c
  UcodeNeeded (amd.c:479-490,621).
- COMETLAKE_L 0x000a0660 has no SRBDS or GDS, 0x000a0661 has both
  (common.c:1310-1311,1480-1483,1521-1523).
- A Dhyana without SMT and with IBPB_BRTYPE is Srso::SmtDisabled
  (bugs.c:2689-2691,3284-3285).

Each of five mutations exits 101 on its test. The owed S0 capture reads
MSR 0x3A. Cargo.toml's description no longer names another crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Round 5 mutations at e767338. Each patch applied with git apply --check then git apply, cargo test -p toyos-cpuvuln exit 101 on the named new test, reverted with git apply -R, tree clean.

key_nibble (exit 101)

diff --git a/toyos-cpuvuln/src/table.rs b/toyos-cpuvuln/src/table.rs
--- a/toyos-cpuvuln/src/table.rs
+++ b/toyos-cpuvuln/src/table.rs
@@ -341,6 +341,6 @@ pub(crate) fn tsa_microcode(id: &Ident, microcode: u32) -> bool {
         (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;
+    let key = (id.family - 0xf) << 16 | (id.model >> 4) << 12 | (id.model & 0x7) << 4 | id.stepping;
     ROWS.iter().find(|&&(k, _)| k == key).is_some_and(|&(_, min)| microcode >= min)
 }

stepping_mask (exit 101)

diff --git a/toyos-cpuvuln/src/table.rs b/toyos-cpuvuln/src/table.rs
--- a/toyos-cpuvuln/src/table.rs
+++ b/toyos-cpuvuln/src/table.rs
@@ -60,7 +60,7 @@ 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)
+    ((1 << (max + 1)) - 1) & !(1 << min)
 }
 
 pub(crate) struct Row<F> {

any_one (exit 101)

diff --git a/toyos-cpuvuln/src/table.rs b/toyos-cpuvuln/src/table.rs
--- a/toyos-cpuvuln/src/table.rs
+++ b/toyos-cpuvuln/src/table.rs
@@ -56,7 +56,7 @@ 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;
+const ANY: u32 = 1;
 
 /// `X86_STEPPINGS(mins, maxs)`, `GENMASK(maxs, mins)` (`asm/cpu_device_id.h:59`).
 const fn steppings(min: u32, max: u32) -> u32 {

row_swap (exit 101)

diff --git a/toyos-cpuvuln/src/table.rs b/toyos-cpuvuln/src/table.rs
--- a/toyos-cpuvuln/src/table.rs
+++ b/toyos-cpuvuln/src/table.rs
@@ -248,8 +248,8 @@ const BLACKLIST: &[Row<Bl>] = &[
     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(COMETLAKE_L, steppings(0x0, 0x0), bl!(MMIO | RETBLEED | 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)),

hygon_family (exit 101)

diff --git a/toyos-cpuvuln/src/lib.rs b/toyos-cpuvuln/src/lib.rs
--- a/toyos-cpuvuln/src/lib.rs
+++ b/toyos-cpuvuln/src/lib.rs
@@ -808,7 +808,7 @@ pub fn decide(facts: &Facts) -> Decision {
     // `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 {
+        if ibpb_brtype && id.family < 0x18 && !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 {

Failing tests: key_nibble -> a_tsa_row_keyed_by_a_model_nibble_of_8_or_more_is_read; stepping_mask, any_one, row_swap -> a_blacklist_row_for_stepping_zero_stands_before_its_any_stepping_row; hygon_family -> hygon_without_smt_and_with_ibpb_brtype_has_srso_smt_disabled.

Gates: cargo test -p toyos-cpuvuln exit 0 (52 passed); clippy exit 0; cargo run -- --ci host exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

@Japabu

Japabu commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: round-4 review's three blockers closed at e767338 — the five named mutations (key_nibble, stepping_mask, any_one, row_swap, hygon_family) each exit 101 on their new test (issuecomment-5891796254); the full 719-mutant sweep and the table diff against the tag were independently re-run by the round-4 reviewer. Landing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs

@Japabu
Japabu added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 5c6d12a Sep 29, 2026
1 check passed
@Japabu
Japabu deleted the wt/toyos-secs1 branch September 29, 2026 14:23
Japabu added a commit that referenced this pull request Sep 29, 2026
One conflict, issues/kernel/the-kernel-still-creates-threads.md's
stages. main (#607) dropped K2 and rewrote K4 with the console wire the
boot and the panic path keep; this branch dropped K5, since iod goes
with the kernel's write-back queue here and no kthread::spawn is added.
The stages are #607's K4 and K6, K6 blocked on K4 alone.

#607's hunks in every-driver-is-still-in-the-kernel.md (audio and
virtio-gpu re-scoped) and the-kernel-is-small-interrupts-post-and-
threads-wait.md (xHCI's MSI-X table) touch lines this branch does not
change and merge as main wrote them; #602's toyos-cpuvuln and its
sourcegate row likewise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant