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};
8
9type MachMsgReturn = c_int;
10type PosixSpawnAttr = *mut c_void;
11
12const MACH_PORT_NULL: MachPort = 0;
13const MACH_PORT_RIGHT_RECEIVE: c_int = 1;
14const MACH_MSG_TYPE_COPY_SEND: u8 = 19;
15const MACH_MSG_TYPE_MAKE_SEND: u8 = 20;
16const MACH_MSG_TYPE_PORT_SEND: u8 = 17;
17const MACH_MSG_PORT_DESCRIPTOR: u8 = 0;
18const MACH_MSGH_BITS_COMPLEX: u32 = 0x8000_0000;
19const MACH_SEND_MSG: u32 = 0x0000_0001;
20const MACH_RCV_MSG: u32 = 0x0000_0002;
21const MACH_SEND_TIMEOUT: u32 = 0x0000_0010;
22const MACH_RCV_TIMEOUT: u32 = 0x0000_0100;
23const MACH_RCV_TRAILER_AUDIT: u32 = 3 << 24;
24const TASK_BOOTSTRAP_PORT: c_int = 4;
25const MESSAGE_ID: c_int = 0x4e49_5043;
26const MESSAGE_MAGIC: [u8; 8] = *b"NIPCMACH";
27const ENV_NONCE: &str = "NATIVE_IPC_MACH_NONCE";
28const ENV_PARENT_PID: &str = "NATIVE_IPC_PARENT_PID";
29const TIMEOUT_MS: u32 = 10_000;
30
31unsafe extern "C" {
32    fn mach_port_allocate(task: MachPort, right: c_int, name: *mut MachPort) -> c_int;
33    fn mach_port_insert_right(
34        task: MachPort,
35        name: MachPort,
36        poly: MachPort,
37        poly_poly: c_int,
38    ) -> c_int;
39    fn mach_port_mod_refs(task: MachPort, name: MachPort, right: c_int, delta: c_int) -> c_int;
40    fn mach_msg(
41        message: *mut MachMsgHeader,
42        option: u32,
43        send_size: u32,
44        receive_limit: u32,
45        receive_name: MachPort,
46        timeout: u32,
47        notify: MachPort,
48    ) -> MachMsgReturn;
49    fn mach_msg_destroy(message: *mut MachMsgHeader);
50    fn task_get_special_port(task: MachPort, which: c_int, port: *mut MachPort) -> c_int;
51    fn posix_spawnattr_init(attributes: *mut PosixSpawnAttr) -> c_int;
52    fn posix_spawnattr_destroy(attributes: *mut PosixSpawnAttr) -> c_int;
53    fn posix_spawnattr_setspecialport_np(
54        attributes: *mut PosixSpawnAttr,
55        port: MachPort,
56        which: c_int,
57    ) -> c_int;
58    fn posix_spawn(
59        pid: *mut Pid,
60        path: *const c_char,
61        file_actions: *const c_void,
62        attributes: *const PosixSpawnAttr,
63        argv: *const *mut c_char,
64        envp: *const *mut c_char,
65    ) -> c_int;
66    fn kill(pid: Pid, signal: c_int) -> c_int;
67    fn waitpid(pid: Pid, status: *mut c_int, options: c_int) -> Pid;
68}
69
70#[link(name = "bsm")]
71unsafe extern "C" {
72    fn audit_token_to_pid(token: AuditToken) -> Pid;
73}
74
75type Pid = c_int;
76
77#[repr(C)]
78#[derive(Clone, Copy)]
79struct MachMsgHeader {
80    bits: u32,
81    size: u32,
82    remote_port: MachPort,
83    local_port: MachPort,
84    voucher_port: MachPort,
85    id: c_int,
86}
87
88#[repr(C)]
89#[derive(Clone, Copy)]
90struct MachMsgBody {
91    descriptor_count: u32,
92}
93
94#[repr(C)]
95#[derive(Clone, Copy)]
96struct MachMsgPortDescriptor {
97    name: MachPort,
98    pad1: u32,
99    pad2: u16,
100    disposition: u8,
101    descriptor_type: u8,
102}
103
104#[repr(C)]
105#[derive(Clone, Copy)]
106struct AuditToken {
107    values: [u32; 8],
108}
109
110#[repr(C)]
111#[derive(Clone, Copy)]
112struct AuditTrailer {
113    trailer_type: u32,
114    trailer_size: u32,
115    sequence: u32,
116    sender_security: [u32; 2],
117    audit: AuditToken,
118}
119
120#[repr(C)]
121struct PortMessage {
122    header: MachMsgHeader,
123    body: MachMsgBody,
124    descriptor: MachMsgPortDescriptor,
125    magic: [u8; 8],
126    nonce: [u8; 32],
127}
128
129#[repr(C)]
130struct ReceiveBuffer {
131    message: PortMessage,
132    trailer: AuditTrailer,
133}
134
135/// Mach bootstrap or authenticated port-transfer failure.
136#[derive(Debug)]
137pub enum BootstrapError {
138    /// A bounded Mach operation failed.
139    Mach {
140        /// Bounded Mach operation.
141        operation: &'static str,
142        /// Kernel return code.
143        code: c_int,
144    },
145    /// `posix_spawn` setup or launch failed.
146    Spawn(c_int),
147    /// Received message shape, nonce, or descriptor was noncanonical.
148    InvalidMessage,
149    /// Kernel audit trailer identified another process.
150    WrongPeer {
151        /// Held spawned or parent PID.
152        expected: u32,
153        /// PID from the kernel audit trailer.
154        actual: u32,
155    },
156    /// Spawn environment was missing or malformed.
157    InvalidEnvironment,
158}
159
160impl fmt::Display for BootstrapError {
161    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
162        write!(formatter, "Mach bootstrap failed: {self:?}")
163    }
164}
165impl std::error::Error for BootstrapError {}
166
167/// Received send right owned by this process.
168pub struct SendRight(MachPort);
169impl SendRight {
170    /// Raw port name for native mapping APIs inside this crate.
171    pub(super) const fn name(&self) -> MachPort {
172        self.0
173    }
174}
175impl Drop for SendRight {
176    fn drop(&mut self) {
177        deallocate_port(current_task(), self.0);
178    }
179}
180
181struct ReceiveRight(MachPort);
182impl ReceiveRight {
183    fn allocate() -> Result<Self, BootstrapError> {
184        let mut name = MACH_PORT_NULL;
185        // SAFETY: output pointer is valid for the current task.
186        let result =
187            unsafe { mach_port_allocate(current_task(), MACH_PORT_RIGHT_RECEIVE, &mut name) };
188        mach("mach_port_allocate", result)?;
189        if name == MACH_PORT_NULL {
190            return Err(BootstrapError::InvalidMessage);
191        }
192        Ok(Self(name))
193    }
194    fn make_send(&self) -> Result<(), BootstrapError> {
195        // SAFETY: this object owns the receive right from which MAKE_SEND is valid.
196        mach("mach_port_insert_right", unsafe {
197            mach_port_insert_right(
198                current_task(),
199                self.0,
200                self.0,
201                MACH_MSG_TYPE_MAKE_SEND.into(),
202            )
203        })
204    }
205}
206impl Drop for ReceiveRight {
207    fn drop(&mut self) {
208        // SAFETY: this object uniquely owns one receive-right reference.
209        let _ = unsafe { mach_port_mod_refs(current_task(), self.0, MACH_PORT_RIGHT_RECEIVE, -1) };
210    }
211}
212
213/// Parent-owned exact helper and authenticated bidirectional Mach channel.
214pub struct SpawnedHelper {
215    pid: Pid,
216    nonce: [u8; 32],
217    receive: Option<ReceiveRight>,
218}
219
220impl SpawnedHelper {
221    /// Spawns an absolute helper path with a private bootstrap send right.
222    pub fn spawn(path: &CString, arguments: &[CString]) -> Result<Self, BootstrapError> {
223        let nonce = random_nonce()?;
224        let receive = ReceiveRight::allocate()?;
225        receive.make_send()?;
226        let mut attributes: PosixSpawnAttr = std::ptr::null_mut();
227        // SAFETY: attribute output pointer is valid.
228        spawn_result(unsafe { posix_spawnattr_init(&mut attributes) })?;
229        struct AttributeGuard(PosixSpawnAttr);
230        impl Drop for AttributeGuard {
231            fn drop(&mut self) {
232                // SAFETY: initialized posix_spawn attributes are destroyed once.
233                let _ = unsafe { posix_spawnattr_destroy(&mut self.0) };
234            }
235        }
236        let mut guard = AttributeGuard(attributes);
237        // SAFETY: attributes are initialized and receive port has a live send right.
238        spawn_result(unsafe {
239            posix_spawnattr_setspecialport_np(&mut guard.0, receive.0, TASK_BOOTSTRAP_PORT)
240        })?;
241
242        let mut argv_storage = Vec::with_capacity(arguments.len() + 1);
243        argv_storage.push(path.clone());
244        argv_storage.extend(arguments.iter().cloned());
245        let mut argv: Vec<*mut c_char> = argv_storage
246            .iter_mut()
247            .map(|argument| argument.as_ptr().cast_mut())
248            .collect();
249        argv.push(std::ptr::null_mut());
250
251        let nonce_value = hex(&nonce);
252        let parent_pid = std::process::id().to_string();
253        let mut environment: Vec<CString> = std::env::vars_os()
254            .filter(|(key, _)| key != ENV_NONCE && key != ENV_PARENT_PID)
255            .filter_map(|(key, value)| {
256                CString::new(format!(
257                    "{}={}",
258                    key.to_string_lossy(),
259                    value.to_string_lossy()
260                ))
261                .ok()
262            })
263            .collect();
264        environment.push(CString::new(format!("{ENV_NONCE}={nonce_value}")).expect("hex env"));
265        environment.push(CString::new(format!("{ENV_PARENT_PID}={parent_pid}")).expect("pid env"));
266        let mut envp: Vec<*mut c_char> = environment
267            .iter_mut()
268            .map(|entry| entry.as_ptr().cast_mut())
269            .collect();
270        envp.push(std::ptr::null_mut());
271        let mut pid = 0;
272        // SAFETY: path/argv/envp and initialized attributes remain live for the call.
273        let result = unsafe {
274            posix_spawn(
275                &mut pid,
276                path.as_ptr(),
277                std::ptr::null(),
278                &guard.0,
279                argv.as_ptr(),
280                envp.as_ptr(),
281            )
282        };
283        spawn_result(result)?;
284        // Drop the parent's extra send reference; receive right remains.
285        deallocate_port(current_task(), receive.0);
286        Ok(Self {
287            pid,
288            nonce,
289            receive: Some(receive),
290        })
291    }
292
293    /// Receives the helper's control port and authenticates its audit PID.
294    pub fn authenticate(mut self) -> Result<ParentChannel, BootstrapError> {
295        let receive = self.receive.take().ok_or(BootstrapError::InvalidMessage)?;
296        let child_send = match receive_port(&receive, &self.nonce, self.pid as u32) {
297            Ok(right) => right,
298            Err(error) => {
299                terminate_and_reap(self.pid);
300                self.pid = 0;
301                return Err(error);
302            }
303        };
304        let channel = ParentChannel {
305            peer_send: child_send,
306            _receive: receive,
307            nonce: self.nonce,
308            peer_pid: self.pid as u32,
309            reaped: false,
310        };
311        self.pid = 0;
312        Ok(channel)
313    }
314
315    /// Spawned process ID held unreaped by the caller's lifecycle policy.
316    pub const fn pid(&self) -> u32 {
317        self.pid as u32
318    }
319}
320
321impl Drop for SpawnedHelper {
322    fn drop(&mut self) {
323        if self.pid > 0 {
324            terminate_and_reap(self.pid);
325        }
326    }
327}
328
329impl Drop for ParentChannel {
330    fn drop(&mut self) {
331        if !self.reaped {
332            terminate_and_reap(self.peer_pid as Pid);
333        }
334    }
335}
336
337/// Parent side of an authenticated bidirectional port-transfer channel.
338pub struct ParentChannel {
339    peer_send: SendRight,
340    _receive: ReceiveRight,
341    nonce: [u8; 32],
342    peer_pid: u32,
343    reaped: bool,
344}
345
346impl ParentChannel {
347    /// Sends one port right to the authenticated helper.
348    pub(super) fn send(&self, port: MachPort) -> Result<(), BootstrapError> {
349        send_port(self.peer_send.0, port, MACH_MSG_TYPE_COPY_SEND, &self.nonce)
350    }
351    /// Kernel-authenticated helper PID.
352    pub const fn peer_pid(&self) -> u32 {
353        self.peer_pid
354    }
355    /// Waits for the helper's authenticated READY barrier.
356    pub fn wait_ready(&self) -> Result<(), BootstrapError> {
357        drop(receive_port(&self._receive, &self.nonce, self.peer_pid)?);
358        Ok(())
359    }
360    /// Waits for normal helper exit and consumes the child cleanup ledger.
361    pub fn wait(mut self) -> Result<(), BootstrapError> {
362        let mut status = 0;
363        // SAFETY: PID is the held unreaped child and output pointer is valid.
364        let result = unsafe { waitpid(self.peer_pid as Pid, &mut status, 0) };
365        self.reaped = result == self.peer_pid as Pid;
366        if self.reaped && status == 0 {
367            Ok(())
368        } else {
369            Err(BootstrapError::Spawn(status))
370        }
371    }
372}
373
374/// Child side obtained from its injected special bootstrap port.
375pub struct ChildChannel {
376    _parent_send: SendRight,
377    receive: ReceiveRight,
378    nonce: [u8; 32],
379    parent_pid: u32,
380}
381
382impl ChildChannel {
383    /// Connects using the injected special port and authenticated environment.
384    pub fn connect_from_environment() -> Result<Self, BootstrapError> {
385        let nonce = parse_nonce(
386            &std::env::var(ENV_NONCE).map_err(|_| BootstrapError::InvalidEnvironment)?,
387        )?;
388        let parent_pid = std::env::var(ENV_PARENT_PID)
389            .map_err(|_| BootstrapError::InvalidEnvironment)?
390            .parse()
391            .map_err(|_| BootstrapError::InvalidEnvironment)?;
392        let mut parent = MACH_PORT_NULL;
393        // SAFETY: output pointer is valid for the current task.
394        mach("task_get_special_port", unsafe {
395            task_get_special_port(current_task(), TASK_BOOTSTRAP_PORT, &mut parent)
396        })?;
397        if parent == MACH_PORT_NULL {
398            return Err(BootstrapError::InvalidEnvironment);
399        }
400        let receive = ReceiveRight::allocate()?;
401        send_port(parent, receive.0, MACH_MSG_TYPE_MAKE_SEND, &nonce)?;
402        Ok(Self {
403            _parent_send: SendRight(parent),
404            receive,
405            nonce,
406            parent_pid,
407        })
408    }
409    /// Receives one port right from the authenticated parent.
410    pub(super) fn receive(&self) -> Result<SendRight, BootstrapError> {
411        receive_port(&self.receive, &self.nonce, self.parent_pid)
412    }
413    /// Signals that all imported mappings passed quiescent validation.
414    pub fn signal_ready(&self) -> Result<(), BootstrapError> {
415        let marker = ReceiveRight::allocate()?;
416        send_port(
417            self._parent_send.0,
418            marker.0,
419            MACH_MSG_TYPE_MAKE_SEND,
420            &self.nonce,
421        )
422    }
423}
424
425fn send_port(
426    remote: MachPort,
427    port: MachPort,
428    disposition: u8,
429    nonce: &[u8; 32],
430) -> Result<(), BootstrapError> {
431    let mut message = PortMessage {
432        header: MachMsgHeader {
433            bits: MACH_MSGH_BITS_COMPLEX | u32::from(MACH_MSG_TYPE_COPY_SEND),
434            size: size_of::<PortMessage>() as u32,
435            remote_port: remote,
436            local_port: MACH_PORT_NULL,
437            voucher_port: MACH_PORT_NULL,
438            id: MESSAGE_ID,
439        },
440        body: MachMsgBody {
441            descriptor_count: 1,
442        },
443        descriptor: MachMsgPortDescriptor {
444            name: port,
445            pad1: 0,
446            pad2: 0,
447            disposition,
448            descriptor_type: MACH_MSG_PORT_DESCRIPTOR,
449        },
450        magic: MESSAGE_MAGIC,
451        nonce: *nonce,
452    };
453    // SAFETY: complete initialized message buffer is live for bounded send.
454    mach("mach_msg(send)", unsafe {
455        mach_msg(
456            &mut message.header,
457            MACH_SEND_MSG | MACH_SEND_TIMEOUT,
458            size_of::<PortMessage>() as u32,
459            0,
460            MACH_PORT_NULL,
461            TIMEOUT_MS,
462            MACH_PORT_NULL,
463        )
464    })
465}
466
467fn receive_port(
468    receive: &ReceiveRight,
469    nonce: &[u8; 32],
470    expected_pid: u32,
471) -> Result<SendRight, BootstrapError> {
472    // SAFETY: zero is valid initialization for receive buffer/out descriptor.
473    let mut buffer: ReceiveBuffer = unsafe { zeroed() };
474    // SAFETY: receive buffer is sized for message plus requested audit trailer.
475    mach("mach_msg(receive)", unsafe {
476        mach_msg(
477            &mut buffer.message.header,
478            MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_TRAILER_AUDIT,
479            0,
480            size_of::<ReceiveBuffer>() as u32,
481            receive.0,
482            TIMEOUT_MS,
483            MACH_PORT_NULL,
484        )
485    })?;
486    let complex = buffer.message.header.bits & MACH_MSGH_BITS_COMPLEX != 0;
487    if buffer.message.header.size as usize != size_of::<PortMessage>()
488        || !complex
489        || buffer.message.header.id != MESSAGE_ID
490        || buffer.message.body.descriptor_count != 1
491        || buffer.message.descriptor.descriptor_type != MACH_MSG_PORT_DESCRIPTOR
492        || buffer.message.descriptor.disposition != MACH_MSG_TYPE_PORT_SEND
493        || buffer.message.magic != MESSAGE_MAGIC
494        || buffer.message.nonce != *nonce
495        || buffer.message.descriptor.name == MACH_PORT_NULL
496        || buffer.trailer.trailer_size as usize != size_of::<AuditTrailer>()
497    {
498        if complex {
499            // SAFETY: the kernel delivered a complex message into this live buffer;
500            // libSystem destroys every delivered descriptor according to its type.
501            unsafe { mach_msg_destroy(&mut buffer.message.header) };
502        }
503        return Err(BootstrapError::InvalidMessage);
504    }
505    // SAFETY: kernel supplied a complete audit trailer of the checked size.
506    let actual = unsafe { audit_token_to_pid(buffer.trailer.audit) } as u32;
507    if actual != expected_pid {
508        deallocate_port(current_task(), buffer.message.descriptor.name);
509        return Err(BootstrapError::WrongPeer {
510            expected: expected_pid,
511            actual,
512        });
513    }
514    Ok(SendRight(buffer.message.descriptor.name))
515}
516
517fn random_nonce() -> Result<[u8; 32], BootstrapError> {
518    let mut nonce = [0_u8; 32];
519    // arc4random_buf is provided by libSystem and has no failure mode.
520    unsafe extern "C" {
521        fn arc4random_buf(buffer: *mut c_void, length: usize);
522    }
523    // SAFETY: output buffer is valid for its complete length.
524    unsafe { arc4random_buf(nonce.as_mut_ptr().cast(), nonce.len()) };
525    if nonce == [0; 32] {
526        Err(BootstrapError::InvalidEnvironment)
527    } else {
528        Ok(nonce)
529    }
530}
531
532fn mach(operation: &'static str, code: c_int) -> Result<(), BootstrapError> {
533    if code == KERN_SUCCESS {
534        Ok(())
535    } else {
536        Err(BootstrapError::Mach { operation, code })
537    }
538}
539fn spawn_result(code: c_int) -> Result<(), BootstrapError> {
540    if code == 0 {
541        Ok(())
542    } else {
543        Err(BootstrapError::Spawn(code))
544    }
545}
546fn hex(bytes: &[u8]) -> String {
547    bytes.iter().map(|byte| format!("{byte:02x}")).collect()
548}
549fn parse_nonce(encoded: &str) -> Result<[u8; 32], BootstrapError> {
550    if encoded.len() != 64 {
551        return Err(BootstrapError::InvalidEnvironment);
552    }
553    let mut nonce = [0; 32];
554    for (output, pair) in nonce.iter_mut().zip(encoded.as_bytes().chunks_exact(2)) {
555        let pair = std::str::from_utf8(pair).map_err(|_| BootstrapError::InvalidEnvironment)?;
556        *output = u8::from_str_radix(pair, 16).map_err(|_| BootstrapError::InvalidEnvironment)?;
557    }
558    Ok(nonce)
559}
560
561fn terminate_and_reap(pid: Pid) {
562    if pid <= 0 {
563        return;
564    }
565    // SAFETY: SIGKILL cannot be ignored and PID is the held spawned child.
566    let _ = unsafe { kill(pid, 9) };
567    let mut status = 0;
568    // SAFETY: status pointer is valid; held child is reaped at most once here.
569    let _ = unsafe { waitpid(pid, &mut status, 0) };
570}
571
572const _: () = assert!(size_of::<MachMsgHeader>() == 24);
573const _: () = assert!(size_of::<MachMsgPortDescriptor>() == 12);
574const _: () = assert!(size_of::<AuditTrailer>() == 52);
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579    use native_ipc_core::layout::{
580        AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSetLayout, RegionSpec, RoleId,
581        ValidationExpectations,
582    };
583    use std::os::unix::ffi::OsStrExt;
584    use std::time::Duration;
585
586    fn topology() -> (RegionSetLayout, RoleId, RoleId) {
587        let producer = RoleId::new(1).unwrap();
588        let peer = RoleId::new(2).unwrap();
589        let specs = [
590            RegionSpec {
591                role: producer,
592                writer: Endpoint::Initiator,
593                slot_count: 1,
594                payload_bytes: 32,
595                acknowledgement_count: 1,
596            },
597            RegionSpec {
598                role: peer,
599                writer: Endpoint::Responder,
600                slot_count: 1,
601                payload_bytes: 32,
602                acknowledgement_count: 1,
603            },
604        ];
605        let routes = [
606            AcknowledgementRouteSpec {
607                owner: peer,
608                target: producer,
609                slot_index: 0,
610                cell_index: 0,
611            },
612            AcknowledgementRouteSpec {
613                owner: producer,
614                target: peer,
615                slot_index: 0,
616                cell_index: 0,
617            },
618        ];
619        let topology = RegionSetLayout::calculate(
620            [6; 32],
621            17,
622            &specs,
623            &routes,
624            LayoutLimits {
625                maximum_mapping_size: 1 << 20,
626                maximum_slot_count: 2,
627                maximum_acknowledgement_count: 2,
628                maximum_payload_bytes: 64,
629            },
630        )
631        .unwrap();
632        (topology, producer, peer)
633    }
634
635    #[test]
636    fn spawned_helper_uses_private_port_and_audit_pid() {
637        let executable = std::env::current_exe().unwrap();
638        let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
639        let arguments = [
640            CString::new("--exact").unwrap(),
641            CString::new("macos::bootstrap::tests::spawned_helper_entry").unwrap(),
642            CString::new("--ignored").unwrap(),
643            CString::new("--nocapture").unwrap(),
644        ];
645        let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
646        let expected_pid = helper.pid();
647        let channel = helper.authenticate().unwrap();
648        assert_eq!(channel.peer_pid(), expected_pid);
649    }
650
651    #[test]
652    #[ignore = "spawned only by the private Mach bootstrap integration test"]
653    fn spawned_helper_entry() {
654        let _channel = ChildChannel::connect_from_environment().unwrap();
655        std::thread::sleep(Duration::from_secs(30));
656    }
657
658    #[test]
659    fn spawned_helper_imports_memory_entry_and_reads_payload() {
660        let (topology, producer, peer) = topology();
661        let layout = topology.region(producer).unwrap();
662        let mut owner = super::super::QuiescentRegion::new(layout.total_size() as usize).unwrap();
663        layout.encode_into(owner.as_bytes_mut()).unwrap();
664        let expected = ValidationExpectations {
665            schema_id: [6; 32],
666            generation: 17,
667            role: producer,
668            writer: Endpoint::Initiator,
669            maximum_mapping_size: owner.len() as u64,
670        };
671        let peer_layout = topology.region(peer).unwrap();
672        let mut peer_owner =
673            super::super::QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
674        peer_layout.encode_into(peer_owner.as_bytes_mut()).unwrap();
675        let peer_expected = ValidationExpectations {
676            schema_id: [6; 32],
677            generation: 17,
678            role: peer,
679            writer: Endpoint::Responder,
680            maximum_mapping_size: peer_owner.len() as u64,
681        };
682        let executable = std::env::current_exe().unwrap();
683        let path = CString::new(executable.as_os_str().as_bytes()).unwrap();
684        let arguments = [
685            CString::new("--exact").unwrap(),
686            CString::new("macos::bootstrap::tests::memory_entry_helper").unwrap(),
687            CString::new("--ignored").unwrap(),
688            CString::new("--nocapture").unwrap(),
689        ];
690        let helper = SpawnedHelper::spawn(&path, &arguments).unwrap();
691        let channel = helper.authenticate().unwrap();
692        let mut writer = owner
693            .transfer_local_writer(expected, topology.clone(), &channel)
694            .unwrap();
695        let peer_reader = peer_owner
696            .transfer_remote_writer(peer_expected, topology, &channel)
697            .unwrap();
698        channel.wait_ready().unwrap();
699        writer.publish(0, 1, None, b"cross-process-mach").unwrap();
700        for _ in 0..10_000 {
701            if let Ok(payload) = peer_reader.copy_payload(0, 1) {
702                assert_eq!(payload, b"child-mach-writer");
703                channel.wait().unwrap();
704                return;
705            }
706            std::thread::sleep(Duration::from_millis(1));
707        }
708        panic!("child never published payload");
709    }
710
711    #[test]
712    #[ignore = "spawned only by the memory-entry integration test"]
713    fn memory_entry_helper() {
714        let (topology, producer, peer) = topology();
715        let layout = topology.region(producer).unwrap();
716        let page = super::super::page_size().unwrap();
717        let len = super::super::page_align(layout.total_size() as usize, page).unwrap();
718        let expected = ValidationExpectations {
719            schema_id: [6; 32],
720            generation: 17,
721            role: producer,
722            writer: Endpoint::Initiator,
723            maximum_mapping_size: len as u64,
724        };
725        let peer_layout = topology.region(peer).unwrap();
726        let peer_len = super::super::page_align(peer_layout.total_size() as usize, page).unwrap();
727        let peer_expected = ValidationExpectations {
728            schema_id: [6; 32],
729            generation: 17,
730            role: peer,
731            writer: Endpoint::Responder,
732            maximum_mapping_size: peer_len as u64,
733        };
734        let channel = ChildChannel::connect_from_environment().unwrap();
735        let reader = channel
736            .receive_reader(len, expected, topology.clone())
737            .unwrap();
738        let mut peer_writer = channel
739            .receive_writer(peer_len, peer_expected, topology)
740            .unwrap();
741        channel.signal_ready().unwrap();
742        for _ in 0..10_000 {
743            if let Ok(payload) = reader.copy_payload(0, 1) {
744                assert_eq!(payload, b"cross-process-mach");
745                peer_writer
746                    .publish(0, 1, None, b"child-mach-writer")
747                    .unwrap();
748                return;
749            }
750            std::thread::sleep(Duration::from_millis(1));
751        }
752        panic!("parent never published payload");
753    }
754}