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}