#[cfg(target_os = "macos")]
use std::sync::Arc;
#[cfg(target_os = "macos")]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_os = "macos")]
use std::thread;
use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(target_os = "macos")]
use crate::sources::helpers::{extract_timing_entropy, mach_time};
#[derive(Debug, Clone)]
pub struct MachIPCConfig {
pub num_ports: usize,
pub ool_size: usize,
pub use_complex_messages: bool,
}
impl Default for MachIPCConfig {
fn default() -> Self {
Self {
num_ports: 8,
ool_size: 4096,
use_complex_messages: true,
}
}
}
#[derive(Default)]
pub struct MachIPCSource {
pub config: MachIPCConfig,
}
static MACH_IPC_INFO: SourceInfo = SourceInfo {
name: "mach_ipc",
description: "Mach port complex OOL message and VM remapping timing jitter",
physics: "Sends complex Mach messages with out-of-line (OOL) memory descriptors via \
mach_msg(), round-robining across multiple ports. OOL descriptors force kernel \
VM remapping (vm_map_copyin/copyout) which exercises page table operations. \
Round-robin across ports with varied queue depths creates namespace contention. \
Timing captures: OOL VM remap latency, port namespace splay tree operations, \
per-port lock contention, and cross-core scheduling nondeterminism.",
category: SourceCategory::IPC,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.0,
composite: false,
is_fast: true,
};
impl EntropySource for MachIPCSource {
fn info(&self) -> &SourceInfo {
&MACH_IPC_INFO
}
fn is_available(&self) -> bool {
cfg!(target_os = "macos")
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
#[cfg(not(target_os = "macos"))]
{
let _ = n_samples;
Vec::new()
}
#[cfg(target_os = "macos")]
{
let raw_count = n_samples * 4 + 64;
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
let task = unsafe { mach_task_self() };
let num_ports = self.config.num_ports.max(1);
let mut ports: Vec<u32> = Vec::with_capacity(num_ports);
for _ in 0..num_ports {
let mut port: u32 = 0;
let kr = unsafe {
mach_port_allocate(task, 1 , &mut port)
};
if kr == 0 {
let kr2 = unsafe {
mach_port_insert_right(
task, port, port, 20,
)
};
if kr2 == 0 {
ports.push(port);
} else {
unsafe {
mach_port_mod_refs(task, port, 1, -1);
}
}
}
}
if ports.is_empty() {
return self.collect_simple(n_samples);
}
if self.config.use_complex_messages {
let ool_size = self.config.ool_size.max(1);
let ool_buf = vec![0xBEu8; ool_size];
let stop = Arc::new(AtomicBool::new(false));
let stop2 = stop.clone();
let recv_ports = ports.clone();
let receiver = thread::spawn(move || {
let mut recv_buf = vec![0u8; 1024 + ool_size * 2];
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
while !stop2.load(Ordering::Relaxed) && std::time::Instant::now() < deadline {
for &port in &recv_ports {
unsafe {
let hdr = recv_buf.as_mut_ptr() as *mut MachMsgHeader;
(*hdr).msgh_local_port = port;
(*hdr).msgh_size = recv_buf.len() as u32;
let kr =
mach_msg(hdr, 2 | 0x100, 0, recv_buf.len() as u32, port, 0, 0);
if kr == 0 && ((*hdr).msgh_bits & 0x80000000) != 0 {
let recv_ool_ptr = recv_buf.as_ptr().add(
std::mem::size_of::<MachMsgHeader>()
+ std::mem::size_of::<MachMsgBody>(),
)
as *const MachMsgOOLDescriptor;
let recv_ool = std::ptr::read_unaligned(recv_ool_ptr);
let addr = recv_ool.address as usize;
let size = recv_ool.size as usize;
if addr != 0 && size > 0 {
vm_deallocate(mach_task_self(), addr, size);
}
}
}
}
std::thread::yield_now();
}
});
for i in 0..raw_count {
let port = ports[i % ports.len()];
let mut msg = MachMsgOOL::zeroed();
msg.header.msgh_bits = 0x80000000 | 17; msg.header.msgh_size = std::mem::size_of::<MachMsgOOL>() as u32;
msg.header.msgh_remote_port = port;
msg.header.msgh_local_port = 0;
msg.header.msgh_id = i as i32;
msg.body.msgh_descriptor_count = 1;
msg.ool.address = ool_buf.as_ptr() as *mut _;
msg.ool.size = ool_size as u32;
msg.ool.deallocate = 0;
msg.ool.copy = 1; msg.ool.ool_type = 1;
let t0 = mach_time();
unsafe {
mach_msg(&mut msg.header, 1 | 0x80, msg.header.msgh_size, 0, 0, 10, 0);
}
let t1 = mach_time();
timings.push(t1.wrapping_sub(t0));
}
stop.store(true, Ordering::Relaxed);
let _ = receiver.join();
} else {
for i in 0..raw_count {
let t0 = mach_time();
let base_port = ports[i % ports.len()];
let mut new_port: u32 = 0;
let kr = unsafe { mach_port_allocate(task, 1, &mut new_port) };
if kr == 0 {
unsafe {
mach_port_mod_refs(task, new_port, 1, -1);
}
}
unsafe {
let mut ptype: u32 = 0;
mach_port_type(task, base_port, &mut ptype);
}
let t1 = mach_time();
timings.push(t1.wrapping_sub(t0));
}
}
for &port in &ports {
unsafe {
mach_port_mod_refs(task, port, 0 , -1);
mach_port_mod_refs(task, port, 1 , -1);
}
}
extract_timing_entropy(&timings, n_samples)
}
}
}
#[cfg(target_os = "macos")]
impl MachIPCSource {
fn collect_simple(&self, n_samples: usize) -> Vec<u8> {
let raw_count = n_samples * 4 + 64;
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
let task = unsafe { mach_task_self() };
for _ in 0..raw_count {
let t0 = mach_time();
let mut port: u32 = 0;
let kr = unsafe { mach_port_allocate(task, 1, &mut port) };
if kr == 0 {
unsafe {
mach_port_mod_refs(task, port, 1, -1);
}
}
let t1 = mach_time();
timings.push(t1.wrapping_sub(t0));
}
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct MachMsgHeader {
msgh_bits: u32,
msgh_size: u32,
msgh_remote_port: u32,
msgh_local_port: u32,
msgh_voucher_port: u32,
msgh_id: i32,
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct MachMsgBody {
msgh_descriptor_count: u32,
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct MachMsgOOLDescriptor {
address: *mut u8,
deallocate: u8,
copy: u8,
_pad: u8,
ool_type: u8, size: u32,
}
#[cfg(target_os = "macos")]
#[repr(C)]
struct MachMsgOOL {
header: MachMsgHeader,
body: MachMsgBody,
ool: MachMsgOOLDescriptor,
}
#[cfg(target_os = "macos")]
impl MachMsgOOL {
fn zeroed() -> Self {
unsafe { std::mem::zeroed() }
}
}
#[cfg(target_os = "macos")]
unsafe extern "C" {
fn mach_task_self() -> u32;
fn mach_port_allocate(task: u32, right: i32, name: *mut u32) -> i32;
fn mach_port_mod_refs(task: u32, name: u32, right: i32, delta: i32) -> i32;
fn mach_port_insert_right(task: u32, name: u32, poly: u32, poly_poly: u32) -> i32;
fn mach_port_type(task: u32, name: u32, ptype: *mut u32) -> i32;
fn mach_msg(
msg: *mut MachMsgHeader,
option: i32,
send_size: u32,
rcv_size: u32,
rcv_name: u32,
timeout: u32,
notify: u32,
) -> i32;
fn vm_deallocate(target: u32, addr: usize, size: usize) -> i32;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = MachIPCSource::default();
assert_eq!(src.name(), "mach_ipc");
assert_eq!(src.info().category, SourceCategory::IPC);
assert!(!src.info().composite);
}
#[test]
fn default_config() {
let config = MachIPCConfig::default();
assert_eq!(config.num_ports, 8);
assert_eq!(config.ool_size, 4096);
assert!(config.use_complex_messages);
}
#[test]
fn custom_config() {
let src = MachIPCSource {
config: MachIPCConfig {
num_ports: 4,
ool_size: 8192,
use_complex_messages: false,
},
};
assert_eq!(src.config.num_ports, 4);
assert!(!src.config.use_complex_messages);
}
#[test]
#[ignore] fn collects_bytes() {
let src = MachIPCSource::default();
assert!(src.is_available());
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
#[test]
#[ignore] fn simple_mode_collects_bytes() {
let src = MachIPCSource {
config: MachIPCConfig {
use_complex_messages: false,
..MachIPCConfig::default()
},
};
assert!(!src.collect(64).is_empty());
}
}