openentropy_core/sources/frontier/
mach_ipc.rs1use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::thread;
6
7use crate::source::{EntropySource, SourceCategory, SourceInfo};
8use crate::sources::helpers::{extract_timing_entropy, mach_time};
9
10#[derive(Debug, Clone)]
22pub struct MachIPCConfig {
23 pub num_ports: usize,
31
32 pub ool_size: usize,
40
41 pub use_complex_messages: bool,
49}
50
51impl Default for MachIPCConfig {
52 fn default() -> Self {
53 Self {
54 num_ports: 8,
55 ool_size: 4096,
56 use_complex_messages: true,
57 }
58 }
59}
60
61#[derive(Default)]
89pub struct MachIPCSource {
90 pub config: MachIPCConfig,
92}
93
94static MACH_IPC_INFO: SourceInfo = SourceInfo {
95 name: "mach_ipc",
96 description: "Mach port complex OOL message and VM remapping timing jitter",
97 physics: "Sends complex Mach messages with out-of-line (OOL) memory descriptors via \
98 mach_msg(), round-robining across multiple ports. OOL descriptors force kernel \
99 VM remapping (vm_map_copyin/copyout) which exercises page table operations. \
100 Round-robin across ports with varied queue depths creates namespace contention. \
101 Timing captures: OOL VM remap latency, port namespace splay tree operations, \
102 per-port lock contention, and cross-core scheduling nondeterminism.",
103 category: SourceCategory::Frontier,
104 platform_requirements: &[],
105 entropy_rate_estimate: 2000.0,
106 composite: false,
107};
108
109impl EntropySource for MachIPCSource {
110 fn info(&self) -> &SourceInfo {
111 &MACH_IPC_INFO
112 }
113
114 fn is_available(&self) -> bool {
115 cfg!(target_os = "macos")
116 }
117
118 fn collect(&self, n_samples: usize) -> Vec<u8> {
119 let raw_count = n_samples * 4 + 64;
120 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
121
122 let task = unsafe { mach_task_self() };
124 let num_ports = self.config.num_ports.max(1);
125
126 let mut ports: Vec<u32> = Vec::with_capacity(num_ports);
127 for _ in 0..num_ports {
128 let mut port: u32 = 0;
129 let kr = unsafe {
131 mach_port_allocate(task, 1 , &mut port)
132 };
133 if kr == 0 {
134 let kr2 = unsafe {
136 mach_port_insert_right(task, port, port, 20 )
137 };
138 if kr2 == 0 {
139 ports.push(port);
140 } else {
141 unsafe {
142 mach_port_mod_refs(task, port, 1, -1);
143 }
144 }
145 }
146 }
147
148 if ports.is_empty() {
149 return self.collect_simple(n_samples);
150 }
151
152 if self.config.use_complex_messages {
153 let ool_size = self.config.ool_size.max(1);
154 let ool_buf = vec![0xBEu8; ool_size];
155
156 let stop = Arc::new(AtomicBool::new(false));
157 let stop2 = stop.clone();
158 let recv_ports = ports.clone();
159 let receiver = thread::spawn(move || {
160 let mut recv_buf = vec![0u8; 1024 + ool_size * 2];
161 while !stop2.load(Ordering::Relaxed) {
162 for &port in &recv_ports {
163 unsafe {
165 let hdr = recv_buf.as_mut_ptr() as *mut MachMsgHeader;
166 (*hdr).msgh_local_port = port;
167 (*hdr).msgh_size = recv_buf.len() as u32;
168 mach_msg(hdr, 2 | 0x100, 0, recv_buf.len() as u32, port, 0, 0);
169 }
170 }
171 std::thread::yield_now();
172 }
173 });
174
175 for i in 0..raw_count {
176 let port = ports[i % ports.len()];
177
178 let mut msg = MachMsgOOL::zeroed();
179 msg.header.msgh_bits = 0x80000000 | 17; msg.header.msgh_size = std::mem::size_of::<MachMsgOOL>() as u32;
181 msg.header.msgh_remote_port = port;
182 msg.header.msgh_local_port = 0;
183 msg.header.msgh_id = i as i32;
184 msg.body.msgh_descriptor_count = 1;
185 msg.ool.address = ool_buf.as_ptr() as *mut _;
186 msg.ool.size = ool_size as u32;
187 msg.ool.deallocate = 0;
188 msg.ool.copy = 1; msg.ool.ool_type = 1; let t0 = mach_time();
192 unsafe {
194 mach_msg(&mut msg.header, 1 | 0x80, msg.header.msgh_size, 0, 0, 10, 0);
195 }
196 let t1 = mach_time();
197 timings.push(t1.wrapping_sub(t0));
198 }
199
200 stop.store(true, Ordering::Relaxed);
201 let _ = receiver.join();
202 } else {
203 for i in 0..raw_count {
204 let t0 = mach_time();
205 let base_port = ports[i % ports.len()];
206
207 let mut new_port: u32 = 0;
208 let kr = unsafe { mach_port_allocate(task, 1, &mut new_port) };
210 if kr == 0 {
211 unsafe {
212 mach_port_deallocate(task, new_port);
213 mach_port_mod_refs(task, new_port, 1, -1);
214 }
215 }
216 unsafe {
217 let mut ptype: u32 = 0;
218 mach_port_type(task, base_port, &mut ptype);
219 }
220 let t1 = mach_time();
221 timings.push(t1.wrapping_sub(t0));
222 }
223 }
224
225 for &port in &ports {
226 unsafe {
227 mach_port_mod_refs(task, port, 1, -1);
228 }
229 }
230
231 extract_timing_entropy(&timings, n_samples)
232 }
233}
234
235impl MachIPCSource {
236 fn collect_simple(&self, n_samples: usize) -> Vec<u8> {
237 let raw_count = n_samples * 4 + 64;
238 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
239 let task = unsafe { mach_task_self() };
240
241 for _ in 0..raw_count {
242 let t0 = mach_time();
243 let mut port: u32 = 0;
244 let kr = unsafe { mach_port_allocate(task, 1, &mut port) };
245 if kr == 0 {
246 unsafe {
247 mach_port_deallocate(task, port);
248 mach_port_mod_refs(task, port, 1, -1);
249 }
250 }
251 let t1 = mach_time();
252 timings.push(t1.wrapping_sub(t0));
253 }
254 extract_timing_entropy(&timings, n_samples)
255 }
256}
257
258#[repr(C)]
260struct MachMsgHeader {
261 msgh_bits: u32,
262 msgh_size: u32,
263 msgh_remote_port: u32,
264 msgh_local_port: u32,
265 msgh_voucher_port: u32,
266 msgh_id: i32,
267}
268
269#[repr(C)]
270struct MachMsgBody {
271 msgh_descriptor_count: u32,
272}
273
274#[repr(C)]
275struct MachMsgOOLDescriptor {
276 address: *mut u8,
277 deallocate: u8,
278 copy: u8,
279 ool_type: u8,
280 _pad: u8,
281 size: u32,
282}
283
284#[repr(C)]
285struct MachMsgOOL {
286 header: MachMsgHeader,
287 body: MachMsgBody,
288 ool: MachMsgOOLDescriptor,
289}
290
291unsafe impl Send for MachMsgOOL {}
294
295impl MachMsgOOL {
296 fn zeroed() -> Self {
297 unsafe { std::mem::zeroed() }
299 }
300}
301
302unsafe extern "C" {
303 fn mach_task_self() -> u32;
304 fn mach_port_allocate(task: u32, right: i32, name: *mut u32) -> i32;
305 fn mach_port_deallocate(task: u32, name: u32) -> i32;
306 fn mach_port_mod_refs(task: u32, name: u32, right: i32, delta: i32) -> i32;
307 fn mach_port_insert_right(task: u32, name: u32, poly: u32, poly_poly: u32) -> i32;
308 fn mach_port_type(task: u32, name: u32, ptype: *mut u32) -> i32;
309 fn mach_msg(
310 msg: *mut MachMsgHeader,
311 option: i32,
312 send_size: u32,
313 rcv_size: u32,
314 rcv_name: u32,
315 timeout: u32,
316 notify: u32,
317 ) -> i32;
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn info() {
326 let src = MachIPCSource::default();
327 assert_eq!(src.name(), "mach_ipc");
328 assert_eq!(src.info().category, SourceCategory::Frontier);
329 assert!(!src.info().composite);
330 }
331
332 #[test]
333 fn default_config() {
334 let config = MachIPCConfig::default();
335 assert_eq!(config.num_ports, 8);
336 assert_eq!(config.ool_size, 4096);
337 assert!(config.use_complex_messages);
338 }
339
340 #[test]
341 fn custom_config() {
342 let src = MachIPCSource {
343 config: MachIPCConfig {
344 num_ports: 4,
345 ool_size: 8192,
346 use_complex_messages: false,
347 },
348 };
349 assert_eq!(src.config.num_ports, 4);
350 assert!(!src.config.use_complex_messages);
351 }
352
353 #[test]
354 #[ignore] fn collects_bytes() {
356 let src = MachIPCSource::default();
357 assert!(src.is_available());
358 let data = src.collect(64);
359 assert!(!data.is_empty());
360 assert!(data.len() <= 64);
361 }
362
363 #[test]
364 #[ignore] fn simple_mode_collects_bytes() {
366 let src = MachIPCSource {
367 config: MachIPCConfig {
368 use_complex_messages: false,
369 ..MachIPCConfig::default()
370 },
371 };
372 assert!(!src.collect(64).is_empty());
373 }
374}