use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use crate::sources::helpers::{extract_timing_entropy, mach_time};
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use std::ptr;
static MEMORY_BUS_CRYPTO_INFO: SourceInfo = SourceInfo {
name: "memory_bus_crypto",
description: "AES-XTS crypto context switching timing from cross-page cache flush cycles",
physics: "Apple Silicon encrypts all DRAM accesses via inline AES-XTS with per-page keys. \
Flushing a cache line (DC CIVAC + DSB SY) then accessing a page from a \
different virtual address region forces the memory controller to switch \
AES-XTS key contexts. The key-context switch latency varies with the AES \
engine pipeline state, DRAM transaction queue depth, and refresh timing. \
Measured CV >270%, consistent with crypto engine state variation rather \
than scheduler noise. Raw LSBs are biased (cache fast-path dominates); \
delta extraction removes the bias while preserving the variance signal.",
category: SourceCategory::Microarch,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: false,
};
const APPLE_PAGE_SIZE: usize = 16 * 1024;
const NUM_REGIONS: usize = 8;
const REGION_PAGES: usize = 16;
const REGION_SIZE: usize = APPLE_PAGE_SIZE * REGION_PAGES;
pub struct MemoryBusCryptoSource;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod imp {
use super::*;
struct MappedRegions(Vec<*mut u8>);
impl Drop for MappedRegions {
fn drop(&mut self) {
for &r in &self.0 {
unsafe {
libc::munmap(r as *mut libc::c_void, REGION_SIZE);
}
}
}
}
impl EntropySource for MemoryBusCryptoSource {
fn info(&self) -> &SourceInfo {
&MEMORY_BUS_CRYPTO_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let raw_count = n_samples * 20 + 128;
let mut raw_regions: Vec<*mut u8> = Vec::with_capacity(NUM_REGIONS);
unsafe {
for i in 0..NUM_REGIONS {
let p = libc::mmap(
ptr::null_mut(),
REGION_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
) as *mut u8;
if p == libc::MAP_FAILED as *mut u8 {
let _guard = MappedRegions(raw_regions);
return Vec::new();
}
for page in 0..REGION_PAGES {
ptr::write_volatile(p.add(page * APPLE_PAGE_SIZE), i as u8);
}
raw_regions.push(p);
}
}
let regions = MappedRegions(raw_regions);
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
let mut lcg: u64 = mach_time() | 1;
unsafe {
for _ in 0..raw_count {
lcg = lcg
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let src_idx = (lcg >> 32) as usize % NUM_REGIONS;
let dst_idx =
(src_idx + 1 + ((lcg & 0xFFFF) as usize % (NUM_REGIONS - 1))) % NUM_REGIONS;
let src_off = ((lcg >> 16) as usize % (REGION_SIZE / 64)) * 64;
let dst_off = (((lcg >> 48) as usize * 97) % (REGION_SIZE / 64)) * 64;
let src_ptr = regions.0[src_idx].add(src_off);
let dst_ptr = regions.0[dst_idx].add(dst_off);
ptr::write_volatile(src_ptr, (lcg & 0xFF) as u8);
std::arch::asm!(
"dc civac, {addr}",
"dsb sy",
addr = in(reg) src_ptr,
options(nostack, preserves_flags)
);
let t0 = mach_time();
let _v = ptr::read_volatile(dst_ptr);
let elapsed = mach_time().wrapping_sub(t0);
timings.push(elapsed);
}
}
drop(regions);
extract_timing_entropy(&timings, n_samples)
}
}
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
impl EntropySource for MemoryBusCryptoSource {
fn info(&self) -> &SourceInfo {
&MEMORY_BUS_CRYPTO_INFO
}
fn is_available(&self) -> bool {
false
}
fn collect(&self, _n_samples: usize) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = MemoryBusCryptoSource;
assert_eq!(src.info().name, "memory_bus_crypto");
assert!(matches!(src.info().category, SourceCategory::Microarch));
assert_eq!(src.info().platform, Platform::MacOS);
assert!(!src.info().composite);
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn is_available_on_apple_silicon() {
assert!(MemoryBusCryptoSource.is_available());
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
#[ignore] fn collects_bytes() {
let src = MemoryBusCryptoSource;
if !src.is_available() {
return;
}
let data = src.collect(32);
assert!(!data.is_empty());
assert!(data.len() <= 32);
}
}