Skip to main content

native_ipc_platform/macos/
bootstrap.rs

1//! Private Mach bootstrap channel with audit-token process authentication.
2
3use 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/// Mach bootstrap or authenticated port-transfer failure.
142#[derive(Debug)]
143pub enum BootstrapError {
144    /// A bounded Mach operation failed.
145    Mach {
146        /// Bounded Mach operation.
147        operation: &'static str,
148        /// Kernel return code.
149        code: c_int,
150    },
151    /// `posix_spawn` setup or launch failed.
152    Spawn(c_int),
153    /// Received message shape, nonce, or descriptor was noncanonical.
154    InvalidMessage,
155    /// Kernel audit trailer identified another process.
156    WrongPeer {
157        /// Held spawned or parent PID.
158        expected: u32,
159        /// PID from the kernel audit trailer.
160        actual: u32,
161    },
162    /// Spawn environment was missing or malformed.
163    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
173/// Received send right owned by this process.
174pub struct SendRight(MachPort);
175impl SendRight {
176    /// Raw port name for native mapping APIs inside this crate.
177    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        // SAFETY: output pointer is valid for the current task.
192        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        // SAFETY: this object owns the receive right from which MAKE_SEND is valid.
202        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        // SAFETY: this object uniquely owns one receive-right reference.
215        let _ = unsafe { mach_port_mod_refs(current_task(), self.0, MACH_PORT_RIGHT_RECEIVE, -1) };
216    }
217}
218
219/// Parent-owned exact helper and authenticated bidirectional Mach channel.
220pub struct SpawnedHelper {
221    pid: Pid,
222    nonce: [u8; 32],
223    receive: Option<ReceiveRight>,
224}
225
226impl SpawnedHelper {
227    /// Spawns an absolute helper path with a private bootstrap send right.
228    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        // SAFETY: attribute output pointer is valid.
234        spawn_result(unsafe { posix_spawnattr_init(&mut attributes) })?;
235        struct AttributeGuard(PosixSpawnAttr);
236        impl Drop for AttributeGuard {
237            fn drop(&mut self) {
238                // SAFETY: initialized posix_spawn attributes are destroyed once.
239                let _ = unsafe { posix_spawnattr_destroy(&mut self.0) };
240            }
241        }
242        let mut guard = AttributeGuard(attributes);
243        // SAFETY: attributes are initialized and receive port has a live send right.
244        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        // SAFETY: path/argv/envp and initialized attributes remain live for the call.
279        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        // Drop the parent's extra send reference; receive right remains.
291        deallocate_port(current_task(), receive.0);
292        Ok(Self {
293            pid,
294            nonce,
295            receive: Some(receive),
296        })
297    }
298
299    /// Receives the helper's control port and authenticates its audit PID.
300    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    /// Spawned process ID held unreaped by the caller's lifecycle policy.
330    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
351/// Parent side of an authenticated bidirectional port-transfer channel.
352pub 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    /// Sends one port right to the authenticated helper.
365    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    /// Kernel-authenticated helper PID.
392    pub const fn peer_pid(&self) -> u32 {
393        self.peer_pid
394    }
395    /// Waits for authenticated READY and acknowledges it with COMMIT.
396    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    /// Waits for normal helper exit and consumes the child cleanup ledger.
428    pub fn wait(mut self) -> Result<(), BootstrapError> {
429        let mut status = 0;
430        // SAFETY: PID is the held unreaped child and output pointer is valid.
431        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
478/// Child side obtained from its injected special bootstrap port.
479pub 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    /// Connects using the injected special port and authenticated environment.
491    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        // SAFETY: output pointer is valid for the current task.
501        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    /// Receives one port right from the authenticated parent.
526    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    /// Signals validation and waits for the creator's COMMIT acknowledgement.
546    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    // SAFETY: complete initialized message buffer is live for bounded send.
637    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    // SAFETY: zero is valid initialization for receive buffer/out descriptor.
657    let mut buffer: ReceiveBuffer = unsafe { zeroed() };
658    // SAFETY: receive buffer is sized for message plus requested audit trailer.
659    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            // SAFETY: the kernel delivered a complex message into this live buffer;
685            // libSystem destroys every delivered descriptor according to its type.
686            unsafe { mach_msg_destroy(&mut buffer.message.header) };
687        }
688        return Err(BootstrapError::InvalidMessage);
689    }
690    // SAFETY: kernel supplied a complete audit trailer of the checked size.
691    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    // arc4random_buf is provided by libSystem and has no failure mode.
705    unsafe extern "C" {
706        fn arc4random_buf(buffer: *mut c_void, length: usize);
707    }
708    // SAFETY: output buffer is valid for its complete length.
709    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    // SAFETY: SIGKILL cannot be ignored and PID is the held spawned child.
751    let _ = unsafe { kill(pid, 9) };
752    let mut status = 0;
753    // SAFETY: status pointer is valid; held child is reaped at most once here.
754    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}