Skip to main content

native_ipc_platform/
macos.rs

1//! Mach memory-entry backed shared regions.
2//!
3//! The ABI declarations and constants are transcribed from the macOS SDK's
4//! Mach VM headers. Runtime typestates intentionally expose no byte slices.
5
6use std::ffi::c_int;
7use std::fmt;
8use std::marker::PhantomData;
9use std::ptr::NonNull;
10
11use native_ipc_core::layout::{
12    LayoutError, RegionSetLayout, ValidatedRegionLayout, ValidationExpectations,
13};
14use native_ipc_core::mapping::{
15    BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
16};
17
18pub mod bootstrap;
19
20type KernReturn = c_int;
21type MachPort = u32;
22type MachVmAddress = u64;
23type MachVmSize = u64;
24type MemoryObjectOffset = u64;
25type MemoryObjectSize = u64;
26type VmInherit = u32;
27type VmProt = c_int;
28
29const KERN_SUCCESS: KernReturn = 0;
30const MACH_PORT_NULL: MachPort = 0;
31const VM_FLAGS_ANYWHERE: c_int = 1;
32const VM_PROT_READ: VmProt = 1;
33const VM_PROT_WRITE: VmProt = 2;
34const VM_PROT_EXECUTE: VmProt = 4;
35const MAP_MEM_VM_SHARE: VmProt = 0x0040_0000;
36const VM_INHERIT_NONE: VmInherit = 2;
37
38unsafe extern "C" {
39    static mach_task_self_: MachPort;
40
41    fn getpagesize() -> c_int;
42    fn mach_vm_allocate(
43        target: MachPort,
44        address: *mut MachVmAddress,
45        size: MachVmSize,
46        flags: c_int,
47    ) -> KernReturn;
48    fn mach_vm_deallocate(target: MachPort, address: MachVmAddress, size: MachVmSize)
49    -> KernReturn;
50    fn mach_vm_protect(
51        target_task: MachPort,
52        address: MachVmAddress,
53        size: MachVmSize,
54        set_maximum: c_int,
55        new_protection: VmProt,
56    ) -> KernReturn;
57    fn mach_make_memory_entry_64(
58        target_task: MachPort,
59        size: *mut MemoryObjectSize,
60        offset: MemoryObjectOffset,
61        permission: VmProt,
62        object_handle: *mut MachPort,
63        parent_entry: MachPort,
64    ) -> KernReturn;
65    fn mach_vm_map(
66        target_task: MachPort,
67        address: *mut MachVmAddress,
68        size: MachVmSize,
69        mask: MachVmAddress,
70        flags: c_int,
71        object: MachPort,
72        offset: MemoryObjectOffset,
73        copy: c_int,
74        current_protection: VmProt,
75        maximum_protection: VmProt,
76        inheritance: VmInherit,
77    ) -> KernReturn;
78    fn mach_port_deallocate(task: MachPort, name: MachPort) -> KernReturn;
79}
80
81/// Failure to create or restrict a Mach shared-memory capability.
82#[allow(missing_docs)]
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub enum MachError {
85    /// Shared regions cannot be empty.
86    ZeroSize,
87    /// Requested size cannot be page-aligned.
88    SizeOverflow { requested: usize },
89    /// Transition size differs from the quiescent region.
90    InvalidViewSize { requested: usize, region: usize },
91    /// Kernel reported an invalid page size.
92    InvalidPageSize(c_int),
93    /// Successful allocation returned an unusable address.
94    InvalidAddress(MachVmAddress),
95    /// Successful memory-entry creation returned a null capability.
96    NullMemoryEntry,
97    /// Kernel changed an already aligned entry size.
98    UnexpectedEntrySize { expected: usize, actual: u64 },
99    /// Mach kernel call failed.
100    Kernel {
101        /// Operation name from this bounded implementation.
102        operation: &'static str,
103        /// Kernel status code.
104        code: KernReturn,
105    },
106}
107
108/// Failure while validating and binding a Mach mapping to the common core.
109#[derive(Debug)]
110pub enum MacBindingError {
111    /// Quiescent bytes failed hostile layout validation.
112    Layout(LayoutError),
113    /// Mach typestate transition failed.
114    Mach(MachError),
115    /// Audited mapping-to-record binding failed.
116    Binding(BindingError),
117    /// Authenticated bootstrap or Mach port transfer failed.
118    Bootstrap(bootstrap::BootstrapError),
119}
120
121impl fmt::Display for MacBindingError {
122    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(formatter, "Mach/core binding failed: {self:?}")
124    }
125}
126
127impl std::error::Error for MacBindingError {}
128impl From<LayoutError> for MacBindingError {
129    fn from(value: LayoutError) -> Self {
130        Self::Layout(value)
131    }
132}
133impl From<MachError> for MacBindingError {
134    fn from(value: MachError) -> Self {
135        Self::Mach(value)
136    }
137}
138impl From<BindingError> for MacBindingError {
139    fn from(value: BindingError) -> Self {
140        Self::Binding(value)
141    }
142}
143impl From<bootstrap::BootstrapError> for MacBindingError {
144    fn from(value: bootstrap::BootstrapError) -> Self {
145        Self::Bootstrap(value)
146    }
147}
148
149impl fmt::Display for MachError {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(formatter, "Mach shared memory operation failed: {self:?}")
152    }
153}
154
155impl std::error::Error for MachError {}
156
157/// Quiescent, pre-transfer owner of a zero-initialized Mach mapping.
158///
159/// This is the only typestate that exposes ordinary byte slices. Consuming it
160/// chooses the one writer direction and permanently removes those accessors.
161#[derive(Debug)]
162pub struct QuiescentRegion {
163    mapping: Mapping,
164    logical_len: usize,
165}
166
167impl QuiescentRegion {
168    /// Allocates a non-executable, zero-initialized Mach VM region.
169    pub fn new(len: usize) -> Result<Self, MachError> {
170        let page_size = page_size()?;
171        let mapped_len = page_align(len, page_size)?;
172        let task = current_task();
173        let mut mapping = Mapping::allocate(task, mapped_len)?;
174        // SAFETY: newly allocated mapping has no aliases or capabilities.
175        unsafe { mapping.bytes_mut(mapped_len) }.fill(0);
176        mapping.protect(VM_PROT_READ | VM_PROT_WRITE, false)?;
177        mapping.protect(VM_PROT_READ | VM_PROT_WRITE, true)?;
178        Ok(Self {
179            mapping,
180            logical_len: len,
181        })
182    }
183
184    /// Returns the negotiated page-rounded capability length.
185    pub const fn len(&self) -> usize {
186        self.mapping.mapped_len
187    }
188
189    /// Returns the requested logical layout length within the capability.
190    pub const fn logical_len(&self) -> usize {
191        self.logical_len
192    }
193
194    /// Returns whether the logical region is empty (always false for a valid value).
195    pub const fn is_empty(&self) -> bool {
196        false
197    }
198
199    /// Borrows quiescent initialization bytes.
200    pub fn as_bytes(&self) -> &[u8] {
201        // SAFETY: quiescent state has no peer capability or second mapping.
202        unsafe { self.mapping.bytes(self.mapping.mapped_len) }
203    }
204
205    /// Mutably borrows quiescent initialization bytes.
206    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
207        // SAFETY: quiescent state plus `&mut self` provides exclusive access.
208        unsafe { self.mapping.bytes_mut(self.mapping.mapped_len) }
209    }
210
211    /// Selects this process as sole writer and creates one read-only peer entry.
212    pub fn into_local_writer(self, expected_len: usize) -> Result<LocalWriterRegion, MachError> {
213        self.validate_transition_size(expected_len)?;
214        let peer_entry = MemoryEntry::<ReadOnlyCapability>::new(self.mapping.task, &self.mapping)?;
215        Ok(LocalWriterRegion {
216            mapping: self.mapping,
217            peer_entry,
218            len: expected_len,
219        })
220    }
221
222    /// Selects the peer as sole writer and permanently downgrades this mapping.
223    pub fn into_remote_writer(
224        mut self,
225        expected_len: usize,
226    ) -> Result<RemoteWriterRegion, MachError> {
227        self.validate_transition_size(expected_len)?;
228        let peer_entry = MemoryEntry::<ReadWriteCapability>::new(self.mapping.task, &self.mapping)?;
229        self.mapping.protect(VM_PROT_READ, false)?;
230        self.mapping.protect(VM_PROT_READ, true)?;
231        Ok(RemoteWriterRegion {
232            mapping: self.mapping,
233            peer_entry,
234            len: expected_len,
235        })
236    }
237
238    fn validate_transition_size(&self, expected_len: usize) -> Result<(), MachError> {
239        if expected_len == self.mapping.mapped_len && expected_len != 0 {
240            Ok(())
241        } else {
242            Err(MachError::InvalidViewSize {
243                requested: expected_len,
244                region: self.mapping.mapped_len,
245            })
246        }
247    }
248
249    /// Validates the complete padded capability, then consumes it as the sole writer.
250    pub fn into_bound_local_writer(
251        self,
252        expected: ValidationExpectations,
253        topology: RegionSetLayout,
254    ) -> Result<WriterRegion<MacWriterMapping>, MacBindingError> {
255        // SAFETY: quiescent typestate excludes peer aliases and validation sees
256        // the exact page-rounded capability range that will be transferred.
257        let layout =
258            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
259        let capability_len = self.len();
260        let region = self.into_local_writer(capability_len)?;
261        Ok(WriterRegion::new(
262            MacWriterMapping { region },
263            layout,
264            topology,
265        )?)
266    }
267
268    /// Validates the complete padded capability, then downgrades it to read-only.
269    pub fn into_bound_remote_writer(
270        self,
271        expected: ValidationExpectations,
272        topology: RegionSetLayout,
273    ) -> Result<ReaderRegion<MacReaderMapping>, MacBindingError> {
274        // SAFETY: same quiescent exact-capability proof as the local-writer path.
275        let layout =
276            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
277        let capability_len = self.len();
278        let region = self.into_remote_writer(capability_len)?;
279        Ok(ReaderRegion::new(
280            MacReaderMapping { region },
281            layout,
282            topology,
283        )?)
284    }
285
286    /// Validates, transfers a read-only entry, and commits the local writer.
287    pub fn transfer_local_writer(
288        self,
289        expected: ValidationExpectations,
290        topology: RegionSetLayout,
291        channel: &bootstrap::ParentChannel,
292    ) -> Result<WriterRegion<TransferredWriterMapping>, MacBindingError> {
293        // SAFETY: quiescent state covers the exact transferred capability.
294        let layout =
295            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
296        let capability_len = self.len();
297        let region = self.into_local_writer(capability_len)?;
298        channel.send(region.peer_entry.name)?;
299        let LocalWriterRegion {
300            mapping,
301            peer_entry,
302            len: _,
303        } = region;
304        drop(peer_entry);
305        Ok(WriterRegion::new(
306            TransferredWriterMapping { mapping },
307            layout,
308            topology,
309        )?)
310    }
311
312    /// Validates, transfers the sole writer entry, and commits local read-only access.
313    pub fn transfer_remote_writer(
314        self,
315        expected: ValidationExpectations,
316        topology: RegionSetLayout,
317        channel: &bootstrap::ParentChannel,
318    ) -> Result<ReaderRegion<TransferredReaderMapping>, MacBindingError> {
319        // SAFETY: quiescent state covers the exact transferred capability.
320        let layout =
321            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
322        let capability_len = self.len();
323        let region = self.into_remote_writer(capability_len)?;
324        channel.send(region.peer_entry.name)?;
325        let RemoteWriterRegion {
326            mapping,
327            peer_entry,
328            len: _,
329        } = region;
330        drop(peer_entry);
331        Ok(ReaderRegion::new(
332            TransferredReaderMapping { mapping },
333            layout,
334            topology,
335        )?)
336    }
337}
338
339/// Parent-side writer mapping after its read-only entry was transferred.
340pub struct TransferredWriterMapping {
341    mapping: Mapping,
342}
343// SAFETY: the only transferred right is kernel-clamped read-only; local mapping is unique RW.
344unsafe impl SoleWriterMapping for TransferredWriterMapping {
345    fn base(&self) -> NonNull<u8> {
346        self.mapping.address
347    }
348    fn len(&self) -> usize {
349        self.mapping.mapped_len
350    }
351}
352
353/// Parent-side read-only mapping after the sole writer entry was transferred.
354pub struct TransferredReaderMapping {
355    mapping: Mapping,
356}
357// SAFETY: local current/maximum protection was permanently downgraded before transfer.
358unsafe impl ReadOnlyMapping for TransferredReaderMapping {
359    fn base(&self) -> NonNull<u8> {
360        self.mapping.address
361    }
362    fn len(&self) -> usize {
363        self.mapping.mapped_len
364    }
365}
366
367/// Imported child-side read-only mapping.
368pub struct ImportedReaderMapping {
369    mapping: Mapping,
370}
371// SAFETY: mapping is created with current/maximum read-only protection.
372unsafe impl ReadOnlyMapping for ImportedReaderMapping {
373    fn base(&self) -> NonNull<u8> {
374        self.mapping.address
375    }
376    fn len(&self) -> usize {
377        self.mapping.mapped_len
378    }
379}
380
381/// Imported child-side sole-writer mapping.
382pub struct ImportedWriterMapping {
383    mapping: Mapping,
384}
385// SAFETY: authenticated parent creates exactly one RW entry for this role.
386unsafe impl SoleWriterMapping for ImportedWriterMapping {
387    fn base(&self) -> NonNull<u8> {
388        self.mapping.address
389    }
390    fn len(&self) -> usize {
391        self.mapping.mapped_len
392    }
393}
394
395impl bootstrap::ChildChannel {
396    /// Receives and binds a read-only memory entry while the parent is quiescent.
397    pub fn receive_reader(
398        &self,
399        len: usize,
400        expected: ValidationExpectations,
401        topology: RegionSetLayout,
402    ) -> Result<ReaderRegion<ImportedReaderMapping>, MacBindingError> {
403        let right = self.receive()?;
404        let mapping = Mapping::map_port(current_task(), len, right.name(), VM_PROT_READ)?;
405        // SAFETY: authenticated transfer remains quiescent until this call returns.
406        let bytes = unsafe { mapping.bytes(len) };
407        let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
408        drop(right);
409        Ok(ReaderRegion::new(
410            ImportedReaderMapping { mapping },
411            layout,
412            topology,
413        )?)
414    }
415
416    /// Receives and binds the sole writable memory entry while quiescent.
417    pub fn receive_writer(
418        &self,
419        len: usize,
420        expected: ValidationExpectations,
421        topology: RegionSetLayout,
422    ) -> Result<WriterRegion<ImportedWriterMapping>, MacBindingError> {
423        let right = self.receive()?;
424        let mapping = Mapping::map_port(
425            current_task(),
426            len,
427            right.name(),
428            VM_PROT_READ | VM_PROT_WRITE,
429        )?;
430        // SAFETY: authenticated transfer remains quiescent until this call returns.
431        let bytes = unsafe { mapping.bytes(len) };
432        let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
433        drop(right);
434        Ok(WriterRegion::new(
435            ImportedWriterMapping { mapping },
436            layout,
437            topology,
438        )?)
439    }
440}
441
442/// Platform-minted sole-writer witness for the audited core bridge.
443pub struct MacWriterMapping {
444    region: LocalWriterRegion,
445}
446
447// SAFETY: `LocalWriterRegion` is consuming, owns the mapping lifetime, and its
448// peer memory entry is kernel-clamped read-only.
449unsafe impl SoleWriterMapping for MacWriterMapping {
450    fn base(&self) -> NonNull<u8> {
451        self.region.mapping.address
452    }
453    fn len(&self) -> usize {
454        self.region.mapping.mapped_len
455    }
456}
457
458/// Platform-minted local read-only witness for the audited core bridge.
459pub struct MacReaderMapping {
460    region: RemoteWriterRegion,
461}
462
463// SAFETY: `RemoteWriterRegion` permanently sets current and maximum local
464// protection to read-only before construction and owns the mapping lifetime.
465unsafe impl ReadOnlyMapping for MacReaderMapping {
466    fn base(&self) -> NonNull<u8> {
467        self.region.mapping.address
468    }
469    fn len(&self) -> usize {
470        self.region.mapping.mapped_len
471    }
472}
473
474/// Runtime region written locally and represented to the peer by a read-only entry.
475///
476/// The runtime state exposes identity only, not ordinary shared-memory slices.
477#[derive(Debug)]
478#[allow(dead_code)]
479pub struct LocalWriterRegion {
480    mapping: Mapping,
481    peer_entry: MemoryEntry<ReadOnlyCapability>,
482    len: usize,
483}
484
485impl LocalWriterRegion {
486    /// Returns the logical region length without granting memory access.
487    pub const fn len(&self) -> usize {
488        self.len
489    }
490
491    /// Returns whether the logical region is empty.
492    pub const fn is_empty(&self) -> bool {
493        self.len == 0
494    }
495}
496
497/// Runtime region written remotely with a permanently read-only local mapping.
498///
499/// The runtime state exposes identity only, not ordinary shared-memory slices.
500#[derive(Debug)]
501#[allow(dead_code)]
502pub struct RemoteWriterRegion {
503    mapping: Mapping,
504    peer_entry: MemoryEntry<ReadWriteCapability>,
505    len: usize,
506}
507
508impl RemoteWriterRegion {
509    /// Returns the logical region length without granting memory access.
510    pub const fn len(&self) -> usize {
511        self.len
512    }
513
514    /// Returns whether the logical region is empty.
515    pub const fn is_empty(&self) -> bool {
516        self.len == 0
517    }
518}
519
520#[derive(Debug)]
521struct Mapping {
522    task: MachPort,
523    address: NonNull<u8>,
524    mapped_len: usize,
525}
526
527impl Mapping {
528    fn allocate(task: MachPort, mapped_len: usize) -> Result<Self, MachError> {
529        let mut address = 0;
530        // SAFETY: output pointer is valid and size was checked/page-aligned.
531        let result = unsafe {
532            mach_vm_allocate(
533                task,
534                &mut address,
535                mapped_len as MachVmSize,
536                VM_FLAGS_ANYWHERE,
537            )
538        };
539        check_kernel("mach_vm_allocate", result)?;
540        Self::from_allocated(task, address, mapped_len)
541    }
542
543    #[cfg(test)]
544    fn map_entry<Access: CapabilityAccess>(
545        task: MachPort,
546        mapped_len: usize,
547        entry: &MemoryEntry<Access>,
548    ) -> Result<Self, MachError> {
549        Self::map_port(task, mapped_len, entry.name, Access::PROTECTION)
550    }
551
552    fn map_port(
553        task: MachPort,
554        mapped_len: usize,
555        port: MachPort,
556        protection: VmProt,
557    ) -> Result<Self, MachError> {
558        debug_assert_eq!(protection & VM_PROT_EXECUTE, 0);
559        let mut address = 0;
560        // SAFETY: entry is live; current/maximum protections exclude execute.
561        let result = unsafe {
562            mach_vm_map(
563                task,
564                &mut address,
565                mapped_len as MachVmSize,
566                0,
567                VM_FLAGS_ANYWHERE,
568                port,
569                0,
570                0,
571                protection,
572                protection,
573                VM_INHERIT_NONE,
574            )
575        };
576        check_kernel("mach_vm_map", result)?;
577        Self::from_allocated(task, address, mapped_len)
578    }
579
580    fn protect(&mut self, protection: VmProt, set_maximum: bool) -> Result<(), MachError> {
581        debug_assert_eq!(protection & VM_PROT_EXECUTE, 0);
582        // SAFETY: mapping is live and no reference exists during transition.
583        let result = unsafe {
584            mach_vm_protect(
585                self.task,
586                self.address(),
587                self.mapped_len as MachVmSize,
588                c_int::from(set_maximum),
589                protection,
590            )
591        };
592        check_kernel("mach_vm_protect", result)
593    }
594
595    fn from_allocated(
596        task: MachPort,
597        address: MachVmAddress,
598        mapped_len: usize,
599    ) -> Result<Self, MachError> {
600        let address_usize = match usize::try_from(address) {
601            Ok(value) => value,
602            Err(_) => {
603                deallocate_mapping(task, address, mapped_len);
604                return Err(MachError::InvalidAddress(address));
605            }
606        };
607        let Some(address) = NonNull::new(address_usize as *mut u8) else {
608            deallocate_mapping(task, 0, mapped_len);
609            return Err(MachError::InvalidAddress(0));
610        };
611        Ok(Self {
612            task,
613            address,
614            mapped_len,
615        })
616    }
617
618    fn address(&self) -> MachVmAddress {
619        self.address.as_ptr() as usize as MachVmAddress
620    }
621
622    unsafe fn bytes(&self, len: usize) -> &[u8] {
623        assert!(len <= self.mapped_len && len <= isize::MAX as usize);
624        // SAFETY: caller proves this address retains provenance from the live
625        // Mach allocation, the range is initialized/readable for the returned
626        // borrow, and neither process mutates it for that borrow's lifetime.
627        unsafe { std::slice::from_raw_parts(self.address.as_ptr(), len) }
628    }
629
630    unsafe fn bytes_mut(&mut self, len: usize) -> &mut [u8] {
631        assert!(len <= self.mapped_len && len <= isize::MAX as usize);
632        // SAFETY: caller proves this address retains provenance from the live
633        // Mach allocation and that the initialized/writable range has no local
634        // or remote aliases for the returned exclusive borrow's lifetime.
635        unsafe { std::slice::from_raw_parts_mut(self.address.as_ptr(), len) }
636    }
637}
638
639impl Drop for Mapping {
640    fn drop(&mut self) {
641        deallocate_mapping(self.task, self.address(), self.mapped_len);
642    }
643}
644
645#[derive(Debug)]
646struct ReadOnlyCapability;
647#[derive(Debug)]
648struct ReadWriteCapability;
649
650trait CapabilityAccess {
651    const PROTECTION: VmProt;
652}
653
654impl CapabilityAccess for ReadOnlyCapability {
655    const PROTECTION: VmProt = VM_PROT_READ;
656}
657impl CapabilityAccess for ReadWriteCapability {
658    const PROTECTION: VmProt = VM_PROT_READ | VM_PROT_WRITE;
659}
660
661#[derive(Debug)]
662struct MemoryEntry<Access> {
663    task: MachPort,
664    name: MachPort,
665    _access: PhantomData<fn() -> Access>,
666}
667
668impl<Access: CapabilityAccess> MemoryEntry<Access> {
669    fn new(task: MachPort, mapping: &Mapping) -> Result<Self, MachError> {
670        let mut entry_size = mapping.mapped_len as MemoryObjectSize;
671        let mut name = MACH_PORT_NULL;
672        let permission = Access::PROTECTION | MAP_MEM_VM_SHARE;
673        debug_assert_eq!(permission & VM_PROT_EXECUTE, 0);
674        // SAFETY: out-pointers are valid; source is a live current-task mapping.
675        let result = unsafe {
676            mach_make_memory_entry_64(
677                task,
678                &mut entry_size,
679                mapping.address(),
680                permission,
681                &mut name,
682                MACH_PORT_NULL,
683            )
684        };
685        if result != KERN_SUCCESS {
686            if name != MACH_PORT_NULL {
687                deallocate_port(task, name);
688            }
689            return Err(MachError::Kernel {
690                operation: "mach_make_memory_entry_64",
691                code: result,
692            });
693        }
694        if name == MACH_PORT_NULL {
695            return Err(MachError::NullMemoryEntry);
696        }
697        let entry = Self {
698            task,
699            name,
700            _access: PhantomData,
701        };
702        if entry_size != mapping.mapped_len as MemoryObjectSize {
703            return Err(MachError::UnexpectedEntrySize {
704                expected: mapping.mapped_len,
705                actual: entry_size,
706            });
707        }
708        Ok(entry)
709    }
710}
711
712impl<Access> Drop for MemoryEntry<Access> {
713    fn drop(&mut self) {
714        deallocate_port(self.task, self.name);
715    }
716}
717
718fn current_task() -> MachPort {
719    // SAFETY: libSystem initializes this process-global task port name.
720    unsafe { mach_task_self_ }
721}
722
723fn page_size() -> Result<usize, MachError> {
724    // SAFETY: `getpagesize` has no caller obligations.
725    let size = unsafe { getpagesize() };
726    let Ok(converted) = usize::try_from(size) else {
727        return Err(MachError::InvalidPageSize(size));
728    };
729    if converted == 0 || !converted.is_power_of_two() {
730        return Err(MachError::InvalidPageSize(size));
731    }
732    Ok(converted)
733}
734
735fn page_align(size: usize, page_size: usize) -> Result<usize, MachError> {
736    if size == 0 {
737        return Err(MachError::ZeroSize);
738    }
739    let aligned = size
740        .checked_add(page_size - 1)
741        .map(|value| value & !(page_size - 1))
742        .ok_or(MachError::SizeOverflow { requested: size })?;
743    if aligned > isize::MAX as usize {
744        return Err(MachError::SizeOverflow { requested: size });
745    }
746    Ok(aligned)
747}
748
749fn check_kernel(operation: &'static str, code: KernReturn) -> Result<(), MachError> {
750    if code == KERN_SUCCESS {
751        Ok(())
752    } else {
753        Err(MachError::Kernel { operation, code })
754    }
755}
756
757fn deallocate_mapping(task: MachPort, address: MachVmAddress, mapped_len: usize) {
758    // SAFETY: callers pass a mapping returned by Mach for this task.
759    let _ = unsafe { mach_vm_deallocate(task, address, mapped_len as MachVmSize) };
760}
761
762fn deallocate_port(task: MachPort, name: MachPort) {
763    // SAFETY: callers pass a live memory-entry send right in this task.
764    let _ = unsafe { mach_port_deallocate(task, name) };
765}
766
767#[cfg(test)]
768mod tests {
769    use super::*;
770    use native_ipc_core::layout::{
771        AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSetLayout, RegionSpec, RoleId,
772    };
773    use std::mem::size_of;
774
775    struct TestWriterWitness<'a>(&'a mut Mapping);
776    struct TestReaderWitness<'a>(&'a Mapping);
777
778    // SAFETY: test witnesses borrow live Mach mappings for their full bound
779    // lifetime; the writer mapping is unique and peer entries are read-only.
780    unsafe impl SoleWriterMapping for TestWriterWitness<'_> {
781        fn base(&self) -> NonNull<u8> {
782            self.0.address
783        }
784        fn len(&self) -> usize {
785            self.0.mapped_len
786        }
787    }
788
789    // SAFETY: test reader mappings are created from read-only memory entries
790    // and remain borrowed for their full bound lifetime.
791    unsafe impl ReadOnlyMapping for TestReaderWitness<'_> {
792        fn base(&self) -> NonNull<u8> {
793            self.0.address
794        }
795        fn len(&self) -> usize {
796            self.0.mapped_len
797        }
798    }
799
800    #[test]
801    fn read_only_capability_rejects_writable_mapping() {
802        let owner = QuiescentRegion::new(37).unwrap();
803        let capability_len = owner.len();
804        let runtime = owner.into_local_writer(capability_len).unwrap();
805        let mut address = 0;
806        let protection = VM_PROT_READ | VM_PROT_WRITE;
807        // SAFETY: deliberately bypasses typed API to probe kernel enforcement.
808        let result = unsafe {
809            mach_vm_map(
810                runtime.mapping.task,
811                &mut address,
812                runtime.mapping.mapped_len as MachVmSize,
813                0,
814                VM_FLAGS_ANYWHERE,
815                runtime.peer_entry.name,
816                0,
817                0,
818                protection,
819                protection,
820                VM_INHERIT_NONE,
821            )
822        };
823        if result == KERN_SUCCESS {
824            deallocate_mapping(runtime.mapping.task, address, runtime.mapping.mapped_len);
825        }
826        assert_ne!(result, KERN_SUCCESS);
827    }
828
829    #[test]
830    fn executable_protection_upgrade_is_rejected() {
831        let owner = QuiescentRegion::new(37).unwrap();
832        let capability_len = owner.len();
833        let runtime = owner.into_local_writer(capability_len).unwrap();
834        // SAFETY: deliberately requests execute to probe the clamped maximum.
835        let result = unsafe {
836            mach_vm_protect(
837                runtime.mapping.task,
838                runtime.mapping.address(),
839                runtime.mapping.mapped_len as MachVmSize,
840                0,
841                VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
842            )
843        };
844        assert_ne!(result, KERN_SUCCESS);
845    }
846
847    #[test]
848    fn remote_writer_downgrades_local_mapping_before_escape() {
849        let mut owner = QuiescentRegion::new(19).unwrap();
850        owner.as_bytes_mut()[0] = 7;
851        let capability_len = owner.len();
852        let mut runtime = owner.into_remote_writer(capability_len).unwrap();
853        assert!(
854            runtime
855                .mapping
856                .protect(VM_PROT_READ | VM_PROT_WRITE, false)
857                .is_err()
858        );
859        let mut peer = Mapping::map_entry(
860            runtime.mapping.task,
861            runtime.mapping.mapped_len,
862            &runtime.peer_entry,
863        )
864        .unwrap();
865        // SAFETY: peer test mapping is the sole writer while quiescent.
866        let peer_bytes = unsafe { peer.bytes_mut(19) };
867        peer_bytes[3..8].copy_from_slice(b"world");
868        drop(peer);
869        // SAFETY: peer mapping is gone; immutable test snapshot is quiescent.
870        assert_eq!(&unsafe { runtime.mapping.bytes(19) }[3..8], b"world");
871    }
872
873    #[test]
874    fn local_writer_peer_observes_quiescent_initialization() {
875        let mut owner = QuiescentRegion::new(37).unwrap();
876        owner.as_bytes_mut()[..5].copy_from_slice(b"hello");
877        let capability_len = owner.len();
878        let runtime = owner.into_local_writer(capability_len).unwrap();
879        let peer = Mapping::map_entry(
880            runtime.mapping.task,
881            runtime.mapping.mapped_len,
882            &runtime.peer_entry,
883        )
884        .unwrap();
885        // SAFETY: local writer is quiescent during immutable test snapshot.
886        assert_eq!(&unsafe { peer.bytes(37) }[..5], b"hello");
887    }
888
889    #[test]
890    fn rejects_bad_sizes_and_matches_sdk_scalars() {
891        assert_eq!(QuiescentRegion::new(0).unwrap_err(), MachError::ZeroSize);
892        assert_eq!(
893            page_align(usize::MAX, 4096).unwrap_err(),
894            MachError::SizeOverflow {
895                requested: usize::MAX
896            }
897        );
898        assert_eq!(size_of::<MachPort>(), 4);
899        assert_eq!(size_of::<MachVmAddress>(), 8);
900        assert_eq!(ReadOnlyCapability::PROTECTION, VM_PROT_READ);
901        assert_eq!(
902            ReadWriteCapability::PROTECTION,
903            VM_PROT_READ | VM_PROT_WRITE
904        );
905    }
906
907    #[test]
908    fn page_capability_padding_is_explicit_validated_and_bound() {
909        let producer = RoleId::new(1).unwrap();
910        let peer = RoleId::new(2).unwrap();
911        let specs = [
912            RegionSpec {
913                role: producer,
914                writer: Endpoint::Initiator,
915                slot_count: 1,
916                payload_bytes: 16,
917                acknowledgement_count: 1,
918            },
919            RegionSpec {
920                role: peer,
921                writer: Endpoint::Responder,
922                slot_count: 1,
923                payload_bytes: 16,
924                acknowledgement_count: 1,
925            },
926        ];
927        let routes = [
928            AcknowledgementRouteSpec {
929                owner: peer,
930                target: producer,
931                slot_index: 0,
932                cell_index: 0,
933            },
934            AcknowledgementRouteSpec {
935                owner: producer,
936                target: peer,
937                slot_index: 0,
938                cell_index: 0,
939            },
940        ];
941        let set = RegionSetLayout::calculate(
942            [3; 32],
943            7,
944            &specs,
945            &routes,
946            LayoutLimits {
947                maximum_mapping_size: 1 << 20,
948                maximum_slot_count: 2,
949                maximum_acknowledgement_count: 2,
950                maximum_payload_bytes: 64,
951            },
952        )
953        .unwrap();
954        let layout = set.region(producer).unwrap();
955        let mut owner = QuiescentRegion::new(layout.total_size() as usize).unwrap();
956        assert!(owner.len() >= owner.logical_len());
957        assert!(owner.len().is_multiple_of(page_size().unwrap()));
958        layout.encode_into(owner.as_bytes_mut()).unwrap();
959        let expected = ValidationExpectations {
960            schema_id: [3; 32],
961            generation: 7,
962            role: producer,
963            writer: Endpoint::Initiator,
964            maximum_mapping_size: owner.len() as u64,
965        };
966        let mut bound = owner
967            .into_bound_local_writer(expected, set.clone())
968            .unwrap();
969        bound
970            .slot(0)
971            .unwrap()
972            .prepare_publish(1, None)
973            .unwrap()
974            .publish(4)
975            .unwrap();
976
977        let mut hostile = QuiescentRegion::new(layout.total_size() as usize).unwrap();
978        layout.encode_into(hostile.as_bytes_mut()).unwrap();
979        let last = hostile.len() - 1;
980        hostile.as_bytes_mut()[last] = 1;
981        let expected = ValidationExpectations {
982            schema_id: [3; 32],
983            generation: 7,
984            role: producer,
985            writer: Endpoint::Initiator,
986            maximum_mapping_size: hostile.len() as u64,
987        };
988        assert!(matches!(
989            hostile.into_bound_local_writer(expected, set),
990            Err(MacBindingError::Layout(
991                LayoutError::CapabilityPaddingNotZero
992            ))
993        ));
994    }
995
996    #[test]
997    fn mach_mapping_completes_core_publish_observe_and_ack_path() {
998        let producer = RoleId::new(1).unwrap();
999        let acknowledger = RoleId::new(2).unwrap();
1000        let specs = [
1001            RegionSpec {
1002                role: producer,
1003                writer: Endpoint::Initiator,
1004                slot_count: 1,
1005                payload_bytes: 16,
1006                acknowledgement_count: 1,
1007            },
1008            RegionSpec {
1009                role: acknowledger,
1010                writer: Endpoint::Responder,
1011                slot_count: 1,
1012                payload_bytes: 16,
1013                acknowledgement_count: 1,
1014            },
1015        ];
1016        let routes = [
1017            AcknowledgementRouteSpec {
1018                owner: acknowledger,
1019                target: producer,
1020                slot_index: 0,
1021                cell_index: 0,
1022            },
1023            AcknowledgementRouteSpec {
1024                owner: producer,
1025                target: acknowledger,
1026                slot_index: 0,
1027                cell_index: 0,
1028            },
1029        ];
1030        let topology = RegionSetLayout::calculate(
1031            [9; 32],
1032            11,
1033            &specs,
1034            &routes,
1035            LayoutLimits {
1036                maximum_mapping_size: 1 << 20,
1037                maximum_slot_count: 2,
1038                maximum_acknowledgement_count: 2,
1039                maximum_payload_bytes: 64,
1040            },
1041        )
1042        .unwrap();
1043
1044        let producer_layout = topology.region(producer).unwrap();
1045        let mut producer_owner =
1046            QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1047        producer_layout
1048            .encode_into(producer_owner.as_bytes_mut())
1049            .unwrap();
1050        let producer_expected = ValidationExpectations {
1051            schema_id: [9; 32],
1052            generation: 11,
1053            role: producer,
1054            writer: Endpoint::Initiator,
1055            maximum_mapping_size: producer_owner.len() as u64,
1056        };
1057        let producer_validated = unsafe {
1058            ValidatedRegionLayout::validate(producer_owner.as_bytes(), producer_expected, &topology)
1059        }
1060        .unwrap();
1061        let producer_len = producer_owner.len();
1062        let mut producer_runtime = producer_owner.into_local_writer(producer_len).unwrap();
1063        let producer_peer = Mapping::map_entry(
1064            producer_runtime.mapping.task,
1065            producer_runtime.mapping.mapped_len,
1066            &producer_runtime.peer_entry,
1067        )
1068        .unwrap();
1069
1070        let ack_layout = topology.region(acknowledger).unwrap();
1071        let mut ack_owner = QuiescentRegion::new(ack_layout.total_size() as usize).unwrap();
1072        ack_layout.encode_into(ack_owner.as_bytes_mut()).unwrap();
1073        let ack_expected = ValidationExpectations {
1074            schema_id: [9; 32],
1075            generation: 11,
1076            role: acknowledger,
1077            writer: Endpoint::Responder,
1078            maximum_mapping_size: ack_owner.len() as u64,
1079        };
1080        let ack_validated = unsafe {
1081            ValidatedRegionLayout::validate(ack_owner.as_bytes(), ack_expected, &topology)
1082        }
1083        .unwrap();
1084        let ack_len = ack_owner.len();
1085        let mut ack_runtime = ack_owner.into_local_writer(ack_len).unwrap();
1086        let ack_peer = Mapping::map_entry(
1087            ack_runtime.mapping.task,
1088            ack_runtime.mapping.mapped_len,
1089            &ack_runtime.peer_entry,
1090        )
1091        .unwrap();
1092
1093        {
1094            let mut writer = WriterRegion::new(
1095                TestWriterWitness(&mut producer_runtime.mapping),
1096                producer_validated.clone(),
1097                topology.clone(),
1098            )
1099            .unwrap();
1100            writer.publish(0, 1, None, b"mach").unwrap();
1101        }
1102        let reader = ReaderRegion::new(
1103            TestReaderWitness(&producer_peer),
1104            producer_validated,
1105            topology.clone(),
1106        )
1107        .unwrap();
1108        let observation = reader.slot(0).unwrap().observe(1).unwrap();
1109        reader.slot(0).unwrap().recheck(observation).unwrap();
1110        assert_eq!(reader.copy_payload(0, 1).unwrap(), b"mach");
1111
1112        {
1113            let mut writer = WriterRegion::new(
1114                TestWriterWitness(&mut ack_runtime.mapping),
1115                ack_validated.clone(),
1116                topology.clone(),
1117            )
1118            .unwrap();
1119            writer
1120                .acknowledgement(producer, 0)
1121                .unwrap()
1122                .acknowledge(observation)
1123                .unwrap();
1124        }
1125        let reader =
1126            ReaderRegion::new(TestReaderWitness(&ack_peer), ack_validated, topology).unwrap();
1127        let acknowledged = reader.acknowledgement(producer, 0).unwrap().observe();
1128        assert_eq!(acknowledged.sequence(), 1);
1129        assert_eq!(acknowledged.slot_index(), 0);
1130        assert_eq!(acknowledged.cell_index(), 0);
1131    }
1132}