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::{
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/// Mach bootstrap or authenticated port-transfer failure.
145#[derive(Debug)]
146pub enum BootstrapError {
147    /// A bounded Mach operation failed.
148    Mach {
149        /// Bounded Mach operation.
150        operation: &'static str,
151        /// Kernel return code.
152        code: c_int,
153    },
154    /// `posix_spawn` setup or launch failed.
155    Spawn(c_int),
156    /// Received message shape, nonce, or descriptor was noncanonical.
157    InvalidMessage,
158    /// Kernel audit trailer identified another process.
159    WrongPeer {
160        /// Held spawned or parent PID.
161        expected: u32,
162        /// PID from the kernel audit trailer.
163        actual: u32,
164    },
165    /// Spawn environment was missing or malformed.
166    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
176/// Received send right owned by this process.
177pub struct SendRight(MachPort);
178impl SendRight {
179    /// Raw port name for native mapping APIs inside this crate.
180    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        // SAFETY: output pointer is valid for the current task.
195        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        // SAFETY: this object owns the receive right from which MAKE_SEND is valid.
205        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        // SAFETY: this object uniquely owns one receive-right reference.
218        let _ = unsafe { mach_port_mod_refs(current_task(), self.0, MACH_PORT_RIGHT_RECEIVE, -1) };
219    }
220}
221
222/// Parent-owned exact helper and authenticated bidirectional Mach channel.
223pub struct SpawnedHelper {
224    pid: Pid,
225    nonce: [u8; 32],
226    receive: Option<ReceiveRight>,
227}
228
229impl SpawnedHelper {
230    /// Spawns an absolute helper path with a private bootstrap send right.
231    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        // SAFETY: attribute output pointer is valid.
237        spawn_result(unsafe { posix_spawnattr_init(&mut attributes) })?;
238        struct AttributeGuard(PosixSpawnAttr);
239        impl Drop for AttributeGuard {
240            fn drop(&mut self) {
241                // SAFETY: initialized posix_spawn attributes are destroyed once.
242                let _ = unsafe { posix_spawnattr_destroy(&mut self.0) };
243            }
244        }
245        let mut guard = AttributeGuard(attributes);
246        // SAFETY: attributes are initialized and receive port has a live send right.
247        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        // SAFETY: path/argv/envp and initialized attributes remain live for the call.
282        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        // Drop the parent's extra send reference on every outcome before the
293        // launch result is inspected; the receive right remains owned.
294        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    /// Receives the helper's control port and authenticates its audit PID.
304    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    /// Spawned process ID held unreaped by the caller's lifecycle policy.
335    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
356/// Parent side of an authenticated bidirectional port-transfer channel.
357pub 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    /// Sends one port right to the authenticated helper.
371    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    /// Kernel-authenticated helper PID.
398    pub const fn peer_pid(&self) -> u32 {
399        self.peer_pid
400    }
401    /// Waits for authenticated READY and acknowledges it with COMMIT.
402    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    /// Waits for normal helper exit and consumes the child cleanup ledger.
434    pub fn wait(mut self) -> Result<(), BootstrapError> {
435        let mut status = 0;
436        // SAFETY: PID is the held unreaped child and output pointer is valid.
437        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    /// Provenance stamp binding pending values to the open transaction.
484    pub(super) const fn pending_provenance(&self) -> TransferProvenance {
485        TransferProvenance::new(self.channel_id, self.next_transfer_id)
486    }
487}
488
489/// Child side obtained from its injected special bootstrap port.
490pub 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    /// Connects using the injected special port and authenticated environment.
503    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        // SAFETY: output pointer is valid for the current task.
513        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    /// Receives one port right from the authenticated parent.
539    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    /// Signals validation and waits for the creator's COMMIT acknowledgement.
559    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    /// Provenance stamp binding pending values to the open transaction.
619    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    // SAFETY: complete initialized message buffer is live for bounded send.
655    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    // SAFETY: zero is valid initialization for receive buffer/out descriptor.
675    let mut buffer: ReceiveBuffer = unsafe { zeroed() };
676    // SAFETY: receive buffer is sized for message plus requested audit trailer.
677    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            // SAFETY: the kernel delivered a complex message into this live buffer;
703            // libSystem destroys every delivered descriptor according to its type.
704            unsafe { mach_msg_destroy(&mut buffer.message.header) };
705        }
706        return Err(BootstrapError::InvalidMessage);
707    }
708    // SAFETY: kernel supplied a complete audit trailer of the checked size.
709    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    // arc4random_buf is provided by libSystem and has no failure mode.
723    unsafe extern "C" {
724        fn arc4random_buf(buffer: *mut c_void, length: usize);
725    }
726    // SAFETY: output buffer is valid for its complete length.
727    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    // SAFETY: SIGKILL cannot be ignored and PID is the held spawned child.
769    let _ = unsafe { kill(pid, 9) };
770    let mut status = 0;
771    // SAFETY: status pointer is valid; held child is reaped at most once here.
772    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        // Session two must reject session one's pending values before READY/COMMIT.
977        assert!(matches!(
978            second.commit_transfers(first_writer, first_reader),
979            Err(super::super::MacBindingError::ForeignPending)
980        ));
981
982        // The mismatched transaction is poisoned: even the channel's own exact
983        // pending values can no longer commit.
984        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}