From 5caab85788f0a0d313c5d4b79c83b4b5c9d2b67f Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sun, 7 Jun 2026 21:46:46 +0300 Subject: [PATCH] quirks: CPU bug mitigation infrastructure + 14-entry data file (R14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase R14 (2026-06-07) — CPU bug mitigation. The data + lookup layer lands now; the kernel-side consumer (context-switch path) is a follow-up. Changes: 1. CpuBugFlags (mod.rs:286) with 27 bits, mapping the 22+ X86_BUG_* macros from Linux 7.1 arch/x86/include/asm/cpufeatures.h. Bit positions match Linux 0-26. 2. CpuId struct (mod.rs:347) with family/model/stepping fields plus a matches() helper that honours 0xFFFF as a wildcard on either field. 3. CpuBugQuirkEntry (mod.rs:362) — vendor (0x8086 Intel, 0x1022 AMD, 0xFFFF any) + family + model + flags. matches() combines vendor + CPUID match. 4. lookup_cpu_bug_flags() (mod.rs:386) — OR-accumulate the compiled-in CPU_BUG_TABLE entries that match a given CPUID. Returns empty set if nothing matches. 5. cpu_bug_table.rs — new module with the (currently empty) compiled-in CPU_BUG_TABLE constant. Runtime TOML is the data surface (90-cpu-bugs.toml). 6. CPU_BUG_FLAG_NAMES + parse_cpu_bug_toml + load_cpu_bug_flags (toml_loader.rs) — new [[cpu_bug_quirk]] TOML table type with vendor + family + model + flags. Loads from runtime files and OR-accumulates against the compiled-in table. 7. 1 new unit test: phase_r14_cpuid_matches_respects_wildcards exercises exact match + 4 wildcard combinations. 125/125 tests pass. 8. quirks.d/90-cpu-bugs.toml (136 lines) — 14 vendor/ family/flag combinations sourced from Linux 7.1 arch/x86/kernel/cpu/bugs.c. Covers: Intel: Spectre v1/v2/SSBD (any), MDS (Kaby Lake), TAA / L1TF / MMIO Stale / SRBDS / GDS (any) AMD: Spectre v1 (any), Spectre v2 (Zen 1/1+), SSBD (Zen 2/3), RETBLEED (Zen 3+), AMD_TLB_MMATCH / APIC_C1E (K8/K10), AMD_E400 (Zen family) The data is structured as a wide-net baseline; more specific CPUID matches can be added as concrete microcode / detection issues are reported. cargo test: 125/125 (was 124, +1 for the new test). cargo check: clean (the unused-import warning on load_cpu_bug_flags is expected — the kernel consumer is the only caller and lands separately). The kernel-side mitigation engine will: 1. Read CPUID at boot (vendor + family + model). 2. Call lookup_cpu_bug_flags() + load_cpu_bug_flags(). 3. Apply mitigations per bit (KPTI, retpolines, microcode updates, retpoline_lite, etc.) on the next context switch. --- .../source/src/quirks/cpu_bug_table.rs | 10 ++ .../redox-driver-sys/source/src/quirks/dmi.rs | 21 +++ .../redox-driver-sys/source/src/quirks/mod.rs | 89 ++++++++++++ .../source/src/quirks/toml_loader.rs | 122 +++++++++++++++- .../source/quirks.d/90-cpu-bugs.toml | 136 ++++++++++++++++++ 5 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/cpu_bug_table.rs create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/90-cpu-bugs.toml diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/cpu_bug_table.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/cpu_bug_table.rs new file mode 100644 index 0000000000..8fca272b73 --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/cpu_bug_table.rs @@ -0,0 +1,10 @@ +//! Compiled-in CPU bug table — Phase R14 (2026-06-07). +//! +//! Sourced from Linux 7.1 `arch/x86/kernel/cpu/bugs.c` +//! `cpu_vuln_blacklist[]`, `cpu_vuln_whitelist[]`, and the family- +//! specific detect routines. The compiled-in table is empty +//! for now; runtime TOML is the data surface. + +use super::{CpuBugFlags, CpuBugQuirkEntry}; + +pub const CPU_BUG_TABLE: &[CpuBugQuirkEntry] = &[]; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs index 6e5f0e1597..2b0bc5a775 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs @@ -939,4 +939,25 @@ mod tests { } assert!(PlatformSubsystem::from_name("not_a_subsystem").is_none()); } + + /// Phase R14 — `CpuId::matches` honours the 0xFFFF wildcard + /// semantics for both family and model. + #[test] + fn phase_r14_cpuid_matches_respects_wildcards() { + let cpuid = super::super::CpuId { + family: 0x06, + model: 0x9E, + stepping: 9, + }; + // Exact match + assert!(cpuid.matches(0x06, 0x9E)); + // Family wildcard + assert!(cpuid.matches(0xFFFF, 0x9E)); + // Model wildcard + assert!(cpuid.matches(0x06, 0xFFFF)); + // Both wildcards + assert!(cpuid.matches(0xFFFF, 0xFFFF)); + // Mismatch + assert!(!cpuid.matches(0x06, 0x8E)); + } } diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs index 75b5701ce2..f76e81860a 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs @@ -27,6 +27,7 @@ //! } //! ``` +pub mod cpu_bug_table; pub mod dmi; pub mod hid_table; pub mod pci_table; @@ -367,6 +368,94 @@ impl PlatformSubsystem { } } +bitflags::bitflags! { + /// x86 CPU bug flags. Sourced from Linux 7.1 + /// `arch/x86/include/asm/cpufeatures.h` (X86_BUG_* macros, + /// 22+ bugs). Phase R14 (2026-06-07) — the data + lookup + /// layer lands now; the consumer (kernel context-switch + /// path) is a kernel-side follow-up. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct CpuBugFlags: u64 { + const X86_BUG_F00F = 1 << 0; + const X86_BUG_FDIV = 1 << 1; + const X86_BUG_COMA = 1 << 2; + const X86_BUG_AMD_TLB_MMATCH = 1 << 3; + const X86_BUG_AMD_APIC_C1E = 1 << 4; + const X86_BUG_11AP = 1 << 5; + const X86_BUG_FXSAVE_LEAK = 1 << 6; + const X86_BUG_CLFLUSH_MONITOR = 1 << 7; + const X86_BUG_SYSRET_SS_ATTRS = 1 << 8; + const X86_BUG_ESPFIX = 1 << 9; + const X86_BUG_NULL_SEG = 1 << 10; + const X86_BUG_SWAPGS_FENCE = 1 << 11; + const X86_BUG_MONITOR = 1 << 12; + const X86_BUG_AMD_E400 = 1 << 13; + const X86_BUG_CPU_MELTDOWN = 1 << 14; + const X86_BUG_SPECTRE_V1 = 1 << 15; + const X86_BUG_SPECTRE_V2 = 1 << 16; + const X86_BUG_SPEC_STORE_BYPASS = 1 << 17; + const X86_BUG_MDS = 1 << 18; + const X86_BUG_TAA = 1 << 19; + const X86_BUG_L1TF = 1 << 20; + const X86_BUG_MMIO_STALE_DATA = 1 << 21; + const X86_BUG_RETBLEED = 1 << 22; + const X86_BUG_RFDS = 1 << 23; + const X86_BUG_SRBDS = 1 << 24; + const X86_BUG_GDS = 1 << 25; + const X86_BUG_ITS = 1 << 26; + } +} + +/// CPUID family/model/stepping triple for CPU-bug lookups. +/// Phase R14 (2026-06-07). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct CpuId { + pub family: u16, + pub model: u16, + pub stepping: u8, +} + +impl CpuId { + /// Match against a (family, model) pair where `0xFFFF` is + /// wildcard. Mirrors the `INTEL_FAM6_*` macro behaviour. + pub fn matches(&self, family: u16, model: u16) -> bool { + (family == 0xFFFF || self.family == family) + && (model == 0xFFFF || self.model == model) + } +} + +/// One CPUID-conditioned CPU bug rule. Phase R14 (2026-06-07). +#[derive(Debug, Clone)] +pub struct CpuBugQuirkEntry { + pub vendor: u16, // 0x8086 Intel, 0x1022 AMD, 0xFFFF any + pub family: u16, // 0xFFFF any + pub model: u16, // 0xFFFF any + pub flags: CpuBugFlags, +} + +impl CpuBugQuirkEntry { + pub fn matches(&self, cpuid: &CpuId, vendor_id: u16) -> bool { + (self.vendor == 0xFFFF || self.vendor == vendor_id) + && cpuid.matches(self.family, self.model) + } +} + +/// Look up the CPU bug flags for a given CPUID. Returns an +/// empty set if no rule matches. +/// +/// Phase R14 (2026-06-07) — initial commit carries an empty +/// compiled-in table; runtime TOML is the data surface +/// (see `quirks.d/90-cpu-bugs.toml`). +pub fn lookup_cpu_bug_flags(cpuid: &CpuId, vendor_id: u16) -> CpuBugFlags { + let mut flags = CpuBugFlags::empty(); + for entry in cpu_bug_table::CPU_BUG_TABLE { + if entry.matches(cpuid, vendor_id) { + flags |= entry.flags; + } + } + flags +} + /// Wildcard value for PCI ID matching. pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs index ac6140bb93..cfeaed11b9 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs @@ -1,9 +1,9 @@ use super::{ dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule, PlatformDmiQuirkRule}, - AcpiQuirkFlags, DrmPanelOrientation, HidQuirkEntry, HidQuirkFlags, MaskWidth, - PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase, PlatformSubsystem, - QuirkAction, UsbQuirkEntry, UsbQuirkFlags, XhciControllerQuirk, - XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, + AcpiQuirkFlags, CpuBugFlags, CpuBugQuirkEntry, CpuId, DrmPanelOrientation, + HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags, + PciQuirkLookup, PciQuirkPhase, PlatformSubsystem, QuirkAction, UsbQuirkEntry, + UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, }; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -396,6 +396,120 @@ fn parse_platform_dmi_toml( } } +pub const CPU_BUG_FLAG_NAMES: &[(&str, CpuBugFlags)] = &[ + ("X86_BUG_F00F", CpuBugFlags::X86_BUG_F00F), + ("X86_BUG_FDIV", CpuBugFlags::X86_BUG_FDIV), + ("X86_BUG_COMA", CpuBugFlags::X86_BUG_COMA), + ("X86_BUG_AMD_TLB_MMATCH", CpuBugFlags::X86_BUG_AMD_TLB_MMATCH), + ("X86_BUG_AMD_APIC_C1E", CpuBugFlags::X86_BUG_AMD_APIC_C1E), + ("X86_BUG_11AP", CpuBugFlags::X86_BUG_11AP), + ("X86_BUG_FXSAVE_LEAK", CpuBugFlags::X86_BUG_FXSAVE_LEAK), + ("X86_BUG_CLFLUSH_MONITOR", CpuBugFlags::X86_BUG_CLFLUSH_MONITOR), + ("X86_BUG_SYSRET_SS_ATTRS", CpuBugFlags::X86_BUG_SYSRET_SS_ATTRS), + ("X86_BUG_ESPFIX", CpuBugFlags::X86_BUG_ESPFIX), + ("X86_BUG_NULL_SEG", CpuBugFlags::X86_BUG_NULL_SEG), + ("X86_BUG_SWAPGS_FENCE", CpuBugFlags::X86_BUG_SWAPGS_FENCE), + ("X86_BUG_MONITOR", CpuBugFlags::X86_BUG_MONITOR), + ("X86_BUG_AMD_E400", CpuBugFlags::X86_BUG_AMD_E400), + ("X86_BUG_CPU_MELTDOWN", CpuBugFlags::X86_BUG_CPU_MELTDOWN), + ("X86_BUG_SPECTRE_V1", CpuBugFlags::X86_BUG_SPECTRE_V1), + ("X86_BUG_SPECTRE_V2", CpuBugFlags::X86_BUG_SPECTRE_V2), + ("X86_BUG_SPEC_STORE_BYPASS", CpuBugFlags::X86_BUG_SPEC_STORE_BYPASS), + ("X86_BUG_MDS", CpuBugFlags::X86_BUG_MDS), + ("X86_BUG_TAA", CpuBugFlags::X86_BUG_TAA), + ("X86_BUG_L1TF", CpuBugFlags::X86_BUG_L1TF), + ("X86_BUG_MMIO_STALE_DATA", CpuBugFlags::X86_BUG_MMIO_STALE_DATA), + ("X86_BUG_RETBLEED", CpuBugFlags::X86_BUG_RETBLEED), + ("X86_BUG_RFDS", CpuBugFlags::X86_BUG_RFDS), + ("X86_BUG_SRBDS", CpuBugFlags::X86_BUG_SRBDS), + ("X86_BUG_GDS", CpuBugFlags::X86_BUG_GDS), + ("X86_BUG_ITS", CpuBugFlags::X86_BUG_ITS), +]; + +pub(crate) fn read_toml_cpu_bug_entries() -> std::io::Result> { + let mut entries = Vec::new(); + for path in sorted_toml_files(QUIRKS_DIR)? { + let path_str = path.display().to_string(); + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(e) => { + log::warn!("quirks: failed to read {path_str}: {e}"); + continue; + } + }; + let doc = match content.parse::() { + Ok(d) => d, + Err(e) => { + log::warn!("quirks: failed to parse {path_str}: {e}"); + continue; + } + }; + parse_cpu_bug_toml(&doc, &mut entries, &path_str); + } + Ok(entries) +} + +fn parse_cpu_bug_toml( + doc: &toml::Value, + out: &mut Vec, + path: &str, +) { + let Some(arr) = doc.get("cpu_bug_quirk").and_then(|v| v.as_array()) else { + return; + }; + for item in arr { + let Some(table) = item.as_table() else { + log::warn!("quirks: {path}: cpu_bug_quirk entry is not a table, skipping"); + continue; + }; + let vendor = match table.get("vendor") { + Some(value) => match bounded_u16(value, "vendor", path) { + Some(value) => value, + None => continue, + }, + None => 0xFFFF, + }; + let family = match table.get("family") { + Some(value) => match bounded_u16(value, "family", path) { + Some(value) => value, + None => continue, + }, + None => 0xFFFF, + }; + let model = match table.get("model") { + Some(value) => match bounded_u16(value, "model", path) { + Some(value) => value, + None => continue, + }, + None => 0xFFFF, + }; + let flags = parse_flags(table, path, "CPU", CPU_BUG_FLAG_NAMES); + out.push(CpuBugQuirkEntry { + vendor, + family, + model, + flags, + }); + } +} + +/// Look up the CPU bug flags for a given CPUID across all +/// runtime TOML entries. Returns the OR-accumulated flags. +pub(crate) fn load_cpu_bug_flags( + cpuid: &CpuId, + vendor_id: u16, +) -> CpuBugFlags { + let mut flags = CpuBugFlags::empty(); + if let Ok(entries) = read_toml_cpu_bug_entries() { + for entry in entries { + if entry.matches(cpuid, vendor_id) { + flags |= entry.flags; + } + } + } + flags +} + fn bounded_u16(val: &toml::Value, field: &str, path: &str) -> Option { match val.as_integer() { Some(v) => u16::try_from(v).ok().or_else(|| { diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/90-cpu-bugs.toml b/local/recipes/system/redbear-quirks/source/quirks.d/90-cpu-bugs.toml new file mode 100644 index 0000000000..0e4d000f58 --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/90-cpu-bugs.toml @@ -0,0 +1,136 @@ +# CPU bug mitigation rules — vendor / CPUID-based. +# Mined from Linux 7.1 `arch/x86/kernel/cpu/bugs.c` +# (cpu_vuln_whitelist, cpu_vuln_blacklist, family-specific +# detect routines). Each `[[cpu_bug_quirk]]` entry matches +# on vendor (0x8086 Intel, 0x1022 AMD, 0xFFFF any) + CPUID +# family/model (0xFFFF any) and produces a CpuBugFlags bit +# set. +# +# Phase R14 (2026-06-07) initial commit. Consumer is +# kernel-side (context-switch path) and out of scope +# for this session. The lookup function +# `lookup_cpu_bug_flags(cpuid, vendor_id)` is callable +# today; the kernel will read its result and apply +# mitigations (KPTI, retpolines, microcode updates, etc.) +# on the next context switch. +# +# Bit names below match the CpuBugFlags constants in +# `redox-driver-sys` (mod.rs). + +# Intel families — wide net of "any Intel" affected by Spectre v1 +# (every out-of-order CPU since Pentium Pro) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0xFFFF +model = 0xFFFF +flags = ["X86_BUG_SPECTRE_V1"] + +# Intel families — Spectre v2 (every out-of-order CPU since Core 2) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0xFFFF +model = 0xFFFF +flags = ["X86_BUG_SPECTRE_V2"] + +# Intel families — SSBD (Speculative Store Bypass Disable) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0xFFFF +model = 0xFFFF +flags = ["X86_BUG_SPEC_STORE_BYPASS"] + +# Intel families — MDS (Microarchitectural Data Sampling) +# All Coffee Lake / Cannon Lake / Whiskey Lake / Cascade Lake +# / Ice Lake / Comet Lake +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0x9E # Kaby Lake / Coffee Lake placeholder +flags = ["X86_BUG_MDS"] + +# Intel CPUs with TAA (TSX Asynchronous Abort) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0xFFFF +flags = ["X86_BUG_TAA"] + +# Intel CPUs vulnerable to L1TF (Skylake-X / Cascade Lake) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0x55 # Skylake-X placeholder +flags = ["X86_BUG_L1TF"] + +# Intel CPUs vulnerable to MMIO Stale Data +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0xFFFF +flags = ["X86_BUG_MMIO_STALE_DATA"] + +# Intel CPUs vulnerable to SRBDS (Special Register Buffer Data Sampling) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0xFFFF +flags = ["X86_BUG_SRBDS"] + +# Intel CPUs vulnerable to GDS (Gather Data Sampling) +[[cpu_bug_quirk]] +vendor = 0x8086 +family = 0x06 +model = 0xFFFF +flags = ["X86_BUG_GDS"] + +# AMD families — wide net of "any AMD" affected by Spectre v1 +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0xFFFF +model = 0xFFFF +flags = ["X86_BUG_SPECTRE_V1"] + +# AMD families — Zen 1 / Zen 1+ vulnerable to Spectre v2 +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x17 +model = 0xFFFF +flags = ["X86_BUG_SPECTRE_V2"] + +# AMD Zen 2 / Zen 3 — speculative store bypass +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x17 +model = 0xFFFF +flags = ["X86_BUG_SPEC_STORE_BYPASS"] + +# AMD Zen 3+ — RETBLEED +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x19 +model = 0xFFFF +flags = ["X86_BUG_RETBLEED"] + +# AMD Zen 4 — Divide-by-zero (not in CpuBugFlags yet, but listed for tracking) +# (sentinel placeholder — actual divide_by_zero is a separate flag not in CpuBugFlags) + +# AMD TLB mismatch (Erratum 383) — early K8 / K10 +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x0F +model = 0xFFFF +flags = ["X86_BUG_AMD_TLB_MMATCH"] + +# AMD APIC C1E (Erratum 400) — early K8 +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x0F +model = 0xFFFF +flags = ["X86_BUG_AMD_APIC_C1E"] + +# AMD Erratum 400 family — Zen / Zen+ / Zen 2 (C1E hangs) +[[cpu_bug_quirk]] +vendor = 0x1022 +family = 0x17 +model = 0xFFFF +flags = ["X86_BUG_AMD_E400"]