openentropy_core/sources/frontier/
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: 2000.0,
111 composite: false,
112};
113
114impl EntropySource for MachIPCSource {
115 fn info(&self) -> &SourceInfo {
116 &MACH_IPC_INFO
117 }
118
119 fn is_available(&self) -> bool {
120 cfg!(target_os = "macos")
121 }
122
123 fn collect(&self, n_samples: usize) -> Vec<u8> {
124 #[cfg(not(target_os = "macos"))]
125 {
126 let _ = n_samples;
127 Vec::new()
128 }
129
130 #[cfg(target_os = "macos")]
131 {
132 let raw_count = n_samples * 4 + 64;
133 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
134
135 let task = unsafe { mach_task_self() };
137 let num_ports = self.config.num_ports.max(1);
138
139 let mut ports: Vec<u32> = Vec::with_capacity(num_ports);
140 for _ in 0..num_ports {
141 let mut port: u32 = 0;
142 let kr = unsafe {
144 mach_port_allocate(task, 1 , &mut port)
145 };
146 if kr == 0 {
147 let kr2 = unsafe {
149 mach_port_insert_right(
150 task, port, port, 20, )
152 };
153 if kr2 == 0 {
154 ports.push(port);
155 } else {
156 unsafe {
157 mach_port_mod_refs(task, port, 1, -1);
158 }
159 }
160 }
161 }
162
163 if ports.is_empty() {
164 return self.collect_simple(n_samples);
165 }
166
167 if self.config.use_complex_messages {
168 let ool_size = self.config.ool_size.max(1);
169 let ool_buf = vec![0xBEu8; ool_size];
170
171 let stop = Arc::new(AtomicBool::new(false));
172 let stop2 = stop.clone();
173 let recv_ports = ports.clone();
174 let receiver = thread::spawn(move || {
175 let mut recv_buf = vec![0u8; 1024 + ool_size * 2];
176 while !stop2.load(Ordering::Relaxed) {
177 for &port in &recv_ports {
178 unsafe {
180 let hdr = recv_buf.as_mut_ptr() as *mut MachMsgHeader;
181 (*hdr).msgh_local_port = port;
182 (*hdr).msgh_size = recv_buf.len() as u32;
183 mach_msg(hdr, 2 | 0x100, 0, recv_buf.len() as u32, port, 0, 0);
184 }
185 }
186 std::thread::yield_now();
187 }
188 });
189
190 for i in 0..raw_count {
191 let port = ports[i % ports.len()];
192
193 let mut msg = MachMsgOOL::zeroed();
194 msg.header.msgh_bits = 0x80000000 | 17; msg.header.msgh_size = std::mem::size_of::<MachMsgOOL>() as u32;
196 msg.header.msgh_remote_port = port;
197 msg.header.msgh_local_port = 0;
198 msg.header.msgh_id = i as i32;
199 msg.body.msgh_descriptor_count = 1;
200 msg.ool.address = ool_buf.as_ptr() as *mut _;
201 msg.ool.size = ool_size as u32;
202 msg.ool.deallocate = 0;
203 msg.ool.copy = 1; msg.ool.ool_type = 1; let t0 = mach_time();
207 unsafe {
209 mach_msg(&mut msg.header, 1 | 0x80, msg.header.msgh_size, 0, 0, 10, 0);
210 }
211 let t1 = mach_time();
212 timings.push(t1.wrapping_sub(t0));
213 }
214
215 stop.store(true, Ordering::Relaxed);
216 let _ = receiver.join();
217 } else {
218 for i in 0..raw_count {
219 let t0 = mach_time();
220 let base_port = ports[i % ports.len()];
221
222 let mut new_port: u32 = 0;
223 let kr = unsafe { mach_port_allocate(task, 1, &mut new_port) };
225 if kr == 0 {
226 unsafe {
227 mach_port_deallocate(task, new_port);
228 mach_port_mod_refs(task, new_port, 1, -1);
229 }
230 }
231 unsafe {
232 let mut ptype: u32 = 0;
233 mach_port_type(task, base_port, &mut ptype);
234 }
235 let t1 = mach_time();
236 timings.push(t1.wrapping_sub(t0));
237 }
238 }
239
240 for &port in &ports {
241 unsafe {
242 mach_port_mod_refs(task, port, 1, -1);
243 }
244 }
245
246 extract_timing_entropy(&timings, n_samples)
247 }
248 }
249}
250
251#[cfg(target_os = "macos")]
252impl MachIPCSource {
253 fn collect_simple(&self, n_samples: usize) -> Vec<u8> {
254 let raw_count = n_samples * 4 + 64;
255 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
256 let task = unsafe { mach_task_self() };
257
258 for _ in 0..raw_count {
259 let t0 = mach_time();
260 let mut port: u32 = 0;
261 let kr = unsafe { mach_port_allocate(task, 1, &mut port) };
262 if kr == 0 {
263 unsafe {
264 mach_port_deallocate(task, port);
265 mach_port_mod_refs(task, port, 1, -1);
266 }
267 }
268 let t1 = mach_time();
269 timings.push(t1.wrapping_sub(t0));
270 }
271 extract_timing_entropy(&timings, n_samples)
272 }
273}
274
275#[cfg(target_os = "macos")]
277#[repr(C)]
278struct MachMsgHeader {
279 msgh_bits: u32,
280 msgh_size: u32,
281 msgh_remote_port: u32,
282 msgh_local_port: u32,
283 msgh_voucher_port: u32,
284 msgh_id: i32,
285}
286
287#[cfg(target_os = "macos")]
288#[repr(C)]
289struct MachMsgBody {
290 msgh_descriptor_count: u32,
291}
292
293#[cfg(target_os = "macos")]
294#[repr(C)]
295struct MachMsgOOLDescriptor {
296 address: *mut u8,
297 deallocate: u8,
298 copy: u8,
299 ool_type: u8,
300 _pad: u8,
301 size: u32,
302}
303
304#[cfg(target_os = "macos")]
305#[repr(C)]
306struct MachMsgOOL {
307 header: MachMsgHeader,
308 body: MachMsgBody,
309 ool: MachMsgOOLDescriptor,
310}
311
312#[cfg(target_os = "macos")]
315unsafe impl Send for MachMsgOOL {}
316
317#[cfg(target_os = "macos")]
318impl MachMsgOOL {
319 fn zeroed() -> Self {
320 unsafe { std::mem::zeroed() }
322 }
323}
324
325#[cfg(target_os = "macos")]
326unsafe extern "C" {
327 fn mach_task_self() -> u32;
328 fn mach_port_allocate(task: u32, right: i32, name: *mut u32) -> i32;
329 fn mach_port_deallocate(task: u32, name: u32) -> i32;
330 fn mach_port_mod_refs(task: u32, name: u32, right: i32, delta: i32) -> i32;
331 fn mach_port_insert_right(task: u32, name: u32, poly: u32, poly_poly: u32) -> i32;
332 fn mach_port_type(task: u32, name: u32, ptype: *mut u32) -> i32;
333 fn mach_msg(
334 msg: *mut MachMsgHeader,
335 option: i32,
336 send_size: u32,
337 rcv_size: u32,
338 rcv_name: u32,
339 timeout: u32,
340 notify: u32,
341 ) -> i32;
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn info() {
350 let src = MachIPCSource::default();
351 assert_eq!(src.name(), "mach_ipc");
352 assert_eq!(src.info().category, SourceCategory::IPC);
353 assert!(!src.info().composite);
354 }
355
356 #[test]
357 fn default_config() {
358 let config = MachIPCConfig::default();
359 assert_eq!(config.num_ports, 8);
360 assert_eq!(config.ool_size, 4096);
361 assert!(config.use_complex_messages);
362 }
363
364 #[test]
365 fn custom_config() {
366 let src = MachIPCSource {
367 config: MachIPCConfig {
368 num_ports: 4,
369 ool_size: 8192,
370 use_complex_messages: false,
371 },
372 };
373 assert_eq!(src.config.num_ports, 4);
374 assert!(!src.config.use_complex_messages);
375 }
376
377 #[test]
378 #[ignore] fn collects_bytes() {
380 let src = MachIPCSource::default();
381 assert!(src.is_available());
382 let data = src.collect(64);
383 assert!(!data.is_empty());
384 assert!(data.len() <= 64);
385 }
386
387 #[test]
388 #[ignore] fn simple_mode_collects_bytes() {
390 let src = MachIPCSource {
391 config: MachIPCConfig {
392 use_complex_messages: false,
393 ..MachIPCConfig::default()
394 },
395 };
396 assert!(!src.collect(64).is_empty());
397 }
398}