openentropy_core/sources/ipc/
mach_ipc.rs1#[cfg(target_os = "macos")]
4use std::sync::Arc;
5#[cfg(target_os = "macos")]
6use std::sync::atomic::{AtomicBool, Ordering};
7#[cfg(target_os = "macos")]
8use std::thread;
9
10use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
11#[cfg(target_os = "macos")]
12use crate::sources::helpers::{extract_timing_entropy, mach_time};
13
14#[derive(Debug, Clone)]
26pub struct MachIPCConfig {
27 pub num_ports: usize,
35
36 pub ool_size: usize,
44
45 pub use_complex_messages: bool,
53}
54
55impl Default for MachIPCConfig {
56 fn default() -> Self {
57 Self {
58 num_ports: 8,
59 ool_size: 4096,
60 use_complex_messages: true,
61 }
62 }
63}
64
65#[derive(Default)]
93pub struct MachIPCSource {
94 pub config: MachIPCConfig,
96}
97
98static MACH_IPC_INFO: SourceInfo = SourceInfo {
99 name: "mach_ipc",
100 description: "Mach port complex OOL message and VM remapping timing jitter",
101 physics: "Sends complex Mach messages with out-of-line (OOL) memory descriptors via \
102 mach_msg(), round-robining across multiple ports. OOL descriptors force kernel \
103 VM remapping (vm_map_copyin/copyout) which exercises page table operations. \
104 Round-robin across ports with varied queue depths creates namespace contention. \
105 Timing captures: OOL VM remap latency, port namespace splay tree operations, \
106 per-port lock contention, and cross-core scheduling nondeterminism.",
107 category: SourceCategory::IPC,
108 platform: Platform::MacOS,
109 requirements: &[],
110 entropy_rate_estimate: 2.0,
111 composite: false,
112 is_fast: true,
113};
114
115impl EntropySource for MachIPCSource {
116 fn info(&self) -> &SourceInfo {
117 &MACH_IPC_INFO
118 }
119
120 fn is_available(&self) -> bool {
121 cfg!(target_os = "macos")
122 }
123
124 fn collect(&self, n_samples: usize) -> Vec<u8> {
125 #[cfg(not(target_os = "macos"))]
126 {
127 let _ = n_samples;
128 Vec::new()
129 }
130
131 #[cfg(target_os = "macos")]
132 {
133 let raw_count = n_samples * 4 + 64;
134 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
135
136 let task = unsafe { mach_task_self() };
138 let num_ports = self.config.num_ports.max(1);
139
140 let mut ports: Vec<u32> = Vec::with_capacity(num_ports);
141 for _ in 0..num_ports {
142 let mut port: u32 = 0;
143 let kr = unsafe {
145 mach_port_allocate(task, 1 , &mut port)
146 };
147 if kr == 0 {
148 let kr2 = unsafe {
150 mach_port_insert_right(
151 task, port, port, 20, )
153 };
154 if kr2 == 0 {
155 ports.push(port);
156 } else {
157 unsafe {
158 mach_port_mod_refs(task, port, 1, -1);
159 }
160 }
161 }
162 }
163
164 if ports.is_empty() {
165 return self.collect_simple(n_samples);
166 }
167
168 if self.config.use_complex_messages {
169 let ool_size = self.config.ool_size.max(1);
170 let ool_buf = vec![0xBEu8; ool_size];
171
172 let stop = Arc::new(AtomicBool::new(false));
173 let stop2 = stop.clone();
174 let recv_ports = ports.clone();
175 let receiver = thread::spawn(move || {
176 let mut recv_buf = vec![0u8; 1024 + ool_size * 2];
177 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
180 while !stop2.load(Ordering::Relaxed) && std::time::Instant::now() < deadline {
181 for &port in &recv_ports {
182 unsafe {
184 let hdr = recv_buf.as_mut_ptr() as *mut MachMsgHeader;
185 (*hdr).msgh_local_port = port;
186 (*hdr).msgh_size = recv_buf.len() as u32;
187 let kr =
188 mach_msg(hdr, 2 | 0x100, 0, recv_buf.len() as u32, port, 0, 0);
189 if kr == 0 && ((*hdr).msgh_bits & 0x80000000) != 0 {
193 let recv_ool_ptr = recv_buf.as_ptr().add(
194 std::mem::size_of::<MachMsgHeader>()
195 + std::mem::size_of::<MachMsgBody>(),
196 )
197 as *const MachMsgOOLDescriptor;
198 let recv_ool = std::ptr::read_unaligned(recv_ool_ptr);
199 let addr = recv_ool.address as usize;
200 let size = recv_ool.size as usize;
201 if addr != 0 && size > 0 {
202 vm_deallocate(mach_task_self(), addr, size);
203 }
204 }
205 }
206 }
207 std::thread::yield_now();
208 }
209 });
210
211 for i in 0..raw_count {
212 let port = ports[i % ports.len()];
213
214 let mut msg = MachMsgOOL::zeroed();
215 msg.header.msgh_bits = 0x80000000 | 17; msg.header.msgh_size = std::mem::size_of::<MachMsgOOL>() as u32;
217 msg.header.msgh_remote_port = port;
218 msg.header.msgh_local_port = 0;
219 msg.header.msgh_id = i as i32;
220 msg.body.msgh_descriptor_count = 1;
221 msg.ool.address = ool_buf.as_ptr() as *mut _;
222 msg.ool.size = ool_size as u32;
223 msg.ool.deallocate = 0;
224 msg.ool.copy = 1; msg.ool.ool_type = 1; let t0 = mach_time();
228 unsafe {
230 mach_msg(&mut msg.header, 1 | 0x80, msg.header.msgh_size, 0, 0, 10, 0);
231 }
232 let t1 = mach_time();
233 timings.push(t1.wrapping_sub(t0));
234 }
235
236 stop.store(true, Ordering::Relaxed);
237 let _ = receiver.join();
238 } else {
239 for i in 0..raw_count {
240 let t0 = mach_time();
241 let base_port = ports[i % ports.len()];
242
243 let mut new_port: u32 = 0;
244 let kr = unsafe { mach_port_allocate(task, 1, &mut new_port) };
246 if kr == 0 {
247 unsafe {
252 mach_port_mod_refs(task, new_port, 1, -1);
253 }
254 }
255 unsafe {
256 let mut ptype: u32 = 0;
257 mach_port_type(task, base_port, &mut ptype);
258 }
259 let t1 = mach_time();
260 timings.push(t1.wrapping_sub(t0));
261 }
262 }
263
264 for &port in &ports {
265 unsafe {
266 mach_port_mod_refs(task, port, 0 , -1);
268 mach_port_mod_refs(task, port, 1 , -1);
270 }
271 }
272
273 extract_timing_entropy(&timings, n_samples)
274 }
275 }
276}
277
278#[cfg(target_os = "macos")]
279impl MachIPCSource {
280 fn collect_simple(&self, n_samples: usize) -> Vec<u8> {
281 let raw_count = n_samples * 4 + 64;
282 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
283 let task = unsafe { mach_task_self() };
284
285 for _ in 0..raw_count {
286 let t0 = mach_time();
287 let mut port: u32 = 0;
288 let kr = unsafe { mach_port_allocate(task, 1, &mut port) };
289 if kr == 0 {
290 unsafe {
294 mach_port_mod_refs(task, port, 1, -1);
295 }
296 }
297 let t1 = mach_time();
298 timings.push(t1.wrapping_sub(t0));
299 }
300 extract_timing_entropy(&timings, n_samples)
301 }
302}
303
304#[cfg(target_os = "macos")]
306#[repr(C)]
307struct MachMsgHeader {
308 msgh_bits: u32,
309 msgh_size: u32,
310 msgh_remote_port: u32,
311 msgh_local_port: u32,
312 msgh_voucher_port: u32,
313 msgh_id: i32,
314}
315
316#[cfg(target_os = "macos")]
317#[repr(C)]
318struct MachMsgBody {
319 msgh_descriptor_count: u32,
320}
321
322#[cfg(target_os = "macos")]
323#[repr(C)]
324struct MachMsgOOLDescriptor {
325 address: *mut u8,
326 deallocate: u8,
327 copy: u8,
328 _pad: u8,
329 ool_type: u8, size: u32,
331}
332
333#[cfg(target_os = "macos")]
334#[repr(C)]
335struct MachMsgOOL {
336 header: MachMsgHeader,
337 body: MachMsgBody,
338 ool: MachMsgOOLDescriptor,
339}
340
341#[cfg(target_os = "macos")]
345impl MachMsgOOL {
346 fn zeroed() -> Self {
347 unsafe { std::mem::zeroed() }
349 }
350}
351
352#[cfg(target_os = "macos")]
353unsafe extern "C" {
354 fn mach_task_self() -> u32;
355 fn mach_port_allocate(task: u32, right: i32, name: *mut u32) -> i32;
356 fn mach_port_mod_refs(task: u32, name: u32, right: i32, delta: i32) -> i32;
357 fn mach_port_insert_right(task: u32, name: u32, poly: u32, poly_poly: u32) -> i32;
358 fn mach_port_type(task: u32, name: u32, ptype: *mut u32) -> i32;
359 fn mach_msg(
360 msg: *mut MachMsgHeader,
361 option: i32,
362 send_size: u32,
363 rcv_size: u32,
364 rcv_name: u32,
365 timeout: u32,
366 notify: u32,
367 ) -> i32;
368 fn vm_deallocate(target: u32, addr: usize, size: usize) -> i32;
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn info() {
377 let src = MachIPCSource::default();
378 assert_eq!(src.name(), "mach_ipc");
379 assert_eq!(src.info().category, SourceCategory::IPC);
380 assert!(!src.info().composite);
381 }
382
383 #[test]
384 fn default_config() {
385 let config = MachIPCConfig::default();
386 assert_eq!(config.num_ports, 8);
387 assert_eq!(config.ool_size, 4096);
388 assert!(config.use_complex_messages);
389 }
390
391 #[test]
392 fn custom_config() {
393 let src = MachIPCSource {
394 config: MachIPCConfig {
395 num_ports: 4,
396 ool_size: 8192,
397 use_complex_messages: false,
398 },
399 };
400 assert_eq!(src.config.num_ports, 4);
401 assert!(!src.config.use_complex_messages);
402 }
403
404 #[test]
405 #[ignore] fn collects_bytes() {
407 let src = MachIPCSource::default();
408 assert!(src.is_available());
409 let data = src.collect(64);
410 assert!(!data.is_empty());
411 assert!(data.len() <= 64);
412 }
413
414 #[test]
415 #[ignore] fn simple_mode_collects_bytes() {
417 let src = MachIPCSource {
418 config: MachIPCConfig {
419 use_complex_messages: false,
420 ..MachIPCConfig::default()
421 },
422 };
423 assert!(!src.collect(64).is_empty());
424 }
425}