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