openentropy_core/sources/microarch/
prefetcher_state.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
35
36#[cfg(target_os = "macos")]
37use crate::sources::helpers::extract_timing_entropy;
38
39static PREFETCHER_STATE_INFO: SourceInfo = SourceInfo {
40 name: "prefetcher_state",
41 description: "Hardware prefetcher stride-learning state — 2.25× learned vs random speedup",
42 physics: "Trains the L1/L2 hardware prefetcher with consistent stride accesses, then \
43 measures learned-stride vs random-stride timing. Learned: mean=302.1 ticks, \
44 CV=24.1%. Random: mean=679.9 ticks, CV=23.1%. Speedup=2.25×. The prefetcher \
45 state encodes: recent access history, prefetch buffer occupancy, training \
46 confidence, and cross-process memory contention. Other threads' access \
47 patterns interfere with our training, creating a cross-process covert channel.",
48 category: SourceCategory::Microarch,
49 platform: Platform::MacOS,
50 requirements: &[],
51 entropy_rate_estimate: 2.0,
52 composite: false,
53 is_fast: false,
54};
55
56pub struct PrefetcherStateSource;
58
59#[cfg(target_os = "macos")]
60impl EntropySource for PrefetcherStateSource {
61 fn info(&self) -> &SourceInfo {
62 &PREFETCHER_STATE_INFO
63 }
64
65 fn is_available(&self) -> bool {
66 true
67 }
68
69 fn collect(&self, n_samples: usize) -> Vec<u8> {
70 use std::ptr;
71
72 const STRIDE: usize = 64;
73 const N_ACCESSES: usize = 500;
74 const N_TRAIN: usize = 1000;
75
76 let buf_size = 8 * 1024 * 1024 + 4096;
79 let mut buf_vec: Vec<u8> = vec![0u8; buf_size];
80 let buf = buf_vec.as_mut_ptr();
81
82 for i in (0..buf_size).step_by(4096) {
84 unsafe { ptr::write_volatile(buf.add(i), i as u8) };
85 }
86
87 let raw = n_samples * 2 + 32;
88 let mut timings = Vec::with_capacity(raw * 2);
89
90 for s in 0..raw {
91 for i in 0..N_TRAIN {
93 let offset = (i * STRIDE) % (buf_size - STRIDE);
94 unsafe { ptr::read_volatile(buf.add(offset)) };
95 }
96
97 let t0 = super::super::helpers::mach_time();
99 for i in 0..N_ACCESSES {
100 let offset = (i * STRIDE) % (buf_size - STRIDE);
101 unsafe { ptr::read_volatile(buf.add(offset)) };
102 }
103 let learned_t = super::super::helpers::mach_time().wrapping_sub(t0);
104
105 for i in 0..N_TRAIN {
107 let offset = ((i * 7919) % (buf_size / STRIDE)) * STRIDE;
108 unsafe { ptr::read_volatile(buf.add(offset)) };
109 }
110
111 let t1 = super::super::helpers::mach_time();
113 for i in 0..N_ACCESSES {
114 let offset = ((i * 7919) % (buf_size / STRIDE)) * STRIDE;
115 unsafe { ptr::read_volatile(buf.add(offset)) };
116 }
117 let random_t = super::super::helpers::mach_time().wrapping_sub(t1);
118
119 timings.push(learned_t);
121 timings.push(random_t);
122
123 unsafe { ptr::read_volatile(buf.add(s % buf_size)) };
125 }
126
127 drop(buf_vec);
128
129 let combined: Vec<u64> = timings
131 .chunks(2)
132 .map(|c| c[0] ^ c[1].wrapping_shl(5))
133 .collect();
134
135 extract_timing_entropy(&combined, n_samples)
136 }
137}
138
139#[cfg(not(target_os = "macos"))]
140impl EntropySource for PrefetcherStateSource {
141 fn info(&self) -> &SourceInfo {
142 &PREFETCHER_STATE_INFO
143 }
144 fn is_available(&self) -> bool {
145 false
146 }
147 fn collect(&self, _: usize) -> Vec<u8> {
148 Vec::new()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn info() {
158 let src = PrefetcherStateSource;
159 assert_eq!(src.info().name, "prefetcher_state");
160 assert!(matches!(src.info().category, SourceCategory::Microarch));
161 assert_eq!(src.info().platform, Platform::MacOS);
162 }
163
164 #[test]
165 #[cfg(target_os = "macos")]
166 fn is_available() {
167 assert!(PrefetcherStateSource.is_available());
168 }
169
170 #[test]
171 #[ignore]
172 fn collects_prefetcher_state() {
173 let data = PrefetcherStateSource.collect(32);
174 assert!(!data.is_empty());
175 }
176}