Skip to main content

openentropy_core/sources/microarch/
speculative_execution.rs

1//! Speculative execution / branch predictor state timing entropy source.
2
3use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
4use crate::sources::helpers::{extract_timing_entropy, mach_time};
5
6/// Measures timing variations from the CPU's speculative execution engine. The
7/// branch predictor maintains per-address history that depends on ALL
8/// previously executed code. Mispredictions cause pipeline flushes (~15 cycle
9/// penalty on M4). By running data-dependent branches and measuring timing, we
10/// capture the predictor's internal state which is influenced by all prior
11/// program activity on the core.
12pub struct SpeculativeExecutionSource;
13
14static SPECULATIVE_EXECUTION_INFO: SourceInfo = SourceInfo {
15    name: "speculative_execution",
16    description: "Branch predictor state timing via data-dependent branches",
17    physics: "Measures timing variations from the CPU's speculative execution engine. \
18              The branch predictor maintains per-address history that depends on ALL \
19              previously executed code. Mispredictions cause pipeline flushes (~15 cycle \
20              penalty on M4). By running data-dependent branches and measuring timing, \
21              we capture the predictor's internal state.",
22    category: SourceCategory::Microarch,
23    platform: Platform::Any,
24    requirements: &[],
25    entropy_rate_estimate: 2.5,
26    composite: false,
27    is_fast: true,
28};
29
30impl EntropySource for SpeculativeExecutionSource {
31    fn info(&self) -> &SourceInfo {
32        &SPECULATIVE_EXECUTION_INFO
33    }
34
35    fn is_available(&self) -> bool {
36        true
37    }
38
39    fn collect(&self, n_samples: usize) -> Vec<u8> {
40        let num_batches = n_samples + 64;
41        let mut timings = Vec::with_capacity(num_batches);
42
43        // LCG state — seeded from the high-resolution clock so every call
44        // exercises a different branch sequence.
45        let mut lcg_state: u64 = mach_time() ^ 0xDEAD_BEEF_CAFE_BABE;
46
47        for _batch_idx in 0..num_batches {
48            // Randomize batch size via LCG (10-40 iterations) so the
49            // branch predictor can't learn the pattern.
50            lcg_state = lcg_state
51                .wrapping_mul(6364136223846793005)
52                .wrapping_add(1442695040888963407);
53            let batch_size = 10 + ((lcg_state >> 48) as usize % 31);
54
55            let t0 = mach_time();
56
57            // Execute a batch of data-dependent branches that defeat the
58            // branch predictor because outcomes depend on runtime LCG values.
59            let mut accumulator: u64 = 0;
60            for _ in 0..batch_size {
61                // Advance LCG: x' = x * 6364136223846793005 + 1442695040888963407
62                lcg_state = lcg_state
63                    .wrapping_mul(6364136223846793005)
64                    .wrapping_add(1442695040888963407);
65
66                // Data-dependent branch — outcome unknowable to predictor.
67                if lcg_state & 0x8000_0000 != 0 {
68                    accumulator = accumulator.wrapping_add(lcg_state);
69                } else {
70                    accumulator = accumulator.wrapping_mul(lcg_state | 1);
71                }
72
73                // Second branch: advance LCG independently for decorrelation.
74                lcg_state = lcg_state
75                    .wrapping_mul(6364136223846793005)
76                    .wrapping_add(1442695040888963407);
77                if lcg_state & 0x8000_0000 != 0 {
78                    accumulator ^= lcg_state.rotate_left(7);
79                } else {
80                    accumulator ^= lcg_state.rotate_right(11);
81                }
82
83                // Third branch: advance LCG again for independent outcome.
84                lcg_state = lcg_state
85                    .wrapping_mul(6364136223846793005)
86                    .wrapping_add(1442695040888963407);
87                if lcg_state & 0x1 != 0 {
88                    accumulator = accumulator.wrapping_add(lcg_state >> 32);
89                }
90            }
91
92            // Prevent the compiler from optimizing away the computation.
93            std::hint::black_box(accumulator);
94
95            let t1 = mach_time();
96            timings.push(t1.wrapping_sub(t0));
97        }
98
99        extract_timing_entropy(&timings, n_samples)
100    }
101}
102
103// ---------------------------------------------------------------------------
104// Tests
105// ---------------------------------------------------------------------------
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    #[ignore] // Run with: cargo test -- --ignored
113    fn speculative_execution_collects_bytes() {
114        let src = SpeculativeExecutionSource;
115        assert!(src.is_available());
116        let data = src.collect(128);
117        assert!(!data.is_empty());
118        assert!(data.len() <= 128);
119        if data.len() > 1 {
120            let first = data[0];
121            assert!(data.iter().any(|&b| b != first), "all bytes were identical");
122        }
123    }
124
125    #[test]
126    fn source_info_category() {
127        assert_eq!(
128            SpeculativeExecutionSource.info().category,
129            SourceCategory::Microarch
130        );
131    }
132
133    #[test]
134    fn source_info_name() {
135        assert_eq!(SpeculativeExecutionSource.name(), "speculative_execution");
136    }
137}