Repository navigation
A pure function decides a CPU's speculation mitigations, and each boot says its verdict - #1243
Conversation
…s the read section's reader orders the recheck - AArch64's counter is `ISB; MRS; ISB` again. The Arm ARM (K.a D12.2.2) lets a counter read happen speculatively and out of order against other instructions; nothing architectural orders it before a later store that does not depend on its value, so x86's argument does not carry over, and no AArch64 bench could measure the change. Its doc no longer says it reads as the x86-64 one does. - The latch no longer says nothing orders its recheck after `inside`'s read: the kernel reads the same page through `cpu::counter_ordered`, whose trailing fence does. Whether the recheck may be answered first is `inside`'s choice, and the clock page's header says which reader pairs a line with a counter read just after it was replaced. - `issues/the-kernel-relies-on-an-amd-lfence-it-never-makes-dispatch-serializing.md` records that `cpu::rdtsc_ordered` and `cpu::wrmsr_fence` rely on an AMD LFENCE the kernel never makes dispatch serializing; #1243 is its exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1243 at 3b07ea1 (round 1)ArchitectureThe split is right:
Size: about 925 lines of production code in What I checked against the vendors' documentsIntel.
The table and the row for 806C1.
AMD family 17h.
AMD LFENCE.
The note that after #1240 only Required
Should the benches' words be pinned now?Yes: in this PR, from the readback already queued (without the microcode revision, per the orchestrator), as a follow-up commit before landing.
What the readback must showT14:
AMD bench:
Both: the Suggestions
SEND BACK. Of the required items, 1–3 and 6 change code. 4, 5 and 7 change only comments, records and the commit message. 8 and the metal readback are evidence that must arrive before landing. |
…#MC and #DF install it whatever GS holds The kernel ran no `swapgs`: GS held this CPU's `PerCpu` from boot on, and every entry's first stores went through `gs:`. Ring 3 replaces the GS base with a segment load (`mov gs, 0x1b`: the user data descriptor, base 0), so its next syscall stored near address 0 at CPL 0 on its own stack. `SYSCALL` switches no stack and `#PF` has no IST, so the fault's frame was pushed to the stack pointer the program chose, a kernel address if it chose one, and every handler after it addressed the same base. Found by reading in #1251; the previous commit's two rows red on that kernel. Now `IA32_GS_BASE` is Ring 3's own while it runs and the kernel's while the kernel runs, with `IA32_KERNEL_GS_BASE` the other: - `SYSCALL` comes only from Ring 3 and swaps first and on its way out. The image gate on the shipping `syscall_entry` now requires it to open `swapgs`, `cld`, the save and the switch. - Every other gate swaps when its frame's `CS` is Ring 3's, and back before an `iretq` to Ring 3. That decision is sound because the kernel's CPL-0 spans with a user's base live (before `SYSCALL`'s `swapgs`, and from an exit's `swapgs` to its `SYSRET` or `IRETQ`) run with `IF` clear and no instruction that can fault: `SYSRET`'s `rcx` is canonical by the user half's placement, and every `iretq` frame is the CPU's or a validated entry's. No `#DB` lands there: nothing arms a debug register, `IA32_FMASK` clears `TF`, and a single-step trap after `MOV SS` is suppressed, not deferred (SDM Vol. 3A §7.8.3). - NMI, `#MC` and `#DF` can land in those spans and their frame does not say so. They take the kernel's base from `GDTR`, whose base is the block's own `gdt`, and write it to `IA32_GS_BASE` whatever it held; the NMI puts back the base it found before its `iretq`. They decide nothing: a test of the found base's sign would hold only while no thread can write its own GS base, and `WRGSBASE` writes any canonical one, the kernel half's included (`issues/fsgsbase-is-taken.md`). `#DF` and `#MC` never return, so any dispatched vector on an IST stack goes through the new `fatal_entry`. Speculation (CVE-2019-1125): the gate's `swapgs` is conditional, so an `LFENCE` follows the join, before any load through GS, which is Intel's placement for both a skipped and an extra `swapgs` ("Speculative Behavior of SWAPGS and Segment Registers", ID 758371). The unconditional `SYSCALL` swap needs none, the exits load nothing through GS after their `swapgs` and end in a serializing `IRETQ` or in `SYSRET`, and the IST entries have no branch before their serializing `WRMSR` (SDM Vol. 3A §11.3). On AMD the fence orders dispatch only where `LFENCE` is dispatch-serializing (AMD 70586 G-2, MSR C001_1029[1]), which #1243 sets; AMD's own statement on SWAPGS was not reachable to read. The switch moves no GS base: every descriptor Ring 3 may load into GS has base 0 (asserted at compile time beside the GDT), AMD's null load keeps the base it found, and `CR4.FSGSBASE` stays forbidden, so each CPU's `IA32_KERNEL_GS_BASE` starts at 0 and every thread's user GS base is 0. The switch's save and restore of it belongs with taking FSGSBASE. AArch64 has no GS and is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the CPUs up, and ToyOS's policy marked STIBP is no longer asked of an Intel CPU with enhanced IBRS: "there is no need to use STIBP when IBRS is set" (Speculative Execution Side Channel Mitigations §2.4.1.3). The T14 and 806F8 drop it. Where AMD prescribes a protection between programs for every program (Branch Type Confusion §6.1.1's STIBP for Jmp2Ret, the SRSO paper §3's IBPB across user/user), the decision keeps ToyOS's policy, which applies it for a program that asks, and its verdict carries the deviation: the line reads "STIBP for programs that ask (policy)". Each rule quotes AMD's unconditional guidance. The policy and its reason are on the manual's Trust page. AMD's Spectre v2 rule no longer asks for a return predictor fill beside SMEP: Branch Type Confusion §5 keeps the return address predictor safe with "a RAP stuffing sequence (mitigation V2-3) and/or SMEP", and SMEP, which every CPU holds, keeps Ring 0 from fetching the user addresses user code's calls leave there (70586 G-3). `ReturnPredictorFill` goes with it. A CPU's `smt` is whether another CPU the kernel brought up shares its core, by the cores the topology places their APIC IDs in, rather than CPUID.0BH or 1FH EBX[15:0], which the SDM (Vol. 1, Chapter 21) says is the count as shipped and must not enumerate the topology. So each CPU records its words and registers at bring-up, and once `boot_aps` has installed the topology the BSP says cpu0's lines and those of each later CPU that differ. `cpu::threads_per_core` goes. The words line now carries the registers that show what is in force (`mcu_opt_ctrl`, `de_cfg2`, `c001_1029`, `-` where not read) and C001_1029 as `apply` found it before setting LFENCE dispatch serializing, which tells the bit firmware set from the bit the kernel set. What is in force is `Decision::in_force` on those registers, with `ALWAYS` the one list of what the kernel does on every CPU. `apply` stops the machine if the bit does not hold after its write, as a CPU without SMEP stops it: the clocksource's `RDTSCP; LFENCE` relies on it. The `mitigations` metal row judges every CPU that logged lines, each against its own words and registers, and holds a bench's words to its pinned words. The AMD bench's are pinned from its boot of 3b07ea1, but the microcode revision, which the `microcode` row holds; the search over register readings goes. `checks::the_benches_words_decide_as_their_vendors_rows` holds the pinned words to AMD's documents for Family 17h Model 60h, and `checks::mitigations_verdict` holds the judge to a later CPU's own lines and refuses a later CPU whose LFENCE bit reads clear, words other than the bench's, and a machine no bench is. Both issue records' exits are again as main has them; what is built is under Built and what is unmet under Left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3b07ea1 to
a2350e6
Compare
Review of #1243 at ab39884 (round 2)ScopeThis round checks only two things: whether round 1's required items R1–R8 are resolved, and whether the code that changed since ArchitectureThe round-2 shape is right:
Two problems remain:
Round 1's items
Checked against the vendors' documentsAMD Spectre v2: SMEP alone. Correct.
C001_1029. Correct as written.
The judge. Correct.
The pins. Correct. Both The MMIO rule, the substantive claim. It is right for the T14. I checked it against Intel's "Processor MMIO Stale Data Vulnerabilities" and the consolidated affected-processor table, both fetched today.
RequiredB1. Every x86-64 boot now panics on the BSP before
B2. The retbleed verdict and the Trust page do not state the exposure the ruling accepts.
B3. The MMIO comment misstates Intel's condition, and the case that follows from it is unconditional.
Suggestions
Evidence
SEND BACK:
R8 and the metal readback must then arrive at the fixed head. |
Addendum to the round-2 review: run 43077064338 confirms B1I read the guest row with The first CPU stopped two seconds into QEMU, before any other serial line: The 480 bytes are the first growth of The harness reports R8 stays open. It needs a new dispatch once B1 is fixed, and the |
… from the CPUs up, and ToyOS's policy marked STIBP is no longer asked of an Intel CPU with enhanced IBRS: "there is no need to use STIBP when IBRS is set" (Speculative Execution Side Channel Mitigations §2.4.1.3). The T14 and 806F8 drop it. Where AMD prescribes a protection between programs for every program (Branch Type Confusion §6.1.1's STIBP for Jmp2Ret, the SRSO paper §3's IBPB across user/user), the decision keeps ToyOS's policy, which applies it for a program that asks, and its verdict carries the deviation: the line reads "STIBP for programs that ask (policy)". Each rule quotes AMD's unconditional guidance. The policy and its reason are on the manual's Trust page. AMD's Spectre v2 rule no longer asks for a return predictor fill beside SMEP: Branch Type Confusion §5 keeps the return address predictor safe with "a RAP stuffing sequence (mitigation V2-3) and/or SMEP", and SMEP, which every CPU holds, keeps Ring 0 from fetching the user addresses user code's calls leave there (70586 G-3). `ReturnPredictorFill` goes with it. A CPU's `smt` is whether another CPU the kernel brought up shares its core, by the cores the topology places their APIC IDs in, rather than CPUID.0BH or 1FH EBX[15:0], which the SDM (Vol. 1, Chapter 21) says is the count as shipped and must not enumerate the topology. So each CPU records its words and registers at bring-up, and once `boot_aps` has installed the topology the BSP says cpu0's lines and those of each later CPU that differ. `cpu::threads_per_core` goes. The words line now carries the registers that show what is in force (`mcu_opt_ctrl`, `de_cfg2`, `c001_1029`, `-` where not read) and C001_1029 as `apply` found it before setting LFENCE dispatch serializing, which tells the bit firmware set from the bit the kernel set. What is in force is `Decision::in_force` on those registers, with `ALWAYS` the one list of what the kernel does on every CPU. `apply` stops the machine if the bit does not hold after its write, as a CPU without SMEP stops it: the clocksource's `RDTSCP; LFENCE` relies on it. The `mitigations` metal row judges every CPU that logged lines, each against its own words and registers, and holds a bench's words to its pinned words. The AMD bench's are pinned from its boot of 3b07ea1, but the microcode revision, which the `microcode` row holds; the search over register readings goes. `checks::the_benches_words_decide_as_their_vendors_rows` holds the pinned words to AMD's documents for Family 17h Model 60h, and `checks::mitigations_verdict` holds the judge to a later CPU's own lines and refuses a later CPU whose LFENCE bit reads clear, words other than the bench's, and a machine no bench is. Both issue records' exits are again as main has them; what is built is under Built and what is unmet under Left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…el's SMT exposure named, and PSDP as Intel conditions it `record` grew a `Vec` on the boot CPU from `after_console`, before `mm::init` turns the allocator on, so every x86-64 boot panicked with "memory allocation of 480 bytes failed" before the IDT existed. Guest run 43077064338 shows it, and both benches' boots of ab39884 did not come back. Now `apply` reads a CPU's words and registers once and returns them as a `Copy` value, which owns no heap. The boot CPU keeps its value in a one-slot static, and each later CPU its own in a `CpuSlice` that `arch::size` sizes with the other per-CPU tables after the allocator is up. The module names no `alloc`. Retbleed on AMD no longer puts STIBP among what can read "mitigated". Branch Type Confusion §6.1.1 asks it of every program for the kernel's own Jmp2Ret ("If the code on the sibling thread cannot be trusted, software should set STIBP to 1 or disable SMT"), so what the policy leaves is a verdict's `SmtExposure`. The line names it, after whatever is in force: "; SMT vulnerable: STIBP only for programs that ask (policy)", or "this CPU has no STIBP" on Family 15h, Zen and Zen+, which the guidance's appendix footnotes 5 and 6 say have none. The Trust page states the consequence in one sentence. The module header, the record's Built and the kernel page stop calling it protection between programs; SRSO's IBPB keeps its "(policy)" mark, since it is. The MMIO rule asks the primary propagator's mitigation, a non-secret MMIO read after each secret one ("Mitigating PSDP Propagation"), unless the part enumerates `MDS_NO`, `FBSDP_NO`, and `TAA_NO` or no TSX, which is the condition `PSDP_NO`'s paragraph gives; only then is a part whose only propagator is the primary one not affected. The rule says why it departs from the article's selection pseudocode: `FB_CLEAR` is for parts the fill buffer propagator affects. The record's SBDR item cites `FBSDP_NO`'s paragraph and says why the T14 keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ab39884 to
8d57dcf
Compare
Review of #1243 at 8d57dcf (round 3)ScopeThis round checks only round 2's blockers B1–B3 and the code changed to fix them, which is commit The fix adds +228/−102 lines in 9 files:
ArchitectureThe fix keeps the round-2 design and puts each piece in the right place:
I found nothing that needs a simpler representation. B1: resolved by inspection. The boot proof is pending.No allocation between
The AP table is sized and filled safely.
Does
A build gate is not warranted.
B2: resolved. Checked against AMD's BTC guidance (PID 221404394-A, fetched today).§6.1.1 "SMT Safety". The comment's quotation matches: "If the code on the sibling thread cannot be trusted, software should set STIBP to 1 or disable SMT to ensure SMT safety when using Jmp2Ret." The appendix's footnotes. Its "Available Mitigations" row marks Jmp2Ret "X⁵" under Fam 15h (Models 00–7Fh) and "X⁶" under Fam 17h Models 00h–2Fh and 50h–5Fh. The footnote text is drawn in a CID font, and I decoded it through its ToUnicode map:
So "Family 15h, Zen and Zen+" in the code comment, the Trust page and the record is correct. The code.
The prose.
B3: resolved. Checked against Intel's "Processor MMIO Stale Data Vulnerabilities" (fetched today).The "Mitigating PSDP Propagation". It prescribes "an additional read of some non-secret data from a valid MMIO register on the primary bus with the same cache line offset (address mod 64)". The code.
The T14 is unchanged.
The citations.
The SBDR item under Left. It now cites the FindingsNo blockers. Suggestions (optional):
EvidenceHost.
There are 18 control runs, each with exit 101, and the tree is restored after each. Those runs are 8 pure, 7 bench and 3 judge. The two controls new this round expose the two defects B2 and B3 named. Guest: run 43082719564 at
Metal:
The T14:
The AMD bench:
LAND, pending guest run 43082719564 and then the metal readback, each showing what is listed above. |
… and two stale comments The benches are down, so the `mitigations` and `microcode` rows' readback on both benches is recorded under the lines record's Left as owed on metal, with what main must show there: each bench's exact lines, the T14's `IA32_MCU_OPT_CTRL` opt-out bits clear, the AMD bench's `DE_CFG2[1]` and C001_1029[1] set and its `c001_1029_found` reported. The record's Built names the `; SMT vulnerable:` clause. `the_primary_propagator_alone_matters_where_fill_buffers_can_be_read` holds the `MDS_NO` term of Intel's exemption. `arch::size`'s comment stops listing two of the four tables it sizes, and `Read`'s second statement of the module header's no-heap constraint goes. Nothing an image builds changes: the kernel's code, the harness and the guest programs are as at 8d57dcf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#MC and #DF install it whatever GS holds The kernel ran no `swapgs`: GS held this CPU's `PerCpu` from boot on, and every entry's first stores went through `gs:`. Ring 3 replaces the GS base with a segment load (`mov gs, 0x1b`: the user data descriptor, base 0), so its next syscall stored near address 0 at CPL 0 on its own stack. `SYSCALL` switches no stack and `#PF` has no IST, so the fault's frame was pushed to the stack pointer the program chose, a kernel address if it chose one, and every handler after it addressed the same base. Found by reading in #1251; the previous commit's two rows red on that kernel. Now `IA32_GS_BASE` is Ring 3's own while it runs and the kernel's while the kernel runs, with `IA32_KERNEL_GS_BASE` the other: - `SYSCALL` comes only from Ring 3 and swaps first and on its way out. The image gate on the shipping `syscall_entry` now requires it to open `swapgs`, `cld`, the save and the switch. - Every other gate swaps when its frame's `CS` is Ring 3's, and back before an `iretq` to Ring 3. That decision is sound because the kernel's CPL-0 spans with a user's base live (before `SYSCALL`'s `swapgs`, and from an exit's `swapgs` to its `SYSRET` or `IRETQ`) run with `IF` clear and no instruction that can fault: `SYSRET`'s `rcx` is canonical by the user half's placement, and every `iretq` frame is the CPU's or a validated entry's. No `#DB` lands there: nothing arms a debug register, `IA32_FMASK` clears `TF`, and a single-step trap after `MOV SS` is suppressed, not deferred (SDM Vol. 3A §7.8.3). - NMI, `#MC` and `#DF` can land in those spans and their frame does not say so. They take the kernel's base from `GDTR`, whose base is the block's own `gdt`, and write it to `IA32_GS_BASE` whatever it held; the NMI puts back the base it found before its `iretq`. They decide nothing: a test of the found base's sign would hold only while no thread can write its own GS base, and `WRGSBASE` writes any canonical one, the kernel half's included (`issues/fsgsbase-is-taken.md`). `#DF` and `#MC` never return, so any dispatched vector on an IST stack goes through the new `fatal_entry`. Speculation (CVE-2019-1125): the gate's `swapgs` is conditional, so an `LFENCE` follows the join, before any load through GS, which is Intel's placement for both a skipped and an extra `swapgs` ("Speculative Behavior of SWAPGS and Segment Registers", ID 758371). The unconditional `SYSCALL` swap needs none, the exits load nothing through GS after their `swapgs` and end in a serializing `IRETQ` or in `SYSRET`, and the IST entries have no branch before their serializing `WRMSR` (SDM Vol. 3A §11.3). On AMD the fence orders dispatch only where `LFENCE` is dispatch-serializing (AMD 70586 G-2, MSR C001_1029[1]), which #1243 sets; AMD's own statement on SWAPGS was not reachable to read. The switch moves no GS base: every descriptor Ring 3 may load into GS has base 0 (asserted at compile time beside the GDT), AMD's null load keeps the base it found, and `CR4.FSGSBASE` stays forbidden, so each CPU's `IA32_KERNEL_GS_BASE` starts at 0 and every thread's user GS base is 0. The switch's save and restore of it belongs with taking FSGSBASE. AArch64 has no GS and is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator: landing under the benches-down rule. Review r3 LAND. Guest run 43082719564 at 8d57dcf: success, and its log carries the boot proof review r3 asked for: no EARLY PANIC, |
…t says its verdict Nothing decided which speculative-execution vulnerabilities a CPU has or what its vendor prescribes against each. `kernel::mitigation::decide` maps a CPU's vendor, signature, microcode revision, whether it is a guest, whether its core has a second thread, and its enumeration words (CPUID 7.0 EBX/EDX, 7.2 EDX, 0x80000008 EBX, 0x80000021 EAX/ECX and IA32_ARCH_CAPABILITIES where enumerated) to a verdict per vulnerability: not affected, or the mitigations the vendor's guidance prescribes and which of them the CPU does not enumerate. Each rule cites the Intel article or AMD bulletin or white paper it reads. Enumeration decides where the guidance lets it; otherwise Intel's affected-processor table, carried as its machine-readable list gave it on 2026-09-04, decides for a CPU it lists, and a CPU it omits is reported as assumed affected. AMD's documents name affected families, so a newer family is held to its `_NO` bits and reported as assumed affected without them. Every CPU reads its words after its microcode loads, as Intel's enumeration article asks. The first CPU logs them and one line per vulnerability: `not affected`, `mitigated: ` and what by, or `vulnerable: needs ` and what is missing, with what is in force and what the CPU does not enumerate. A later CPU logs only where its words or standing differ. What is in force is what the kernel already does everywhere (branchless user-pointer clamping, SMEP, no guest) and what the CPU's own MCU_OPT_CTRL, DE_CFG2 and C001_1029 show. Syscall entry, exit and context switch are unchanged. One control is applied. On AMD, LFENCE holds back later instructions only where MSR C001_1029[1] is set (AMD 70586, "Software Techniques for Managing Speculation", mitigation G-2) or CPUID Fn8000_0021_EAX[2] says it always does (APM Vol. 3, LFENCE). The kernel never set it, though the paper's V1-1 bounds-check barrier and the clocksource's ordered TSC read, `rdtsc_ordered`'s `RDTSCP; LFENCE`, rely on that. The decision now names it on spectre_v1 for every AMD family the paper gives the MSR (10h, 12h-17h and later) unless CPUID says LFENCE always serializes; 0Fh and 11h always serialize. A guest cannot write its host's MSR, so a guest is reported lacking it. Each CPU sets the bit right after its microcode loads, before the clocksource reads the TSC, and its line reads the bit back. The metal judge holds it set. The `mitigations` metal row holds a boot's lines to the decision on the words that boot logged; `checks::mitigations_verdict` holds that judge to a boot's lines and to the ways one could fall short of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the CPUs up, and ToyOS's policy marked STIBP is no longer asked of an Intel CPU with enhanced IBRS: "there is no need to use STIBP when IBRS is set" (Speculative Execution Side Channel Mitigations §2.4.1.3). The T14 and 806F8 drop it. Where AMD prescribes a protection between programs for every program (Branch Type Confusion §6.1.1's STIBP for Jmp2Ret, the SRSO paper §3's IBPB across user/user), the decision keeps ToyOS's policy, which applies it for a program that asks, and its verdict carries the deviation: the line reads "STIBP for programs that ask (policy)". Each rule quotes AMD's unconditional guidance. The policy and its reason are on the manual's Trust page. AMD's Spectre v2 rule no longer asks for a return predictor fill beside SMEP: Branch Type Confusion §5 keeps the return address predictor safe with "a RAP stuffing sequence (mitigation V2-3) and/or SMEP", and SMEP, which every CPU holds, keeps Ring 0 from fetching the user addresses user code's calls leave there (70586 G-3). `ReturnPredictorFill` goes with it. A CPU's `smt` is whether another CPU the kernel brought up shares its core, by the cores the topology places their APIC IDs in, rather than CPUID.0BH or 1FH EBX[15:0], which the SDM (Vol. 1, Chapter 21) says is the count as shipped and must not enumerate the topology. So each CPU records its words and registers at bring-up, and once `boot_aps` has installed the topology the BSP says cpu0's lines and those of each later CPU that differ. `cpu::threads_per_core` goes. The words line now carries the registers that show what is in force (`mcu_opt_ctrl`, `de_cfg2`, `c001_1029`, `-` where not read) and C001_1029 as `apply` found it before setting LFENCE dispatch serializing, which tells the bit firmware set from the bit the kernel set. What is in force is `Decision::in_force` on those registers, with `ALWAYS` the one list of what the kernel does on every CPU. `apply` stops the machine if the bit does not hold after its write, as a CPU without SMEP stops it: the clocksource's `RDTSCP; LFENCE` relies on it. The `mitigations` metal row judges every CPU that logged lines, each against its own words and registers, and holds a bench's words to its pinned words. The AMD bench's are pinned from its boot of 3b07ea1, but the microcode revision, which the `microcode` row holds; the search over register readings goes. `checks::the_benches_words_decide_as_their_vendors_rows` holds the pinned words to AMD's documents for Family 17h Model 60h, and `checks::mitigations_verdict` holds the judge to a later CPU's own lines and refuses a later CPU whose LFENCE bit reads clear, words other than the bench's, and a machine no bench is. Both issue records' exits are again as main has them; what is built is under Built and what is unmet under Left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ors a part has The T14's boot of 3b07ea1 logged its words, and they are pinned beside the AMD bench's, but the microcode revision, which the `microcode` row holds. `checks::the_benches_words_decide_as_their_vendors_rows` holds what they decide to CPUID 806C1's row of Intel's affected-processor list on its listed microcode 0xbe, which the T14 runs, and `checks::mitigations_verdict` refuses the T14's words with `ARCH_CAPABILITIES` read as 0 under lines that agree with them: the record's mutation, which only the pin catches. The pin showed the MMIO rule wrong for this CPU. It asked VERW wherever the table lists a part and found it lacking without `FB_CLEAR`. The T14 runs the microcode Intel lists as its mitigation and enumerates `FBSDP_NO` and no `FB_CLEAR`. Intel's list gives Shared Buffers Data Sampling and Shared Buffers Data Read one combined cell, "MCU", and on the T14, which has neither MDS nor TSX, only the read can apply. In "Processor MMIO Stale Data Vulnerabilities", VERW's fill-buffer overwrite blocks the fill buffer propagator; against Shared Buffers Data Read and the sideband propagator, the microcode that has RDRAND, RDSEED and EGETKEY overwrite the uncore buffers "is also relevant", under the control `SRBDS_CTRL` enumerates. So the rule asks VERW where the part has the fill buffer propagator and the microcode's overwrite where it has Shared Buffers Data Read, and the T14's line reads `mitigated: microcode's RDRAND and RDSEED mitigation`. The synthetic 806C1 row takes the bits the T14 enumerates. What the decision does not name, that a program with MMIO access can read other cores' stale MMIO data, is recorded under the record's Left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…el's SMT exposure named, and PSDP as Intel conditions it `record` grew a `Vec` on the boot CPU from `after_console`, before `mm::init` turns the allocator on, so every x86-64 boot panicked with "memory allocation of 480 bytes failed" before the IDT existed. Guest run 43077064338 shows it, and both benches' boots of ab39884 did not come back. Now `apply` reads a CPU's words and registers once and returns them as a `Copy` value, which owns no heap. The boot CPU keeps its value in a one-slot static, and each later CPU its own in a `CpuSlice` that `arch::size` sizes with the other per-CPU tables after the allocator is up. The module names no `alloc`. Retbleed on AMD no longer puts STIBP among what can read "mitigated". Branch Type Confusion §6.1.1 asks it of every program for the kernel's own Jmp2Ret ("If the code on the sibling thread cannot be trusted, software should set STIBP to 1 or disable SMT"), so what the policy leaves is a verdict's `SmtExposure`. The line names it, after whatever is in force: "; SMT vulnerable: STIBP only for programs that ask (policy)", or "this CPU has no STIBP" on Family 15h, Zen and Zen+, which the guidance's appendix footnotes 5 and 6 say have none. The Trust page states the consequence in one sentence. The module header, the record's Built and the kernel page stop calling it protection between programs; SRSO's IBPB keeps its "(policy)" mark, since it is. The MMIO rule asks the primary propagator's mitigation, a non-secret MMIO read after each secret one ("Mitigating PSDP Propagation"), unless the part enumerates `MDS_NO`, `FBSDP_NO`, and `TAA_NO` or no TSX, which is the condition `PSDP_NO`'s paragraph gives; only then is a part whose only propagator is the primary one not affected. The rule says why it departs from the article's selection pseudocode: `FB_CLEAR` is for parts the fill buffer propagator affects. The record's SBDR item cites `FBSDP_NO`'s paragraph and says why the T14 keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and two stale comments The benches are down, so the `mitigations` and `microcode` rows' readback on both benches is recorded under the lines record's Left as owed on metal, with what main must show there: each bench's exact lines, the T14's `IA32_MCU_OPT_CTRL` opt-out bits clear, the AMD bench's `DE_CFG2[1]` and C001_1029[1] set and its `c001_1029_found` reported. The record's Built names the `; SMT vulnerable:` clause. `the_primary_propagator_alone_matters_where_fill_buffers_can_be_read` holds the `MDS_NO` term of Intel's exemption. `arch::size`'s comment stops listing two of the four tables it sizes, and `Read`'s second statement of the module header's no-heap constraint goes. Nothing an image builds changes: the kernel's code, the harness and the guest programs are as at 8d57dcf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1254 made every metal row say which benches it runs on. The `mitigations` row's judge holds a boot to its own bench's pinned words, and it ran on both benches, so it is `On::Every`, as the `microcode` row it rides with is. The comment stops listing the benches the field now names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8c203f6 to
003c0e3
Compare
Problem
Nothing decided which speculative-execution vulnerabilities a CPU has or what its vendor prescribes, and no boot said where the machine stands (
issues/a-pure-function-decides-a-cpus-speculation-mitigations-from-its-vendors-guidance.md,issues/each-boot-prints-the-vulnerabilities-lines-linux-prints.md). Separately, the kernel never made AMD's LFENCE dispatch serializing, though its own ordered TSC read relies on it.Behavior
kernel::mitigation::decideis pure and host-tested. It maps a CPU's vendor, signature, microcode revision, guest status, whether another online CPU shares its core, and its enumeration words to a verdict per vulnerability. Each verdict is either not affected, or the mitigations the vendor prescribes plus which of them the CPU does not enumerate._NObits.mcu_opt_ctrl,de_cfg2,c001_1029, and C001_1029 as found before the kernel's write), and one line per vulnerability:not affected,mitigated: …, orvulnerable: needs …; has …; this CPU enumerates no …. A later CPU logs only where it differs.smtcomes from the topology of the CPUs actually brought up, by APIC ID, not from CPUID.0BH/1FH EBX[15:0]. SDM Vol. 1, Chapter 21 says that field must not be used to enumerate topology.rdtsc_ordered(RDTSCP; LFENCE) relies on it, as AMD's V1-1 bounds-check barrier does.spectre_v1for every AMD family the paper gives the MSR (10h, 12h to 17h, and later), unless CPUID says LFENCE always serializes; 0Fh and 11h always serialize.FB_CLEAR, or byMD_CLEARplusL1D_FLUSHwhere MDS affects the part.SRBDS_CTRLenumerates its control.MDS_NO,FBSDP_NO, andTAA_NOor no TSX, the conditionPSDP_NO's paragraph gives.FB_CLEAR. The rule follows the article's fill-buffer section, which givesFB_CLEARonly to parts the fill buffer propagator affects.FBSDP_NOand noFB_CLEAR. Intel's list has one combined SBDS/SBDR cell, "MCU", for it, and on the T14 (no MDS, no TSX) only SBDR can apply. Its line is nowmitigated: microcode's RDRAND and RDSEED mitigation.The manual's kernel page gains a "Speculative execution" section, and its Trust page a section titled "Speculative execution applied on request".
Tests
kernel/pure/mitigation.rs. Among them are rules for Intel 806C1, 806F8 and 506E3, AMD Family 17h Model 60h, Zen+ and 19h, the policy marking and the SMT exposure, the primary propagator's condition, andwhat_is_in_force_is_what_the_registers_show.Copy, so they own no heap, and its slot is a static. The guest row below is the check that the head boots.checks::the_benches_words_decide_as_their_vendors_rows: holds each bench's pinned words, as its own boot at 3b07ea1 logged them, to the lines its vendor's documents give that CPU. For the T14 that is CPUID 806C1's row of Intel's list on its listed microcode; for the AMD bench, AMD's documents for Family 17h Model 60h.mitigationsrow (T14 and AMD bench) judges every CPU that logged lines.microcoderow holds.decide's verdict on those words, with what its logged registers show in force.checks::mitigations_verdict: holds the judge to a boot's own lines and to a later CPU's own lines. It refuses a later CPU whose LFENCE bit reads clear, a misread whose lines agree with it (only the pin catches that), the T14 withARCH_CAPABILITIESread as 0 (the record's mutation), a machine no bench is, a guest, and missing words, lines or registers.Evidence
The head is
003c0e3e2, rebased onto main3b93f3770(r6 index).mitigationsrowon: On::Every. A metal row declares its benches, a boot names its bench, and judges name the one they judged #1254 made every metal row name its benches, and this row ran on both.toyos-checks(52),toyos-buildclippy, the pure tests (19) and the x86-64 kernel clippy all exit 0 (log).8c203f6e8.8c203f6e8differs from8d57dcf87only in two comments, one assertion inside the pure module's test module, and the lines record (r5).The checks below ran at
8d57dcf87. Index: r4/index.md.Host checks, chosen for what the change touches. All exit 0 at
8d57dcf87(log):cargo test -p kernel --lib mitigation: 19 passed.cargo test --test toyos-checks: 47 passed. This includesmitigations_verdict,the_benches_words_decide_as_their_vendors_rowsandmicrocode_verdict.sched-check, andx86_64-unknown-noneby default and withboot-actuators,test-actuators. clippy ontoyos-build --all-targets. All with the adopted lints and-D warnings. AArch64 is untouched.--ci host, because nothing in the build system or harness mechanics changed. The merge queue runs it.Negative controls, all at
8d57dcf87(r4 index). Each is applied withgit apply, tested, and reversed withgit apply -R; the tree's digest is identical before and after. Each builds and fails with exit 101:ab39884e2;Guest:
ab39884e2, run 43077064338 (log) failed withEARLY PANIC … memory allocation of 480 bytes failed. That is the defect this round fixes.8d57dcf87, whose kernel the head carries unchanged, run 43082719564 (std_threads_count_the_cpus_and_give_back_ended_threads --nocapture) is green (log).EARLY PANICand nomitigation:panic.mitigations: cpu0 words Intel … hypervisor=true smt=false … mcu_opt_ctrl=- de_cfg2=- c001_1029=- c001_1029_found=-and its 17 lines. The second vCPU, being alike, logged none.Metal is not a landing condition: both benches are down. Both benches'
metalcaseboots at3b07ea1ddpassed, and their cpu0 words lines, minusmicrocode=, are the pinned words (T14, AMD). The boots ofab39884e2did not come back (B1).Owed on metal, recorded under the lines record's Left, for one batched boot of main once a bench returns: the
mitigationsandmicrocoderows, onemetalcaseboot each on the T14 and the AMD bench. Main must show:mitigation:panic. A later CPU logs lines only where its registers differ.T14_BENCH_LINES, andmcu_opt_ctrlhas bits 0 (RNGDS_MITG_DIS) and 4 (GDS_MITG_DIS) clear. A set bit is to be reported, because those lines assume both clear.AMD_BENCH_LINES, andde_cfg2andc001_1029each have bit 1 set. The value ofc001_1029_foundis to be reported: it tells whether firmware had already set the bit.Earlier rounds: r1 at
3b07ea1dd, r2 ata2350e613, r3 atab39884e2.Risks and open items
ab39884e2,recordgrew aVecon the boot CPU before the allocator was up, so every x86-64 boot panicked early. Both benches' boots of it hung, and they have not come back since. This head fixes that. Run 43082719564 boots its kernel under KVM; no boot of it on metal exists yet.c001_1029_foundfield shows which.🤖 Generated with Claude Code