openentropy_core/sources/
silicon.rs1use rand::Rng;
6
7use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
8
9use super::helpers::{extract_timing_entropy, mach_time};
10
11pub struct DRAMRowBufferSource;
21
22static DRAM_ROW_BUFFER_INFO: SourceInfo = SourceInfo {
23 name: "dram_row_buffer",
24 description: "DRAM row buffer hit/miss timing from random memory accesses",
25 physics: "Measures DRAM row buffer hit/miss timing by accessing different memory rows. \
26 DRAM is organized into rows of capacitor cells. Accessing an open row (hit) \
27 is fast; accessing a different row requires precharge + activate (miss), \
28 which is slower. The exact timing depends on: physical address mapping, \
29 row buffer state from ALL system activity, memory controller scheduling, \
30 and DRAM refresh interference.",
31 category: SourceCategory::Timing,
32 platform: Platform::Any,
33 requirements: &[],
34 entropy_rate_estimate: 3000.0,
35 composite: false,
36};
37
38impl EntropySource for DRAMRowBufferSource {
39 fn info(&self) -> &SourceInfo {
40 &DRAM_ROW_BUFFER_INFO
41 }
42
43 fn is_available(&self) -> bool {
44 true
45 }
46
47 fn collect(&self, n_samples: usize) -> Vec<u8> {
48 const BUF_SIZE: usize = 32 * 1024 * 1024; let num_accesses = n_samples * 4 + 64;
53
54 let mut buffer: Vec<u8> = vec![0u8; BUF_SIZE];
56 for i in (0..BUF_SIZE).step_by(4096) {
57 buffer[i] = i as u8;
58 }
59
60 let mut rng = rand::rng();
61 let mut timings = Vec::with_capacity(num_accesses);
62
63 for _ in 0..num_accesses {
64 let idx1 = rng.random_range(0..BUF_SIZE);
67 let idx2 = rng.random_range(0..BUF_SIZE);
68
69 let t0 = mach_time();
70 let _v1 = unsafe { std::ptr::read_volatile(&buffer[idx1]) };
73 let _v2 = unsafe { std::ptr::read_volatile(&buffer[idx2]) };
74 let t1 = mach_time();
75
76 timings.push(t1.wrapping_sub(t0));
77 }
78
79 std::hint::black_box(&buffer);
81
82 extract_timing_entropy(&timings, n_samples)
83 }
84}
85
86pub struct CacheContentionSource;
96
97static CACHE_CONTENTION_INFO: SourceInfo = SourceInfo {
98 name: "cache_contention",
99 description: "L1/L2 cache contention timing from alternating access patterns",
100 physics: "Measures L1/L2 cache miss patterns by alternating access patterns. Cache \
101 timing depends on what every other process and hardware unit is doing \
102 \u{2014} the cache is a shared resource whose state is fundamentally \
103 unpredictable. A cache miss requires main memory access (100+ ns vs \
104 1 ns for L1 hit).",
105 category: SourceCategory::Timing,
106 platform: Platform::Any,
107 requirements: &[],
108 entropy_rate_estimate: 2500.0,
109 composite: false,
110};
111
112impl EntropySource for CacheContentionSource {
113 fn info(&self) -> &SourceInfo {
114 &CACHE_CONTENTION_INFO
115 }
116
117 fn is_available(&self) -> bool {
118 true
119 }
120
121 fn collect(&self, n_samples: usize) -> Vec<u8> {
122 const BUF_SIZE: usize = 8 * 1024 * 1024; let mut buffer: Vec<u8> = vec![0u8; BUF_SIZE];
125 for i in (0..BUF_SIZE).step_by(4096) {
127 buffer[i] = i as u8;
128 }
129
130 let num_rounds = n_samples * 4 + 64;
132 let mut rng = rand::rng();
133 let mut timings = Vec::with_capacity(num_rounds);
134
135 for round in 0..num_rounds {
136 let t0 = mach_time();
137
138 match round % 3 {
141 0 => {
142 let start = rng.random_range(0..BUF_SIZE.saturating_sub(512));
144 let mut sink: u8 = 0;
145 for offset in 0..512 {
146 sink ^= unsafe { std::ptr::read_volatile(&buffer[start + offset]) };
148 }
149 std::hint::black_box(sink);
150 }
151 1 => {
152 let mut sink: u8 = 0;
154 for _ in 0..512 {
155 let idx = rng.random_range(0..BUF_SIZE);
156 sink ^= unsafe { std::ptr::read_volatile(&buffer[idx]) };
158 }
159 std::hint::black_box(sink);
160 }
161 _ => {
162 let start = rng.random_range(0..BUF_SIZE.saturating_sub(512 * 64));
164 let mut sink: u8 = 0;
165 for i in 0..512 {
166 sink ^= unsafe { std::ptr::read_volatile(&buffer[start + i * 64]) };
168 }
169 std::hint::black_box(sink);
170 }
171 }
172
173 let t1 = mach_time();
174 timings.push(t1.wrapping_sub(t0));
175 }
176
177 std::hint::black_box(&buffer);
178
179 extract_timing_entropy(&timings, n_samples)
180 }
181}
182
183pub struct PageFaultTimingSource;
192
193static PAGE_FAULT_TIMING_INFO: SourceInfo = SourceInfo {
194 name: "page_fault_timing",
195 description: "Minor page fault timing via mmap/munmap cycles",
196 physics: "Triggers and times minor page faults via mmap/munmap. Page fault resolution \
197 requires: TLB lookup, hardware page table walk (up to 4 levels on ARM64), \
198 physical page allocation from the kernel free list, and zero-fill for \
199 security. The timing depends on physical memory fragmentation.",
200 category: SourceCategory::Timing,
201 platform: Platform::Any,
202 requirements: &[],
203 entropy_rate_estimate: 1500.0,
204 composite: false,
205};
206
207impl EntropySource for PageFaultTimingSource {
208 fn info(&self) -> &SourceInfo {
209 &PAGE_FAULT_TIMING_INFO
210 }
211
212 fn is_available(&self) -> bool {
213 true
214 }
215
216 fn collect(&self, n_samples: usize) -> Vec<u8> {
217 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
219 let num_pages: usize = 8;
220 let map_size = page_size * num_pages;
221
222 let num_cycles = (n_samples * 4 / num_pages) + 4;
224
225 let mut timings = Vec::with_capacity(num_cycles * num_pages);
226
227 for _ in 0..num_cycles {
228 let addr = unsafe {
231 libc::mmap(
232 std::ptr::null_mut(),
233 map_size,
234 libc::PROT_READ | libc::PROT_WRITE,
235 libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
236 -1,
237 0,
238 )
239 };
240
241 if addr == libc::MAP_FAILED {
242 continue;
243 }
244
245 for p in 0..num_pages {
248 let page_ptr = unsafe { (addr as *mut u8).add(p * page_size) };
251
252 let t0 = mach_time();
253 unsafe {
256 std::ptr::write_volatile(page_ptr, 0xAA);
257 let _v = std::ptr::read_volatile(page_ptr);
258 }
259 let t1 = mach_time();
260
261 timings.push(t1.wrapping_sub(t0));
262 }
263
264 unsafe {
266 libc::munmap(addr, map_size);
267 }
268 }
269
270 extract_timing_entropy(&timings, n_samples)
271 }
272}
273
274pub struct SpeculativeExecutionSource;
285
286static SPECULATIVE_EXECUTION_INFO: SourceInfo = SourceInfo {
287 name: "speculative_execution",
288 description: "Branch predictor state timing via data-dependent branches",
289 physics: "Measures timing variations from the CPU's speculative execution engine. \
290 The branch predictor maintains per-address history that depends on ALL \
291 previously executed code. Mispredictions cause pipeline flushes (~15 cycle \
292 penalty on M4). By running data-dependent branches and measuring timing, \
293 we capture the predictor's internal state.",
294 category: SourceCategory::Microarch,
295 platform: Platform::Any,
296 requirements: &[],
297 entropy_rate_estimate: 2000.0,
298 composite: false,
299};
300
301impl EntropySource for SpeculativeExecutionSource {
302 fn info(&self) -> &SourceInfo {
303 &SPECULATIVE_EXECUTION_INFO
304 }
305
306 fn is_available(&self) -> bool {
307 true
308 }
309
310 fn collect(&self, n_samples: usize) -> Vec<u8> {
311 let num_batches = n_samples * 4 + 64;
313 let mut timings = Vec::with_capacity(num_batches);
314
315 let mut lcg_state: u64 = mach_time() ^ 0xDEAD_BEEF_CAFE_BABE;
318
319 for batch_idx in 0..num_batches {
320 let batch_size = 10 + (batch_idx % 31);
323
324 let t0 = mach_time();
325
326 let mut accumulator: u64 = 0;
329 for _ in 0..batch_size {
330 lcg_state = lcg_state
332 .wrapping_mul(6364136223846793005)
333 .wrapping_add(1442695040888963407);
334
335 if lcg_state & 0x8000_0000 != 0 {
337 accumulator = accumulator.wrapping_add(lcg_state);
338 } else {
339 accumulator = accumulator.wrapping_mul(lcg_state | 1);
340 }
341
342 if (lcg_state >> 16) & 0xFF > 128 {
344 accumulator ^= lcg_state.rotate_left(7);
345 } else {
346 accumulator ^= lcg_state.rotate_right(11);
347 }
348
349 if (lcg_state >> 32) & 0x1 != 0 {
351 accumulator = accumulator.wrapping_add(batch_idx as u64);
352 }
353 }
354
355 std::hint::black_box(accumulator);
357
358 let t1 = mach_time();
359 timings.push(t1.wrapping_sub(t0));
360 }
361
362 extract_timing_entropy(&timings, n_samples)
363 }
364}
365
366#[cfg(test)]
371mod tests {
372 use super::*;
373
374 #[test]
375 #[ignore] fn dram_row_buffer_collects_bytes() {
377 let src = DRAMRowBufferSource;
378 assert!(src.is_available());
379 let data = src.collect(128);
380 assert!(!data.is_empty());
381 assert!(data.len() <= 128);
382 if data.len() > 1 {
384 let first = data[0];
385 assert!(data.iter().any(|&b| b != first), "all bytes were identical");
386 }
387 }
388
389 #[test]
390 #[ignore] fn cache_contention_collects_bytes() {
392 let src = CacheContentionSource;
393 assert!(src.is_available());
394 let data = src.collect(128);
395 assert!(!data.is_empty());
396 assert!(data.len() <= 128);
397 if data.len() > 1 {
398 let first = data[0];
399 assert!(data.iter().any(|&b| b != first), "all bytes were identical");
400 }
401 }
402
403 #[test]
404 #[ignore] fn page_fault_timing_collects_bytes() {
406 let src = PageFaultTimingSource;
407 assert!(src.is_available());
408 let data = src.collect(64);
409 assert!(!data.is_empty());
410 assert!(data.len() <= 64);
411 }
412
413 #[test]
414 #[ignore] fn speculative_execution_collects_bytes() {
416 let src = SpeculativeExecutionSource;
417 assert!(src.is_available());
418 let data = src.collect(128);
419 assert!(!data.is_empty());
420 assert!(data.len() <= 128);
421 if data.len() > 1 {
422 let first = data[0];
423 assert!(data.iter().any(|&b| b != first), "all bytes were identical");
424 }
425 }
426
427 #[test]
428 fn source_info_categories() {
429 assert_eq!(DRAMRowBufferSource.info().category, SourceCategory::Timing);
430 assert_eq!(
431 CacheContentionSource.info().category,
432 SourceCategory::Timing
433 );
434 assert_eq!(
435 PageFaultTimingSource.info().category,
436 SourceCategory::Timing
437 );
438 assert_eq!(
439 SpeculativeExecutionSource.info().category,
440 SourceCategory::Microarch
441 );
442 }
443
444 #[test]
445 fn source_info_names() {
446 assert_eq!(DRAMRowBufferSource.name(), "dram_row_buffer");
447 assert_eq!(CacheContentionSource.name(), "cache_contention");
448 assert_eq!(PageFaultTimingSource.name(), "page_fault_timing");
449 assert_eq!(SpeculativeExecutionSource.name(), "speculative_execution");
450 }
451}