use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(target_os = "macos")]
use crate::sources::helpers::{extract_timing_entropy, mach_time};
static PROC_INFO_TIMING_INFO: SourceInfo = SourceInfo {
name: "proc_info_timing",
description: "proc_pidinfo / proc_pid_rusage syscall — kernel proc_lock contention timing",
physics: "Times proc_pidinfo(TBSDINFO) and proc_pid_rusage(V4) syscalls. Each acquires \
the BSD kernel proc_lock, walks the process table, and optionally reads hardware \
perf counters. Lock contention from concurrent fork/exec/exit operations creates \
variable delay. LSB=0.24\u{2013}0.29 (near-uniform, unlike always-even instruction \
timing) — kernel scheduling dominates, not microarch quantization. CV=43\u{2013}48%, \
range=[434,10583]. Cross-process sensitivity: any process lifecycle activity \
(terminal commands, builds, browser tabs) leaks into our timing distribution \
through proc_lock contention. Genuine covert channel.",
category: SourceCategory::System,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 1.5,
composite: false,
is_fast: false,
};
pub struct ProcInfoTimingSource;
#[cfg(target_os = "macos")]
unsafe extern "C" {
fn proc_pidinfo(
pid: i32,
flavor: i32,
arg: u64,
buffer: *mut core::ffi::c_void,
buffersize: i32,
) -> i32;
fn proc_pid_rusage(pid: i32, flavor: i32, buffer: *mut core::ffi::c_void) -> i32;
fn getpid() -> i32;
}
#[cfg(target_os = "macos")]
const PROC_PIDTBSDINFO: i32 = 3;
#[cfg(target_os = "macos")]
const RUSAGE_INFO_V4: i32 = 4;
#[cfg(target_os = "macos")]
#[repr(C, align(8))]
struct ProcBSDInfo {
_pad: [u8; 512], }
#[cfg(target_os = "macos")]
#[repr(C, align(8))]
struct RusageInfoV4 {
_pad: [u8; 320], }
#[cfg(target_os = "macos")]
impl EntropySource for ProcInfoTimingSource {
fn info(&self) -> &SourceInfo {
&PROC_INFO_TIMING_INFO
}
fn is_available(&self) -> bool {
true
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
let raw = n_samples * 2 + 32;
let mut timings = Vec::with_capacity(raw * 2);
let pid = unsafe { getpid() };
let mut bsd_info = ProcBSDInfo { _pad: [0u8; 512] };
let mut ru_info = RusageInfoV4 { _pad: [0u8; 320] };
for _ in 0..4 {
unsafe {
proc_pidinfo(
pid,
PROC_PIDTBSDINFO,
0,
bsd_info._pad.as_mut_ptr() as *mut core::ffi::c_void,
bsd_info._pad.len() as i32,
);
}
}
for _ in 0..raw {
let t0 = mach_time();
unsafe {
proc_pidinfo(
pid,
PROC_PIDTBSDINFO,
0,
bsd_info._pad.as_mut_ptr() as *mut core::ffi::c_void,
bsd_info._pad.len() as i32,
);
}
let t_pid = mach_time().wrapping_sub(t0);
let t1 = mach_time();
unsafe {
proc_pid_rusage(
pid,
RUSAGE_INFO_V4,
ru_info._pad.as_mut_ptr() as *mut core::ffi::c_void,
);
}
let t_ru = mach_time().wrapping_sub(t1);
if t_pid < 120_000 {
timings.push(t_pid);
}
if t_ru < 120_000 {
timings.push(t_ru);
}
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(not(target_os = "macos"))]
impl EntropySource for ProcInfoTimingSource {
fn info(&self) -> &SourceInfo {
&PROC_INFO_TIMING_INFO
}
fn is_available(&self) -> bool {
false
}
fn collect(&self, _: usize) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = ProcInfoTimingSource;
assert_eq!(src.info().name, "proc_info_timing");
assert!(matches!(src.info().category, SourceCategory::System));
assert_eq!(src.info().platform, Platform::MacOS);
assert!(!src.info().composite);
}
#[test]
#[cfg(target_os = "macos")]
fn is_available_on_macos() {
assert!(ProcInfoTimingSource.is_available());
}
#[test]
#[ignore]
fn collects_lock_contention_timing() {
let data = ProcInfoTimingSource.collect(32);
assert!(!data.is_empty());
}
}