openentropy_core/sources/timing/
page_fault_timing.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
4use crate::sources::helpers::{extract_timing_entropy, mach_time};
5
6pub struct PageFaultTimingSource;
11
12static PAGE_FAULT_TIMING_INFO: SourceInfo = SourceInfo {
13 name: "page_fault_timing",
14 description: "Minor page fault timing via mmap/munmap cycles",
15 physics: "Triggers and times minor page faults via mmap/munmap. Page fault resolution \
16 requires: TLB lookup, hardware page table walk (up to 4 levels on ARM64), \
17 physical page allocation from the kernel free list, and zero-fill for \
18 security. The timing depends on physical memory fragmentation.",
19 category: SourceCategory::Timing,
20 platform: Platform::Any,
21 requirements: &[],
22 entropy_rate_estimate: 2.0,
23 composite: false,
24 is_fast: true,
25};
26
27impl EntropySource for PageFaultTimingSource {
28 fn info(&self) -> &SourceInfo {
29 &PAGE_FAULT_TIMING_INFO
30 }
31
32 fn is_available(&self) -> bool {
33 cfg!(unix)
34 }
35
36 fn collect(&self, n_samples: usize) -> Vec<u8> {
37 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
39 let num_pages: usize = 8;
40 let map_size = page_size * num_pages;
41
42 let num_cycles = (n_samples * 4 / num_pages) + 4;
44
45 let mut timings = Vec::with_capacity(num_cycles * num_pages);
46
47 for _ in 0..num_cycles {
48 let addr = unsafe {
51 libc::mmap(
52 std::ptr::null_mut(),
53 map_size,
54 libc::PROT_READ | libc::PROT_WRITE,
55 libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
56 -1,
57 0,
58 )
59 };
60
61 if addr == libc::MAP_FAILED {
62 continue;
63 }
64
65 for p in 0..num_pages {
68 let page_ptr = unsafe { (addr as *mut u8).add(p * page_size) };
71
72 let t0 = mach_time();
73 unsafe {
76 std::ptr::write_volatile(page_ptr, 0xAA);
77 let _v = std::ptr::read_volatile(page_ptr);
78 }
79 let t1 = mach_time();
80
81 timings.push(t1.wrapping_sub(t0));
82 }
83
84 unsafe {
86 libc::munmap(addr, map_size);
87 }
88 }
89
90 extract_timing_entropy(&timings, n_samples)
91 }
92}
93
94#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 #[ignore] fn page_fault_timing_collects_bytes() {
105 let src = PageFaultTimingSource;
106 assert!(src.is_available());
107 let data = src.collect(64);
108 assert!(!data.is_empty());
109 assert!(data.len() <= 64);
110 }
111
112 #[test]
113 fn source_info_category() {
114 assert_eq!(
115 PageFaultTimingSource.info().category,
116 SourceCategory::Timing
117 );
118 }
119
120 #[test]
121 fn source_info_name() {
122 assert_eq!(PageFaultTimingSource.name(), "page_fault_timing");
123 }
124}