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