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