use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
use crate::sources::helpers::{extract_timing_entropy, mach_time};
pub struct PageFaultTimingSource;
static PAGE_FAULT_TIMING_INFO: SourceInfo = SourceInfo {
name: "page_fault_timing",
description: "Minor page fault timing via mmap/munmap cycles",
physics: "Triggers and times minor page faults via mmap/munmap. Page fault resolution \
requires: TLB lookup, hardware page table walk (up to 4 levels on ARM64), \
physical page allocation from the kernel free list, and zero-fill for \
security. The timing depends on physical memory fragmentation.",
category: SourceCategory::Timing,
platform: Platform::Any,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: true,
};
impl EntropySource for PageFaultTimingSource {
fn info(&self) -> &SourceInfo {
&PAGE_FAULT_TIMING_INFO
}
fn is_available(&self) -> bool {
cfg!(unix)
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
let num_pages: usize = 8;
let map_size = page_size * num_pages;
let num_cycles = (n_samples * 4 / num_pages) + 4;
let mut timings = Vec::with_capacity(num_cycles * num_pages);
for _ in 0..num_cycles {
let addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
map_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
-1,
0,
)
};
if addr == libc::MAP_FAILED {
continue;
}
for p in 0..num_pages {
let page_ptr = unsafe { (addr as *mut u8).add(p * page_size) };
let t0 = mach_time();
unsafe {
std::ptr::write_volatile(page_ptr, 0xAA);
let _v = std::ptr::read_volatile(page_ptr);
}
let t1 = mach_time();
timings.push(t1.wrapping_sub(t0));
}
unsafe {
libc::munmap(addr, map_size);
}
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore] fn page_fault_timing_collects_bytes() {
let src = PageFaultTimingSource;
assert!(src.is_available());
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
#[test]
fn source_info_category() {
assert_eq!(
PageFaultTimingSource.info().category,
SourceCategory::Timing
);
}
#[test]
fn source_info_name() {
assert_eq!(PageFaultTimingSource.name(), "page_fault_timing");
}
}