⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/security/
spectre.rs

1//! Retpoline / Spectre Mitigations
2//!
3//! Provides software and hardware mitigations for Spectre-class speculative
4//! execution vulnerabilities.
5//!
6//! ## Spectre v1 (Bounds Check Bypass)
7//!
8//! - [`bounds_mask`]: branchless index clamping that produces a safe index even
9//!   under mis-speculation.
10//! - [`speculation_barrier`]: serialising instruction that halts speculative
11//!   execution past this point.
12//!
13//! ## Spectre v2 (Branch Target Injection)
14//!
15//! - **IBRS** (Indirect Branch Restricted Speculation): restricts speculative
16//!   targets of indirect branches to a curated set.
17//! - **IBPB** (Indirect Branch Prediction Barrier): flushes the Branch Target
18//!   Buffer on context switches.
19//! - **STIBP** (Single Thread Indirect Branch Predictors): prevents cross-SMT
20//!   branch-target poisoning.
21//! - **Retpoline**: compiler-based mitigation that replaces indirect calls with
22//!   a construct that never speculatively follows the real target.
23//!
24//! ## Architecture Support
25//!
26//! - **x86_64**: IBRS/IBPB/STIBP via IA32_SPEC_CTRL (MSR 0x48) and
27//!   IA32_PRED_CMD (MSR 0x49). Feature detection through CPUID leaf 7.
28//! - **AArch64**: CSV2 (Cache Speculation Variant 2) detection via
29//!   ID_AA64PFR0_EL1. Barriers via DSB SY + ISB.
30//! - **RISC-V**: FENCE.I as speculation barrier.
31
32use core::sync::atomic::{AtomicBool, Ordering};
33
34use crate::error::KernelError;
35
36// ---------------------------------------------------------------------------
37// Feature detection flags
38// ---------------------------------------------------------------------------
39
40/// Whether the CPU supports IBRS (IA32_SPEC_CTRL bit 0).
41static IBRS_SUPPORTED: AtomicBool = AtomicBool::new(false);
42/// Whether the CPU supports IBPB (IA32_PRED_CMD).
43static IBPB_SUPPORTED: AtomicBool = AtomicBool::new(false);
44/// Whether the CPU supports STIBP (IA32_SPEC_CTRL bit 1).
45static STIBP_SUPPORTED: AtomicBool = AtomicBool::new(false);
46/// Whether IBRS is currently enabled.
47static IBRS_ENABLED: AtomicBool = AtomicBool::new(false);
48/// Whether the CPU has hardware Spectre-v2 mitigation (e.g., eIBRS,
49/// CSV2, or similar micro-architectural fix).
50static HW_MITIGATED: AtomicBool = AtomicBool::new(false);
51
52// ---------------------------------------------------------------------------
53// x86_64 MSR / CPUID constants
54// ---------------------------------------------------------------------------
55
56/// IA32_SPEC_CTRL MSR -- IBRS (bit 0), STIBP (bit 1).
57#[cfg(all(target_arch = "x86_64", target_os = "none"))]
58const MSR_SPEC_CTRL: u32 = 0x48;
59/// IA32_PRED_CMD MSR -- IBPB (bit 0).
60#[cfg(all(target_arch = "x86_64", target_os = "none"))]
61const MSR_PRED_CMD: u32 = 0x49;
62
63/// CPUID leaf 7, EDX bit 26 -- IBRS / IBPB supported.
64#[cfg(target_arch = "x86_64")]
65const CPUID_IBRS_IBPB_BIT: u32 = 1 << 26;
66/// CPUID leaf 7, EDX bit 27 -- STIBP supported.
67#[cfg(target_arch = "x86_64")]
68const CPUID_STIBP_BIT: u32 = 1 << 27;
69/// CPUID leaf 7, EDX bit 29 -- IA32_ARCH_CAPABILITIES available.
70#[cfg(target_arch = "x86_64")]
71const CPUID_ARCH_CAP_BIT: u32 = 1 << 29;
72
73// ---------------------------------------------------------------------------
74// Public query API
75// ---------------------------------------------------------------------------
76
77/// Returns `true` if IBRS is supported by the CPU.
78pub fn has_ibrs() -> bool {
79    IBRS_SUPPORTED.load(Ordering::Relaxed)
80}
81
82/// Returns `true` if IBPB is supported by the CPU.
83pub fn has_ibpb() -> bool {
84    IBPB_SUPPORTED.load(Ordering::Relaxed)
85}
86
87/// Returns `true` if STIBP is supported by the CPU.
88pub fn has_stibp() -> bool {
89    STIBP_SUPPORTED.load(Ordering::Relaxed)
90}
91
92/// Returns `true` if IBRS is currently enabled.
93pub fn is_ibrs_enabled() -> bool {
94    IBRS_ENABLED.load(Ordering::Relaxed)
95}
96
97/// Returns `true` if the CPU has hardware-level Spectre mitigations.
98pub fn is_hw_mitigated() -> bool {
99    HW_MITIGATED.load(Ordering::Relaxed)
100}
101
102// ---------------------------------------------------------------------------
103// Feature detection
104// ---------------------------------------------------------------------------
105
106/// Detect Spectre mitigation features via CPUID on x86_64.
107#[cfg(target_arch = "x86_64")]
108fn detect_features() {
109    // CPUID leaf 7, sub-leaf 0: EDX contains IBRS/IBPB/STIBP bits.
110    let edx: u32;
111
112    #[cfg(target_os = "none")]
113    {
114        // SAFETY: CPUID is read-only. We save/restore RBX for LLVM.
115        let edx_val: u32;
116        unsafe {
117            core::arch::asm!(
118                "push rbx",
119                "cpuid",
120                "pop rbx",
121                in("eax") 7u32,
122                in("ecx") 0u32,
123                lateout("eax") _,
124                lateout("ecx") _,
125                lateout("edx") edx_val,
126                options(nostack),
127            );
128        }
129        edx = edx_val;
130    }
131
132    #[cfg(not(target_os = "none"))]
133    {
134        // Host/CI stub: report all features as supported.
135        edx = CPUID_IBRS_IBPB_BIT | CPUID_STIBP_BIT | CPUID_ARCH_CAP_BIT;
136    }
137
138    if edx & CPUID_IBRS_IBPB_BIT != 0 {
139        IBRS_SUPPORTED.store(true, Ordering::Relaxed);
140        IBPB_SUPPORTED.store(true, Ordering::Relaxed);
141    }
142    if edx & CPUID_STIBP_BIT != 0 {
143        STIBP_SUPPORTED.store(true, Ordering::Relaxed);
144    }
145    if edx & CPUID_ARCH_CAP_BIT != 0 {
146        // If IA32_ARCH_CAPABILITIES is available, the CPU likely has
147        // enhanced IBRS (eIBRS) or other hardware fixes.
148        HW_MITIGATED.store(true, Ordering::Relaxed);
149    }
150}
151
152/// AArch64: detect CSV2 support via ID_AA64PFR0_EL1.
153#[cfg(target_arch = "aarch64")]
154fn detect_features() {
155    let pfr0: u64;
156
157    #[cfg(target_os = "none")]
158    // SAFETY: Reading the ID register is a read-only operation.
159    unsafe {
160        core::arch::asm!(
161            "mrs {}, id_aa64pfr0_el1",
162            out(reg) pfr0,
163            options(nomem, nostack),
164        );
165    }
166
167    #[cfg(not(target_os = "none"))]
168    let pfr0: u64 = 0x1 << 56; // stub: CSV2 supported
169
170    // CSV2 field: bits [59:56]. Values >= 1 indicate CSV2 mitigation.
171    let csv2 = (pfr0 >> 56) & 0xF;
172    if csv2 >= 1 {
173        HW_MITIGATED.store(true, Ordering::Relaxed);
174    }
175}
176
177/// RISC-V: no specific feature detection registers for Spectre.
178#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
179fn detect_features() {
180    // RISC-V does not define Spectre feature-detection registers in the
181    // base privileged specification. Rely on FENCE as a software barrier.
182}
183
184/// Fallback for other / host targets.
185#[cfg(not(any(
186    target_arch = "x86_64",
187    target_arch = "aarch64",
188    target_arch = "riscv32",
189    target_arch = "riscv64",
190)))]
191fn detect_features() {}
192
193// ---------------------------------------------------------------------------
194// Enable / Flush helpers
195// ---------------------------------------------------------------------------
196
197/// Enable IBRS (Indirect Branch Restricted Speculation).
198///
199/// After enabling, speculative targets of indirect branches executed in
200/// supervisor mode are restricted to those trained in supervisor mode.
201pub fn enable_ibrs() -> Result<(), KernelError> {
202    if !has_ibrs() {
203        return Err(KernelError::OperationNotSupported {
204            operation: "IBRS not supported by CPU",
205        });
206    }
207    if is_ibrs_enabled() {
208        return Ok(());
209    }
210
211    #[cfg(all(target_arch = "x86_64", target_os = "none"))]
212    {
213        // Write IA32_SPEC_CTRL.IBRS = 1.
214        // SAFETY: We confirmed IBRS support via CPUID. Writing MSR 0x48
215        // with bit 0 set enables IBRS. We pin EAX/ECX/EDX explicitly to
216        // avoid LLVM register conflicts (see wrmsr hazard note in memory).
217        unsafe {
218            core::arch::asm!(
219                "wrmsr",
220                in("ecx") MSR_SPEC_CTRL,
221                in("eax") 1u32,
222                in("edx") 0u32,
223                options(nomem, nostack),
224            );
225        }
226    }
227
228    IBRS_ENABLED.store(true, Ordering::Release);
229    Ok(())
230}
231
232/// Flush the Branch Target Buffer (issue IBPB).
233///
234/// Should be called on context switches between different security
235/// domains (e.g., different processes) to prevent branch-target poisoning.
236pub fn flush_btb() {
237    if has_ibpb() {
238        #[cfg(all(target_arch = "x86_64", target_os = "none"))]
239        {
240            // Write IA32_PRED_CMD.IBPB = 1.
241            // SAFETY: We confirmed IBPB support. Writing MSR 0x49 with bit 0
242            // invalidates indirect-branch predictions.
243            unsafe {
244                core::arch::asm!(
245                    "wrmsr",
246                    in("ecx") MSR_PRED_CMD,
247                    in("eax") 1u32,
248                    in("edx") 0u32,
249                    options(nomem, nostack),
250                );
251            }
252        }
253    }
254    // AArch64 and RISC-V rely on architectural guarantees or fences;
255    // there is no explicit BTB flush instruction.
256}
257
258/// Enable STIBP (Single Thread Indirect Branch Predictors).
259///
260/// Prevents sibling SMT threads from influencing indirect-branch predictions.
261pub fn enable_stibp() -> Result<(), KernelError> {
262    if !has_stibp() {
263        return Err(KernelError::OperationNotSupported {
264            operation: "STIBP not supported by CPU",
265        });
266    }
267
268    #[cfg(all(target_arch = "x86_64", target_os = "none"))]
269    {
270        // Read current IA32_SPEC_CTRL value, then set bit 1 (STIBP).
271        let lo: u32;
272        let hi: u32;
273        // SAFETY: Reading MSR 0x48 after confirming support is safe.
274        unsafe {
275            core::arch::asm!(
276                "rdmsr",
277                in("ecx") MSR_SPEC_CTRL,
278                out("eax") lo,
279                out("edx") hi,
280                options(nomem, nostack),
281            );
282        }
283        let new_lo = lo | (1 << 1); // set STIBP bit
284                                    // SAFETY: We only add the STIBP bit; all other bits are preserved.
285        unsafe {
286            core::arch::asm!(
287                "wrmsr",
288                in("ecx") MSR_SPEC_CTRL,
289                in("eax") new_lo,
290                in("edx") hi,
291                options(nomem, nostack),
292            );
293        }
294    }
295
296    Ok(())
297}
298
299// ---------------------------------------------------------------------------
300// Speculation barriers
301// ---------------------------------------------------------------------------
302
303/// Insert a full speculation barrier.
304///
305/// On x86_64, emits LFENCE. On AArch64, DSB SY + ISB. On RISC-V, FENCE.
306/// This prevents speculative execution from proceeding past this point.
307#[inline(always)]
308pub fn speculation_barrier() {
309    #[cfg(all(target_arch = "x86_64", target_os = "none"))]
310    // SAFETY: LFENCE is a serialising instruction with no side effects
311    // beyond ordering.
312    unsafe {
313        core::arch::asm!("lfence", options(nomem, nostack));
314    }
315
316    #[cfg(all(target_arch = "aarch64", target_os = "none"))]
317    // SAFETY: DSB SY + ISB form a full speculation barrier on AArch64.
318    unsafe {
319        core::arch::asm!("dsb sy", "isb", options(nomem, nostack));
320    }
321
322    #[cfg(all(
323        any(target_arch = "riscv32", target_arch = "riscv64"),
324        target_os = "none"
325    ))]
326    // SAFETY: FENCE orders all prior memory operations and serves as
327    // a speculation barrier on RISC-V.
328    unsafe {
329        core::arch::asm!("fence iorw, iorw", options(nomem, nostack));
330    }
331}
332
333// ---------------------------------------------------------------------------
334// Spectre v1: safe bounds masking
335// ---------------------------------------------------------------------------
336
337/// Branchless bounds mask for Spectre v1 mitigation.
338///
339/// Returns a mask that is all-ones if `index < size` and all-zeros
340/// otherwise. The comparison is performed without a branch, so
341/// mis-speculation cannot bypass the bounds check.
342///
343/// # Usage
344///
345/// ```ignore
346/// let safe_idx = index & bounds_mask(index, arr.len());
347/// let value = arr[safe_idx]; // safe even under mis-speculation
348/// ```
349#[inline(always)]
350pub fn bounds_mask(index: usize, size: usize) -> usize {
351    // Compute (index < size) without a branch.
352    //
353    // If index < size, the subtraction does NOT borrow, so the high bit
354    // is 0; arithmetic right-shift produces all zeros, then NOT gives
355    // all ones.
356    //
357    // If index >= size, the subtraction borrows, high bit is 1; right-
358    // shift produces all ones, then NOT gives all zeros.
359    //
360    // This is the standard pattern recommended by the Linux kernel and
361    // Intel for Spectre-v1 safe array indexing.
362    // Branchless bounds check for Spectre v1 mitigation.
363    //
364    // (index < size) as usize gives 1 if in-bounds, 0 if out-of-bounds.
365    // wrapping_neg() converts: 1 -> usize::MAX (all ones), 0 -> 0.
366    //
367    // The compiler emits cmov (branchless) for the comparison-to-integer
368    // conversion, which is safe against branch predictor speculation.
369    ((index < size) as usize).wrapping_neg()
370}
371
372/// Safe array index that clamps to zero under mis-speculation.
373///
374/// Returns `index` unchanged if `index < size`, or `0` otherwise.
375/// Uses [`bounds_mask`] internally for branchless operation.
376#[inline(always)]
377pub fn safe_index(index: usize, size: usize) -> usize {
378    index & bounds_mask(index, size)
379}
380
381// ---------------------------------------------------------------------------
382// Retpoline thunk marker
383// ---------------------------------------------------------------------------
384
385/// Whether retpoline (compiler-level Spectre v2 mitigation) is active.
386///
387/// Retpoline replaces indirect calls (`call *%rax`) with a construct that
388/// traps speculation in an infinite pause loop. It is enabled at the
389/// compiler level (e.g., `-Cllvm-args=-x86-speculative-load-hardening`
390/// or equivalent). Because Rust does not expose a `target_feature` for
391/// retpoline, this is a compile-time constant that should be updated
392/// when the build system enables retpoline flags.
393pub const RETPOLINE_ENABLED: bool = false;
394
395// ---------------------------------------------------------------------------
396// Initialization
397// ---------------------------------------------------------------------------
398
399/// Detect and enable Spectre mitigations.
400///
401/// Called during early boot from `security::init()`. Detects CPU features
402/// and enables available hardware mitigations. If IBRS is available, it is
403/// enabled. IBPB flushes are performed on context switches via [`flush_btb`].
404pub fn init() -> Result<(), KernelError> {
405    detect_features();
406
407    // Enable IBRS if available (enhanced IBRS is preferred).
408    let ibrs_ok = if has_ibrs() {
409        enable_ibrs().is_ok()
410    } else {
411        false
412    };
413
414    // Enable STIBP if available.
415    let stibp_ok = if has_stibp() {
416        enable_stibp().is_ok()
417    } else {
418        false
419    };
420
421    #[cfg(target_arch = "x86_64")]
422    crate::println!(
423        "[SPECTRE] x86_64: IBRS {} ({}), IBPB {}, STIBP {} ({}), eIBRS/HW {}",
424        if has_ibrs() { "yes" } else { "no" },
425        if ibrs_ok { "enabled" } else { "skipped" },
426        if has_ibpb() { "yes" } else { "no" },
427        if has_stibp() { "yes" } else { "no" },
428        if stibp_ok { "enabled" } else { "skipped" },
429        if is_hw_mitigated() { "yes" } else { "no" },
430    );
431
432    #[cfg(target_arch = "aarch64")]
433    {
434        let _ = (ibrs_ok, stibp_ok);
435        crate::kprintln!("[SPECTRE] AArch64: CSV2 detection complete");
436    }
437
438    #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
439    {
440        let _ = (ibrs_ok, stibp_ok);
441        crate::kprintln!("[SPECTRE] RISC-V: fence-based barriers configured");
442    }
443
444    #[cfg(not(any(
445        target_arch = "x86_64",
446        target_arch = "aarch64",
447        target_arch = "riscv32",
448        target_arch = "riscv64",
449    )))]
450    {
451        let _ = (ibrs_ok, stibp_ok);
452    }
453
454    Ok(())
455}
456
457// ---------------------------------------------------------------------------
458// Tests
459// ---------------------------------------------------------------------------
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    #[test]
466    fn test_bounds_mask_in_range() {
467        let mask = bounds_mask(3, 10);
468        assert_eq!(mask, usize::MAX);
469        assert_eq!(3 & mask, 3);
470    }
471
472    #[test]
473    fn test_bounds_mask_at_boundary() {
474        let mask = bounds_mask(10, 10);
475        assert_eq!(mask, 0);
476        assert_eq!(10 & mask, 0);
477    }
478
479    #[test]
480    fn test_bounds_mask_out_of_range() {
481        let mask = bounds_mask(15, 10);
482        assert_eq!(mask, 0);
483        assert_eq!(15 & mask, 0);
484    }
485
486    #[test]
487    fn test_bounds_mask_zero_size() {
488        let mask = bounds_mask(0, 0);
489        assert_eq!(mask, 0);
490    }
491
492    #[test]
493    fn test_bounds_mask_max_index() {
494        let mask = bounds_mask(usize::MAX, 10);
495        assert_eq!(mask, 0);
496    }
497
498    #[test]
499    fn test_safe_index_in_range() {
500        assert_eq!(safe_index(5, 10), 5);
501    }
502
503    #[test]
504    fn test_safe_index_out_of_range() {
505        assert_eq!(safe_index(10, 10), 0);
506        assert_eq!(safe_index(100, 10), 0);
507    }
508
509    #[test]
510    fn test_safe_index_zero() {
511        assert_eq!(safe_index(0, 10), 0);
512        assert_eq!(safe_index(0, 0), 0);
513    }
514
515    #[test]
516    fn test_feature_queries() {
517        // Just verify the query functions don't panic.
518        let _ = has_ibrs();
519        let _ = has_ibpb();
520        let _ = has_stibp();
521        let _ = is_ibrs_enabled();
522        let _ = is_hw_mitigated();
523    }
524
525    #[test]
526    fn test_detect_features_host() {
527        detect_features();
528        // On host/CI, stubs report features as supported.
529        #[cfg(not(target_os = "none"))]
530        {
531            assert!(has_ibrs());
532            assert!(has_ibpb());
533            assert!(has_stibp());
534        }
535    }
536
537    #[test]
538    fn test_enable_ibrs_without_support() {
539        IBRS_SUPPORTED.store(false, Ordering::Relaxed);
540        IBRS_ENABLED.store(false, Ordering::Relaxed);
541        assert!(enable_ibrs().is_err());
542        // Restore.
543        detect_features();
544    }
545
546    #[test]
547    fn test_flush_btb_noop_without_support() {
548        IBPB_SUPPORTED.store(false, Ordering::Relaxed);
549        flush_btb(); // should not panic
550                     // Restore.
551        detect_features();
552    }
553
554    #[test]
555    fn test_speculation_barrier_noop_on_host() {
556        // On the host target, speculation_barrier is a no-op (all asm
557        // is gated with target_os = "none"). Just verify it compiles.
558        speculation_barrier();
559    }
560
561    #[test]
562    #[cfg(target_arch = "x86_64")]
563    fn test_cpuid_constants() {
564        assert_eq!(CPUID_IBRS_IBPB_BIT, 1 << 26);
565        assert_eq!(CPUID_STIBP_BIT, 1 << 27);
566        assert_eq!(CPUID_ARCH_CAP_BIT, 1 << 29);
567    }
568}