1use std::ffi::{CString, c_char, c_int, c_void};
4use std::fmt;
5use std::mem::{size_of, zeroed};
6
7use super::{KERN_SUCCESS, MachPort, current_task, deallocate_port};
8use crate::protocol::{CONTROL_FRAME_LEN, ManifestEntry, PeerAccess, TransferManifest};
9use native_ipc_core::layout::ValidationExpectations;
10
11type MachMsgReturn = c_int;
12type PosixSpawnAttr = *mut c_void;
13
14const MACH_PORT_NULL: MachPort = 0;
15const MACH_PORT_RIGHT_RECEIVE: c_int = 1;
16const MACH_MSG_TYPE_COPY_SEND: u8 = 19;
17const MACH_MSG_TYPE_MAKE_SEND: u8 = 20;
18const MACH_MSG_TYPE_PORT_SEND: u8 = 17;
19const MACH_MSG_PORT_DESCRIPTOR: u8 = 0;
20const MACH_MSGH_BITS_COMPLEX: u32 = 0x8000_0000;
21const MACH_SEND_MSG: u32 = 0x0000_0001;
22const MACH_RCV_MSG: u32 = 0x0000_0002;
23const MACH_SEND_TIMEOUT: u32 = 0x0000_0010;
24const MACH_RCV_TIMEOUT: u32 = 0x0000_0100;
25const MACH_RCV_TRAILER_AUDIT: u32 = 3 << 24;
26const TASK_BOOTSTRAP_PORT: c_int = 4;
27const MESSAGE_ID: c_int = 0x4e49_5043;
28const MESSAGE_MAGIC: [u8; 8] = *b"NIPCMACH";
29const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCCAP1";
30const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
31const COMMIT_MAGIC: [u8; 8] = *b"NIPCCMT1";
32const ENV_NONCE: &str = "NATIVE_IPC_MACH_NONCE";
33const ENV_PARENT_PID: &str = "NATIVE_IPC_PARENT_PID";
34const TIMEOUT_MS: u32 = 10_000;
35
36unsafe extern "C" {
37 fn mach_port_allocate(task: MachPort, right: c_int, name: *mut MachPort) -> c_int;
38 fn mach_port_insert_right(
39 task: MachPort,
40 name: MachPort,
41 poly: MachPort,
42 poly_poly: c_int,
43 ) -> c_int;
44 fn mach_port_mod_refs(task: MachPort, name: MachPort, right: c_int, delta: c_int) -> c_int;
45 fn mach_msg(
46 message: *mut MachMsgHeader,
47 option: u32,
48 send_size: u32,
49 receive_limit: u32,
50 receive_name: MachPort,
51 timeout: u32,
52 notify: MachPort,
53 ) -> MachMsgReturn;
54 fn mach_msg_destroy(message: *mut MachMsgHeader);
55 fn task_get_special_port(task: MachPort, which: c_int, port: *mut MachPort) -> c_int;
56 fn posix_spawnattr_init(attributes: *mut PosixSpawnAttr) -> c_int;
57 fn posix_spawnattr_destroy(attributes: *mut PosixSpawnAttr) -> c_int;
58 fn posix_spawnattr_setspecialport_np(
59 attributes: *mut PosixSpawnAttr,
60 port: MachPort,
61 which: c_int,
62 ) -> c_int;
63 fn posix_spawn(
64 pid: *mut Pid,
65 path: *const c_char,
66 file_actions: *const c_void,
67 attributes: *const PosixSpawnAttr,
68 argv: *const *mut c_char,
69 envp: *const *mut c_char,
70 ) -> c_int;
71 fn kill(pid: Pid, signal: c_int) -> c_int;
72 fn waitpid(pid: Pid, status: *mut c_int, options: c_int) -> Pid;
73}
74
75#[link(name = "bsm")]
76unsafe extern "C" {
77 fn audit_token_to_pid(token: AuditToken) -> Pid;
78}
79
80type Pid = c_int;
81
82#[repr(C)]
83#[derive(Clone, Copy)]
84struct MachMsgHeader {
85 bits: u32,
86 size: u32,
87 remote_port: MachPort,
88 local_port: MachPort,
89 voucher_port: MachPort,
90 id: c_int,
91}
92
93#[repr(C)]
94#[derive(Clone, Copy)]
95struct MachMsgBody {
96 descriptor_count: u32,
97}
98
99#[repr(C)]
100#[derive(Clone, Copy)]
101struct MachMsgPortDescriptor {
102 name: MachPort,
103 pad1: u32,
104 pad2: u16,
105 disposition: u8,
106 descriptor_type: u8,
107}
108
109#[repr(C)]
110#[derive(Clone, Copy)]
111struct AuditToken {
112 values: [u32; 8],
113}
114
115#[repr(C)]
116#[derive(Clone, Copy)]
117struct AuditTrailer {
118 trailer_type: u32,
119 trailer_size: u32,
120 sequence: u32,
121 sender_security: [u32; 2],
122 audit: AuditToken,
123}
124
125#[repr(C)]
126struct PortMessage {
127 header: MachMsgHeader,
128 body: MachMsgBody,
129 descriptor: MachMsgPortDescriptor,
130 magic: [u8; 8],
131 nonce: [u8; 32],
132 transcript: [u8; CONTROL_FRAME_LEN],
133}
134
135#[repr(C)]
136struct ReceiveBuffer {
137 message: PortMessage,
138 trailer: AuditTrailer,
139}
140
141#[derive(Debug)]
143pub enum BootstrapError {
144 Mach {
146 operation: &'static str,
148 code: c_int,
150 },
151 Spawn(c_int),
153 InvalidMessage,
155 WrongPeer {
157 expected: u32,
159 actual: u32,
161 },
162 InvalidEnvironment,
164}
165
166impl fmt::Display for BootstrapError {
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(formatter, "Mach bootstrap failed: {self:?}")
169 }
170}
171impl std::error::Error for BootstrapError {}
172
173pub struct SendRight(MachPort);
175impl SendRight {
176 pub(super) const fn name(&self) -> MachPort {
178 self.0
179 }
180}
181impl Drop for SendRight {
182 fn drop(&mut self) {
183 deallocate_port(current_task(), self.0);
184 }
185}
186
187struct ReceiveRight(MachPort);
188impl ReceiveRight {
189 fn allocate() -> Result<Self, BootstrapError> {
190 let mut name = MACH_PORT_NULL;
191 let result =
193 unsafe { mach_port_allocate(current_task(), MACH_PORT_RIGHT_RECEIVE, &mut name) };
194 mach("mach_port_allocate", result)?;
195 if name == MACH_PORT_NULL {
196 return Err(BootstrapError::InvalidMessage);
197 }
198 Ok(Self(name))
199 }
200 fn make_send(&self) -> Result<(), BootstrapError> {
201 mach("mach_port_insert_right", unsafe {
203 mach_port_insert_right(
204 current_task(),
205 self.0,
206 self.0,
207 MACH_MSG_TYPE_MAKE_SEND.into(),
208 )
209 })
210 }
211}
212impl Drop for ReceiveRight {
213 fn drop(&mut self) {
214 let _ = unsafe { mach_port_mod_refs(current_task(), self.0, MACH_PORT_RIGHT_RECEIVE, -1) };
216 }
217}
218
219pub struct SpawnedHelper {
221 pid: Pid,
222 nonce: [u8; 32],
223 receive: Option<ReceiveRight>,
224}
225
226impl SpawnedHelper {
227 pub fn spawn(path: &CString, arguments: &[CString]) -> Result<Self, BootstrapError> {
229 let nonce = random_nonce()?;
230 let receive = ReceiveRight::allocate()?;
231 receive.make_send()?;
232 let mut attributes: PosixSpawnAttr = std::ptr::null_mut();
233 spawn_result(unsafe { posix_spawnattr_init(&mut attributes) })?;
235 struct AttributeGuard(PosixSpawnAttr);
236 impl Drop for AttributeGuard {
237 fn drop(&mut self) {
238 let _ = unsafe { posix_spawnattr_destroy(&mut self.0) };
240 }
241 }
242 let mut guard = AttributeGuard(attributes);
243 spawn_result(unsafe {
245 posix_spawnattr_setspecialport_np(&mut guard.0, receive.0, TASK_BOOTSTRAP_PORT)
246 })?;
247
248 let mut argv_storage = Vec::with_capacity(arguments.len() + 1);
249 argv_storage.push(path.clone());
250 argv_storage.extend(arguments.iter().cloned());
251 let mut argv: Vec<*mut c_char> = argv_storage
252 .iter_mut()
253 .map(|argument| argument.as_ptr().cast_mut())
254 .collect();
255 argv.push(std::ptr::null_mut());
256
257 let nonce_value = hex(&nonce);
258 let parent_pid = std::process::id().to_string();
259 let mut environment: Vec<CString> = std::env::vars_os()
260 .filter(|(key, _)| key != ENV_NONCE && key != ENV_PARENT_PID)
261 .filter_map(|(key, value)| {
262 CString::new(format!(
263 "{}={}",
264 key.to_string_lossy(),
265 value.to_string_lossy()
266 ))
267 .ok()
268 })
269 .collect();
270 environment.push(CString::new(format!("{ENV_NONCE}={nonce_value}")).expect("hex env"));
271 environment.push(CString::new(format!("{ENV_PARENT_PID}={parent_pid}")).expect("pid env"));
272 let mut envp: Vec<*mut c_char> = environment
273 .iter_mut()
274 .map(|entry| entry.as_ptr().cast_mut())
275 .collect();
276 envp.push(std::ptr::null_mut());
277 let mut pid = 0;
278 let result = unsafe {
280 posix_spawn(
281 &mut pid,
282 path.as_ptr(),
283 std::ptr::null(),
284 &guard.0,
285 argv.as_ptr(),
286 envp.as_ptr(),
287 )
288 };
289 spawn_result(result)?;
290 deallocate_port(current_task(), receive.0);
292 Ok(Self {
293 pid,
294 nonce,
295 receive: Some(receive),
296 })
297 }
298
299 pub fn authenticate(mut self) -> Result<ParentChannel, BootstrapError> {
301 let receive = self.receive.take().ok_or(BootstrapError::InvalidMessage)?;
302 let child_send = match receive_port(
303 &receive,
304 &self.nonce,
305 self.pid as u32,
306 &[0; CONTROL_FRAME_LEN],
307 ) {
308 Ok(right) => right,
309 Err(error) => {
310 terminate_and_reap(self.pid);
311 self.pid = 0;
312 return Err(error);
313 }
314 };
315 let channel = ParentChannel {
316 peer_send: child_send,
317 _receive: receive,
318 nonce: self.nonce,
319 peer_pid: self.pid as u32,
320 reaped: false,
321 pending_entries: Vec::new(),
322 next_transfer_id: 1,
323 poisoned: false,
324 };
325 self.pid = 0;
326 Ok(channel)
327 }
328
329 pub const fn pid(&self) -> u32 {
331 self.pid as u32
332 }
333}
334
335impl Drop for SpawnedHelper {
336 fn drop(&mut self) {
337 if self.pid > 0 {
338 terminate_and_reap(self.pid);
339 }
340 }
341}
342
343impl Drop for ParentChannel {
344 fn drop(&mut self) {
345 if !self.reaped {
346 terminate_and_reap(self.peer_pid as Pid);
347 }
348 }
349}
350
351pub struct ParentChannel {
353 peer_send: SendRight,
354 _receive: ReceiveRight,
355 nonce: [u8; 32],
356 peer_pid: u32,
357 reaped: bool,
358 pending_entries: Vec<ManifestEntry>,
359 next_transfer_id: u64,
360 poisoned: bool,
361}
362
363impl ParentChannel {
364 pub(super) fn send(
366 &mut self,
367 port: MachPort,
368 expected: ValidationExpectations,
369 len: usize,
370 access: PeerAccess,
371 ) -> Result<(), BootstrapError> {
372 let result = (|| {
373 let entry = ManifestEntry::validated(expected, len, access)
374 .ok_or(BootstrapError::InvalidMessage)?;
375 let transcript = self.single_manifest(entry)?.encode(CAPABILITY_MAGIC);
376 send_port(
377 self.peer_send.0,
378 port,
379 MACH_MSG_TYPE_COPY_SEND,
380 &self.nonce,
381 &transcript,
382 )?;
383 self.pending_entries.push(entry);
384 Ok(())
385 })();
386 if result.is_err() {
387 self.poison();
388 }
389 result
390 }
391 pub const fn peer_pid(&self) -> u32 {
393 self.peer_pid
394 }
395 pub(super) fn ready_and_commit(&mut self) -> Result<(), BootstrapError> {
397 let result = (|| {
398 let manifest = self.batch_manifest()?;
399 drop(receive_port(
400 &self._receive,
401 &self.nonce,
402 self.peer_pid,
403 &manifest.encode(READY_MAGIC),
404 )?);
405 #[cfg(test)]
406 std::thread::sleep(std::time::Duration::from_millis(50));
407 let marker = ReceiveRight::allocate()?;
408 send_port(
409 self.peer_send.0,
410 marker.0,
411 MACH_MSG_TYPE_MAKE_SEND,
412 &self.nonce,
413 &manifest.encode(COMMIT_MAGIC),
414 )?;
415 self.pending_entries.clear();
416 self.next_transfer_id = self
417 .next_transfer_id
418 .checked_add(1)
419 .ok_or(BootstrapError::InvalidMessage)?;
420 Ok(())
421 })();
422 if result.is_err() {
423 self.poison();
424 }
425 result
426 }
427 pub fn wait(mut self) -> Result<(), BootstrapError> {
429 let mut status = 0;
430 let result = unsafe { waitpid(self.peer_pid as Pid, &mut status, 0) };
432 self.reaped = result == self.peer_pid as Pid;
433 if self.reaped && status == 0 {
434 Ok(())
435 } else {
436 Err(BootstrapError::Spawn(status))
437 }
438 }
439
440 fn single_manifest(&self, entry: ManifestEntry) -> Result<TransferManifest, BootstrapError> {
441 TransferManifest::new(
442 self.nonce,
443 std::process::id(),
444 self.peer_pid,
445 self.next_transfer_id,
446 vec![entry],
447 )
448 .ok_or(BootstrapError::InvalidMessage)
449 }
450
451 fn batch_manifest(&self) -> Result<TransferManifest, BootstrapError> {
452 if self.poisoned {
453 return Err(BootstrapError::InvalidMessage);
454 }
455 TransferManifest::new(
456 self.nonce,
457 std::process::id(),
458 self.peer_pid,
459 self.next_transfer_id,
460 self.pending_entries.clone(),
461 )
462 .ok_or(BootstrapError::InvalidMessage)
463 }
464
465 fn poison(&mut self) {
466 self.poisoned = true;
467 if !self.reaped {
468 terminate_and_reap(self.peer_pid as Pid);
469 self.reaped = true;
470 }
471 }
472
473 pub(super) fn poison_transaction(&mut self) {
474 self.poison();
475 }
476}
477
478pub struct ChildChannel {
480 _parent_send: SendRight,
481 receive: ReceiveRight,
482 nonce: [u8; 32],
483 parent_pid: u32,
484 pending_entries: Vec<ManifestEntry>,
485 next_transfer_id: u64,
486 poisoned: bool,
487}
488
489impl ChildChannel {
490 pub fn connect_from_environment() -> Result<Self, BootstrapError> {
492 let nonce = parse_nonce(
493 &std::env::var(ENV_NONCE).map_err(|_| BootstrapError::InvalidEnvironment)?,
494 )?;
495 let parent_pid = std::env::var(ENV_PARENT_PID)
496 .map_err(|_| BootstrapError::InvalidEnvironment)?
497 .parse()
498 .map_err(|_| BootstrapError::InvalidEnvironment)?;
499 let mut parent = MACH_PORT_NULL;
500 mach("task_get_special_port", unsafe {
502 task_get_special_port(current_task(), TASK_BOOTSTRAP_PORT, &mut parent)
503 })?;
504 if parent == MACH_PORT_NULL {
505 return Err(BootstrapError::InvalidEnvironment);
506 }
507 let receive = ReceiveRight::allocate()?;
508 send_port(
509 parent,
510 receive.0,
511 MACH_MSG_TYPE_MAKE_SEND,
512 &nonce,
513 &[0; CONTROL_FRAME_LEN],
514 )?;
515 Ok(Self {
516 _parent_send: SendRight(parent),
517 receive,
518 nonce,
519 parent_pid,
520 pending_entries: Vec::new(),
521 next_transfer_id: 1,
522 poisoned: false,
523 })
524 }
525 pub(super) fn receive(
527 &mut self,
528 expected: ValidationExpectations,
529 len: usize,
530 access: PeerAccess,
531 ) -> Result<SendRight, BootstrapError> {
532 let result = (|| {
533 let entry = ManifestEntry::validated(expected, len, access)
534 .ok_or(BootstrapError::InvalidMessage)?;
535 let transcript = self.single_manifest(entry)?.encode(CAPABILITY_MAGIC);
536 let right = receive_port(&self.receive, &self.nonce, self.parent_pid, &transcript)?;
537 self.pending_entries.push(entry);
538 Ok(right)
539 })();
540 if result.is_err() {
541 self.poisoned = true;
542 }
543 result
544 }
545 pub(super) fn ready_and_wait_commit(&mut self) -> Result<(), BootstrapError> {
547 let result = (|| {
548 let manifest = self.batch_manifest()?;
549 let marker = ReceiveRight::allocate()?;
550 send_port(
551 self._parent_send.0,
552 marker.0,
553 MACH_MSG_TYPE_MAKE_SEND,
554 &self.nonce,
555 &manifest.encode(READY_MAGIC),
556 )?;
557 drop(receive_port(
558 &self.receive,
559 &self.nonce,
560 self.parent_pid,
561 &manifest.encode(COMMIT_MAGIC),
562 )?);
563 self.pending_entries.clear();
564 self.next_transfer_id = self
565 .next_transfer_id
566 .checked_add(1)
567 .ok_or(BootstrapError::InvalidMessage)?;
568 Ok(())
569 })();
570 if result.is_err() {
571 self.poisoned = true;
572 }
573 result
574 }
575
576 fn single_manifest(&self, entry: ManifestEntry) -> Result<TransferManifest, BootstrapError> {
577 TransferManifest::new(
578 self.nonce,
579 self.parent_pid,
580 std::process::id(),
581 self.next_transfer_id,
582 vec![entry],
583 )
584 .ok_or(BootstrapError::InvalidMessage)
585 }
586
587 fn batch_manifest(&self) -> Result<TransferManifest, BootstrapError> {
588 if self.poisoned {
589 return Err(BootstrapError::InvalidMessage);
590 }
591 TransferManifest::new(
592 self.nonce,
593 self.parent_pid,
594 std::process::id(),
595 self.next_transfer_id,
596 self.pending_entries.clone(),
597 )
598 .ok_or(BootstrapError::InvalidMessage)
599 }
600
601 pub(super) fn poison_transaction(&mut self) {
602 self.poisoned = true;
603 }
604}
605
606fn send_port(
607 remote: MachPort,
608 port: MachPort,
609 disposition: u8,
610 nonce: &[u8; 32],
611 transcript: &[u8; CONTROL_FRAME_LEN],
612) -> Result<(), BootstrapError> {
613 let mut message = PortMessage {
614 header: MachMsgHeader {
615 bits: MACH_MSGH_BITS_COMPLEX | u32::from(MACH_MSG_TYPE_COPY_SEND),
616 size: size_of::<PortMessage>() as u32,
617 remote_port: remote,
618 local_port: MACH_PORT_NULL,
619 voucher_port: MACH_PORT_NULL,
620 id: MESSAGE_ID,
621 },
622 body: MachMsgBody {
623 descriptor_count: 1,
624 },
625 descriptor: MachMsgPortDescriptor {
626 name: port,
627 pad1: 0,
628 pad2: 0,
629 disposition,
630 descriptor_type: MACH_MSG_PORT_DESCRIPTOR,
631 },
632 magic: MESSAGE_MAGIC,
633 nonce: *nonce,
634 transcript: *transcript,
635 };
636 mach("mach_msg(send)", unsafe {
638 mach_msg(
639 &mut message.header,
640 MACH_SEND_MSG | MACH_SEND_TIMEOUT,
641 size_of::<PortMessage>() as u32,
642 0,
643 MACH_PORT_NULL,
644 TIMEOUT_MS,
645 MACH_PORT_NULL,
646 )
647 })
648}
649
650fn receive_port(
651 receive: &ReceiveRight,
652 nonce: &[u8; 32],
653 expected_pid: u32,
654 expected_transcript: &[u8; CONTROL_FRAME_LEN],
655) -> Result<SendRight, BootstrapError> {
656 let mut buffer: ReceiveBuffer = unsafe { zeroed() };
658 mach("mach_msg(receive)", unsafe {
660 mach_msg(
661 &mut buffer.message.header,
662 MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_TRAILER_AUDIT,
663 0,
664 size_of::<ReceiveBuffer>() as u32,
665 receive.0,
666 TIMEOUT_MS,
667 MACH_PORT_NULL,
668 )
669 })?;
670 let complex = buffer.message.header.bits & MACH_MSGH_BITS_COMPLEX != 0;
671 if buffer.message.header.size as usize != size_of::<PortMessage>()
672 || !complex
673 || buffer.message.header.id != MESSAGE_ID
674 || buffer.message.body.descriptor_count != 1
675 || buffer.message.descriptor.descriptor_type != MACH_MSG_PORT_DESCRIPTOR
676 || buffer.message.descriptor.disposition != MACH_MSG_TYPE_PORT_SEND
677 || buffer.message.magic != MESSAGE_MAGIC
678 || buffer.message.nonce != *nonce
679 || buffer.message.transcript != *expected_transcript
680 || buffer.message.descriptor.name == MACH_PORT_NULL
681 || buffer.trailer.trailer_size as usize != size_of::<AuditTrailer>()
682 {
683 if complex {
684 unsafe { mach_msg_destroy(&mut buffer.message.header) };
687 }
688 return Err(BootstrapError::InvalidMessage);
689 }
690 let actual = unsafe { audit_token_to_pid(buffer.trailer.audit) } as u32;
692 if actual != expected_pid {
693 deallocate_port(current_task(), buffer.message.descriptor.name);
694 return Err(BootstrapError::WrongPeer {
695 expected: expected_pid,
696 actual,
697 });
698 }
699 Ok(SendRight(buffer.message.descriptor.name))
700}
701
702fn random_nonce() -> Result<[u8; 32], BootstrapError> {
703 let mut nonce = [0_u8; 32];
704 unsafe extern "C" {
706 fn arc4random_buf(buffer: *mut c_void, length: usize);
707 }
708 unsafe { arc4random_buf(nonce.as_mut_ptr().cast(), nonce.len()) };
710 if nonce == [0; 32] {
711 Err(BootstrapError::InvalidEnvironment)
712 } else {
713 Ok(nonce)
714 }
715}
716
717fn mach(operation: &'static str, code: c_int) -> Result<(), BootstrapError> {
718 if code == KERN_SUCCESS {
719 Ok(())
720 } else {
721 Err(BootstrapError::Mach { operation, code })
722 }
723}
724fn spawn_result(code: c_int) -> Result<(), BootstrapError> {
725 if code == 0 {
726 Ok(())
727 } else {
728 Err(BootstrapError::Spawn(code))
729 }
730}
731fn hex(bytes: &[u8]) -> String {
732 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
733}
734fn parse_nonce(encoded: &str) -> Result<[u8; 32], BootstrapError> {
735 if encoded.len() != 64 {
736 return Err(BootstrapError::InvalidEnvironment);
737 }
738 let mut nonce = [0; 32];
739 for (output, pair) in nonce.iter_mut().zip(encoded.as_bytes().chunks_exact(2)) {
740 let pair = std::str::from_utf8(pair).map_err(|_| BootstrapError::InvalidEnvironment)?;
741 *output = u8::from_str_radix(pair, 16).map_err(|_| BootstrapError::InvalidEnvironment)?;
742 }
743 Ok(nonce)
744}
745
746fn terminate_and_reap(pid: Pid) {
747 if pid <= 0 {
748 return;
749 }
750 let _ = unsafe { kill(pid, 9) };
752 let mut status = 0;
753 let _ = unsafe { waitpid(pid, &mut status, 0) };
755}
756
757const _: () = assert!(size_of::<MachMsgHeader>() == 24);
758const _: () = assert!(size_of::<MachMsgPortDescriptor>() == 12);
759const _: () = assert!(size_of::<AuditTrailer>() == 52);
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use native_ipc_core::layout::{
765 AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSetLayout, RegionSpec, RoleId,
766 ValidationExpectations,
767 };
768 use std::os::unix::ffi::OsStrExt;
769 use std::time::{Duration, Instant};
770
771 fn topology() -> (RegionSetLayout, RoleId, RoleId) {
772 let producer = RoleId::new(1).unwrap();
773 let peer = RoleId::new(2).unwrap();
774 let specs = [
775 RegionSpec {
776 role: producer,
777 writer: Endpoint::Initiator,
778 slot_count: 1,
779 payload_bytes: 32,
780 acknowledgement_count: 1,
781 },
782 RegionSpec {
783 role: peer,
784 writer: Endpoint::Responder,
785 slot_count: 1,
786 payload_bytes: 32,
787 acknowledgement_count: 1,
788 },
789 ];
790 let routes = [
791 AcknowledgementRouteSpec {
792 owner: peer,
793 target: producer,
794 slot_index: 0,
795 cell_index: 0,
796 },
797 AcknowledgementRouteSpec {
798 owner: producer,
799 target: peer,
800 slot_index: 0,
801 cell_index: 0,
802 },
803 ];
804 let topology = RegionSetLayout::calculate(
805 [6; 32],
806 17,
807 &specs,
808 &routes,
809 LayoutLimits {
810 maximum_mapping_size: 1 << 20,
811 maximum_slot_count: 2,
812 maximum_acknowledgement_count: 2,
813 maximum_payload_bytes: 64,
814 },
815 )
816 .unwrap();
817 (topology, producer, peer)
818 }
819
820 #[test]
821 fn spawned_helper_uses_private_port_and_audit_pid() {
822 let executable = std::env::current_exe().unwrap();
823 let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
824 let arguments = [
825 CString::new("--exact").unwrap(),
826 CString::new("macos::bootstrap::tests::spawned_helper_entry").unwrap(),
827 CString::new("--ignored").unwrap(),
828 CString::new("--nocapture").unwrap(),
829 ];
830 let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
831 let expected_pid = helper.pid();
832 let channel = helper.authenticate().unwrap();
833 assert_eq!(channel.peer_pid(), expected_pid);
834 }
835
836 #[test]
837 #[ignore = "spawned only by the private Mach bootstrap integration test"]
838 fn spawned_helper_entry() {
839 let _channel = ChildChannel::connect_from_environment().unwrap();
840 std::thread::sleep(Duration::from_secs(30));
841 }
842
843 #[test]
844 fn spawned_helper_imports_memory_entry_and_reads_payload() {
845 let (topology, producer, peer) = topology();
846 let layout = topology.region(producer).unwrap();
847 let mut owner = super::super::QuiescentRegion::new(layout.total_size() as usize).unwrap();
848 layout.encode_into(owner.as_bytes_mut()).unwrap();
849 let expected = ValidationExpectations {
850 schema_id: [6; 32],
851 generation: 17,
852 role: producer,
853 writer: Endpoint::Initiator,
854 maximum_mapping_size: owner.len() as u64,
855 };
856 let peer_layout = topology.region(peer).unwrap();
857 let mut peer_owner =
858 super::super::QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
859 peer_layout.encode_into(peer_owner.as_bytes_mut()).unwrap();
860 let peer_expected = ValidationExpectations {
861 schema_id: [6; 32],
862 generation: 17,
863 role: peer,
864 writer: Endpoint::Responder,
865 maximum_mapping_size: peer_owner.len() as u64,
866 };
867 let executable = std::env::current_exe().unwrap();
868 let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
869 let arguments = [
870 CString::new("--exact").unwrap(),
871 CString::new("macos::bootstrap::tests::memory_entry_helper").unwrap(),
872 CString::new("--ignored").unwrap(),
873 CString::new("--nocapture").unwrap(),
874 ];
875 let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
876 let mut channel = helper.authenticate().unwrap();
877 let writer = owner
878 .transfer_local_writer(expected, topology.clone(), &mut channel)
879 .unwrap();
880 let peer_reader = peer_owner
881 .transfer_remote_writer(peer_expected, topology, &mut channel)
882 .unwrap();
883 let before_commit = Instant::now();
884 let (mut writer, peer_reader) = channel.commit_transfers(writer, peer_reader).unwrap();
885 assert!(before_commit.elapsed() >= Duration::from_millis(90));
886 writer.publish(0, 1, None, b"cross-process-mach").unwrap();
887 for _ in 0..10_000 {
888 if let Ok(payload) = peer_reader.copy_payload(0, 1) {
889 assert_eq!(payload, b"child-mach-writer");
890 channel.wait().unwrap();
891 return;
892 }
893 std::thread::sleep(Duration::from_millis(1));
894 }
895 panic!("child never published payload");
896 }
897
898 #[test]
899 #[ignore = "spawned only by the memory-entry integration test"]
900 fn memory_entry_helper() {
901 let (topology, producer, peer) = topology();
902 let layout = topology.region(producer).unwrap();
903 let page = super::super::page_size().unwrap();
904 let len = super::super::page_align(layout.total_size() as usize, page).unwrap();
905 let expected = ValidationExpectations {
906 schema_id: [6; 32],
907 generation: 17,
908 role: producer,
909 writer: Endpoint::Initiator,
910 maximum_mapping_size: len as u64,
911 };
912 let peer_layout = topology.region(peer).unwrap();
913 let peer_len = super::super::page_align(peer_layout.total_size() as usize, page).unwrap();
914 let peer_expected = ValidationExpectations {
915 schema_id: [6; 32],
916 generation: 17,
917 role: peer,
918 writer: Endpoint::Responder,
919 maximum_mapping_size: peer_len as u64,
920 };
921 let mut channel = ChildChannel::connect_from_environment().unwrap();
922 std::thread::sleep(Duration::from_millis(50));
923 let reader = channel
924 .receive_reader(len, expected, topology.clone())
925 .unwrap();
926 let peer_writer = channel
927 .receive_writer(peer_len, peer_expected, topology)
928 .unwrap();
929 let (reader, mut peer_writer) = channel.commit_imports(reader, peer_writer).unwrap();
930 for _ in 0..10_000 {
931 if let Ok(payload) = reader.copy_payload(0, 1) {
932 assert_eq!(payload, b"cross-process-mach");
933 peer_writer
934 .publish(0, 1, None, b"child-mach-writer")
935 .unwrap();
936 return;
937 }
938 std::thread::sleep(Duration::from_millis(1));
939 }
940 panic!("parent never published payload");
941 }
942}