Skip to main content

native_ipc/
binding.rs

1//! Safe audited binding from committed active mappings to core capabilities.
2//!
3//! This module is the safe, fully audited alternative to the feature-gated
4//! `raw-pointer` escape. It converts a committed [`ActiveReader`] or
5//! [`ActiveWriter`] into the matching `native-ipc-core` capability owner
6//! ([`ReaderRegion`]/[`WriterRegion`]) by consuming the active mapping into a
7//! witness the core boundary trusts. Running the audited core protocol over a
8//! session-transferred region needs no consumer unsafe code and no
9//! `raw-pointer` feature on this path.
10//!
11//! # Witness soundness
12//!
13//! An active mapping is uniquely owned and cannot be cloned. Its local native
14//! view is released only by the owner's own `Drop`; session poison or close
15//! gates the safe accessors but never unmaps the view. Moving the consumed
16//! active value inside a witness therefore keeps the whole `base..base+len`
17//! extent mapped and initialized — and, for the read side, OS-enforced
18//! read-only — for the entire witness lifetime. `len` is the mapping's
19//! validated logical extent ([`ActiveReader::len`]/[`ActiveWriter::len`]); the
20//! page-rounded tail beyond it is deliberately excluded, matching the range the
21//! [`ValidatedRegionLayout`] was validated over.
22//!
23//! After the peer session ends the peer is gone and the bytes are frozen or
24//! stale: read witnesses still observe only hostile, memory-safe bytes, and
25//! write witnesses simply publish to nobody. Liveness re-checking is
26//! deliberately not part of the witness contract; a consumer that needs it
27//! keeps the owning session handle and quiesces before dropping the witness.
28//!
29//! [`ActiveReader`]: crate::active::ActiveReader
30//! [`ActiveReader::len`]: crate::active::ActiveReader::len
31//! [`ActiveWriter`]: crate::active::ActiveWriter
32//! [`ActiveWriter::len`]: crate::active::ActiveWriter::len
33//! [`ReaderRegion`]: crate::core::mapping::ReaderRegion
34//! [`WriterRegion`]: crate::core::mapping::WriterRegion
35//! [`ValidatedRegionLayout`]: crate::core::layout::ValidatedRegionLayout
36
37use crate::active::{ActiveReader, ActiveWriter};
38use crate::core::layout::{RegionSetLayout, ValidatedRegionLayout};
39use crate::core::mapping::{
40    BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
41};
42use core::fmt;
43use core::ptr::NonNull;
44
45/// Read-only witness that owns its consumed active mapping.
46///
47/// A region bound over this witness is a unique capability; neither the witness
48/// nor the region it backs can be duplicated:
49///
50/// ```compile_fail
51/// use native_ipc::binding::BoundReadMapping;
52/// use native_ipc::core::mapping::ReaderRegion;
53/// fn duplicate(
54///     region: ReaderRegion<BoundReadMapping>,
55/// ) -> (ReaderRegion<BoundReadMapping>, ReaderRegion<BoundReadMapping>) {
56///     let copy = region.clone();
57///     (region, copy)
58/// }
59/// ```
60pub struct BoundReadMapping {
61    reader: ActiveReader,
62    base: NonNull<u8>,
63}
64
65// SAFETY: the witness owns an `ActiveReader`, which is itself `Send + Sync`, and
66// the cached `base` is a copy of that mapping's own base that can never outlive
67// the owned reader. Moving or sharing the witness across threads only moves or
68// shares the reader it already permits, so the markers grant no authority the
69// inner mapping did not already have.
70unsafe impl Send for BoundReadMapping {}
71unsafe impl Sync for BoundReadMapping {}
72
73// SAFETY: the owned `ActiveReader` uniquely owns exactly one page-aligned,
74// OS-enforced read-only native view and releases it only in its own `Drop`;
75// session poison or close gates the safe accessors without unmapping, so the
76// view stays mapped and initialized for as long as this witness lives. `base`
77// is that mapping's own base carrying its allocation provenance, and `len`
78// reports its validated logical extent — precisely the bytes the caller's
79// `ValidatedRegionLayout` was validated over. Peer mutation may race, but the
80// core boundary only ever performs volatile loads through `base` and never
81// forms a shared reference, so exposing this witness cannot violate memory
82// safety.
83unsafe impl ReadOnlyMapping for BoundReadMapping {
84    fn base(&self) -> NonNull<u8> {
85        self.base
86    }
87
88    fn len(&self) -> usize {
89        self.reader.len()
90    }
91}
92
93/// Sole-writer witness that owns its consumed active mapping.
94///
95/// Like the [`ActiveWriter`] it owns, a bound writer may move between threads
96/// but cannot be shared between them:
97///
98/// ```compile_fail
99/// use native_ipc::binding::BoundWriteMapping;
100/// use native_ipc::core::mapping::WriterRegion;
101/// fn assert_sync<T: Sync>() {}
102/// assert_sync::<WriterRegion<BoundWriteMapping>>();
103/// ```
104pub struct BoundWriteMapping {
105    writer: ActiveWriter,
106    base: NonNull<u8>,
107}
108
109// SAFETY: the witness owns an `ActiveWriter`, which is `Send` but deliberately
110// not `Sync`; moving it between threads transfers the single writable view
111// without ever aliasing it, and the cached `base` cannot outlive that owned
112// writer. Matching the inner mapping, the witness is `Send` only, so no shared
113// cross-thread writer alias becomes reachable.
114unsafe impl Send for BoundWriteMapping {}
115
116// SAFETY: the owned `ActiveWriter` is, by construction, the region's only
117// writable native view: it is non-cloneable, holds sole store authority, and
118// releases its page-aligned view only in its own `Drop`. Session poison or
119// close gates the safe accessors without unmapping, so the view stays mapped
120// and writable for the witness lifetime. `base` is that mapping's own base with
121// allocation provenance and `len` is its validated logical extent, exactly the
122// range the `ValidatedRegionLayout` was validated over. While a `WriterRegion`
123// owns this witness, safe code cannot recover a second writer for the region.
124unsafe impl SoleWriterMapping for BoundWriteMapping {
125    fn base(&self) -> NonNull<u8> {
126        self.base
127    }
128
129    fn len(&self) -> usize {
130        self.writer.len()
131    }
132}
133
134impl BoundReadMapping {
135    /// Releases the witness and returns the owned active mapping unchanged.
136    pub fn into_active(self) -> ActiveReader {
137        self.reader
138    }
139}
140
141impl BoundWriteMapping {
142    /// Releases the witness and returns the owned active mapping unchanged.
143    pub fn into_active(self) -> ActiveWriter {
144        self.writer
145    }
146}
147
148/// A rejected bind that returns the consumed active mapping to its caller.
149///
150/// The bind boundary consumes the active mapping by value; on rejection this
151/// carrier hands the exact same value back so the caller recovers it instead of
152/// losing the committed mapping.
153pub struct BindRejected<T> {
154    /// Reason the validated layout could not bind to this mapping.
155    pub error: BindingError,
156    value: T,
157}
158
159impl<T> BindRejected<T> {
160    /// Recovers the consumed active mapping unchanged.
161    pub fn into_inner(self) -> T {
162        self.value
163    }
164}
165
166// The recovered active mapping is deliberately opaque, so this does not require
167// `T: Debug`; only the binding error is reported.
168impl<T> fmt::Debug for BindRejected<T> {
169    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170        formatter
171            .debug_struct("BindRejected")
172            .field("error", &self.error)
173            .finish_non_exhaustive()
174    }
175}
176
177impl ActiveReader {
178    /// Consumes this committed mapping into an audited core read capability.
179    ///
180    /// On rejection the returned [`BindRejected`] carries this exact reader back
181    /// through [`BindRejected::into_inner`].
182    pub fn bind(
183        self,
184        layout: ValidatedRegionLayout,
185        topology: RegionSetLayout,
186    ) -> Result<ReaderRegion<BoundReadMapping>, Box<BindRejected<ActiveReader>>> {
187        let base = self.payload_base();
188        let witness = BoundReadMapping { reader: self, base };
189        ReaderRegion::new(witness, layout, topology).map_err(|(witness, error)| {
190            Box::new(BindRejected {
191                error,
192                value: witness.into_active(),
193            })
194        })
195    }
196}
197
198impl ActiveWriter {
199    /// Consumes this committed mapping into an audited core write capability.
200    ///
201    /// On rejection the returned [`BindRejected`] carries this exact writer back
202    /// through [`BindRejected::into_inner`].
203    pub fn bind(
204        mut self,
205        layout: ValidatedRegionLayout,
206        topology: RegionSetLayout,
207    ) -> Result<WriterRegion<BoundWriteMapping>, Box<BindRejected<ActiveWriter>>> {
208        let base = self.payload_base_mut();
209        let witness = BoundWriteMapping { writer: self, base };
210        WriterRegion::new(witness, layout, topology).map_err(|(witness, error)| {
211            Box::new(BindRejected {
212                error,
213                value: witness.into_active(),
214            })
215        })
216    }
217}
218
219#[cfg(test)]
220#[path = "binding_test.rs"]
221mod tests;