Skip to main content

native_ipc_platform/
linux.rs

1//! Linux sealed-memfd mappings and authenticated descriptor transfer.
2
3use std::ffi::OsStr;
4use std::fmt;
5use std::io::{self, Write};
6use std::mem::{size_of, zeroed};
7use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
8use std::os::unix::fs::PermissionsExt;
9use std::os::unix::net::UnixListener;
10use std::os::unix::net::UnixStream;
11use std::path::PathBuf;
12use std::process::{Child, Command};
13use std::ptr::NonNull;
14use std::time::{Duration, Instant};
15
16use native_ipc_core::layout::{RegionSetLayout, ValidatedRegionLayout, ValidationExpectations};
17use native_ipc_core::mapping::{
18    BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
19};
20
21use crate::BackendStatus;
22use crate::protocol::{CONTROL_FRAME_LEN, ManifestEntry, PeerAccess, TransferManifest};
23
24const REQUIRED_SEALS: libc::c_int =
25    libc::F_SEAL_GROW | libc::F_SEAL_SHRINK | libc::F_SEAL_FUTURE_WRITE | libc::F_SEAL_SEAL;
26const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCFD\0\0";
27const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
28const COMMIT_MAGIC: [u8; 8] = *b"NIPCCMT1";
29const TMPFS_MAGIC: libc::c_long = 0x0102_1994;
30const ENV_SOCKET: &str = "NATIVE_IPC_LINUX_SOCKET";
31const ENV_NONCE: &str = "NATIVE_IPC_SESSION_NONCE";
32const ENV_PARENT_PID: &str = "NATIVE_IPC_PARENT_PID";
33
34/// Linux mapping, descriptor-transfer, or peer-authentication failure.
35#[derive(Debug)]
36pub enum LinuxError {
37    /// A native syscall failed with the captured errno.
38    Os {
39        /// Bounded syscall name.
40        operation: &'static str,
41        /// Captured platform errno.
42        code: i32,
43    },
44    /// Requested mapping size is zero or cannot be page-rounded.
45    InvalidSize(usize),
46    /// Received peer credentials differ from the expected live process.
47    WrongPeer,
48    /// Descriptor transfer frame was truncated, malformed, or stale.
49    InvalidFrame,
50    /// Received descriptor count or ancillary type was not exactly one SCM_RIGHTS fd.
51    InvalidAncillaryData,
52    /// Received capability lacks the exact anonymous sealed-shmem policy.
53    InvalidCapability,
54    /// Private bootstrap path, environment, or child process setup failed.
55    Bootstrap,
56    /// Quiescent core layout validation failed.
57    Layout(native_ipc_core::layout::LayoutError),
58    /// Audited core binding failed.
59    Binding(BindingError),
60}
61
62/// Owned exact child process, authenticated control channel, and cleanup ledger.
63pub struct ChildSession {
64    child: Child,
65    channel: AuthenticatedChannel,
66    bootstrap_dir: PathBuf,
67}
68
69/// Construction guard returning bootstrap resources to baseline on any failure.
70///
71/// It owns the private bootstrap directory from creation and the helper child
72/// from spawn until a fully constructed [`ChildSession`] assumes both.
73struct BootstrapGuard {
74    bootstrap_dir: Option<PathBuf>,
75    child: Option<Child>,
76}
77
78impl BootstrapGuard {
79    fn dir(&self) -> &std::path::Path {
80        self.bootstrap_dir
81            .as_deref()
82            .expect("directory is owned until disarm")
83    }
84
85    fn disarm(mut self) -> (PathBuf, Child) {
86        (
87            self.bootstrap_dir
88                .take()
89                .expect("directory is owned until disarm"),
90            self.child.take().expect("child is armed before disarm"),
91        )
92    }
93}
94
95impl Drop for BootstrapGuard {
96    fn drop(&mut self) {
97        if let Some(child) = self.child.as_mut() {
98            let _ = child.kill();
99            let _ = child.wait();
100        }
101        if let Some(dir) = self.bootstrap_dir.take() {
102            let _ = std::fs::remove_dir_all(dir);
103        }
104    }
105}
106
107impl ChildSession {
108    /// Spawns an exact helper executable and authenticates its post-exec connection.
109    pub fn spawn(program: &OsStr, arguments: &[&OsStr]) -> Result<Self, LinuxError> {
110        let mut nonce = [0_u8; 32];
111        // SAFETY: output buffer is valid and flags zero request blocking system RNG.
112        if unsafe { libc::getrandom(nonce.as_mut_ptr().cast(), nonce.len(), 0) }
113            != nonce.len() as isize
114        {
115            return Err(last_os("getrandom"));
116        }
117        let suffix = hex(&nonce[..16]);
118        let bootstrap_dir = std::env::temp_dir().join(format!("native-ipc-{suffix}"));
119        std::fs::create_dir(&bootstrap_dir).map_err(|_| LinuxError::Bootstrap)?;
120        // Every failure past this point must return the filesystem and process
121        // ledger to baseline: the guard removes the directory and, once the
122        // helper is spawned, kills and reaps it.
123        let mut guard = BootstrapGuard {
124            bootstrap_dir: Some(bootstrap_dir),
125            child: None,
126        };
127        std::fs::set_permissions(guard.dir(), std::fs::Permissions::from_mode(0o700))
128            .map_err(|_| LinuxError::Bootstrap)?;
129        let socket_path = guard.dir().join("control.sock");
130        let listener = UnixListener::bind(&socket_path).map_err(|_| LinuxError::Bootstrap)?;
131        std::fs::set_permissions(&socket_path, std::fs::Permissions::from_mode(0o600))
132            .map_err(|_| LinuxError::Bootstrap)?;
133        listener
134            .set_nonblocking(true)
135            .map_err(|_| LinuxError::Bootstrap)?;
136
137        let mut command = Command::new(program);
138        command
139            .args(arguments)
140            .env(ENV_SOCKET, &socket_path)
141            .env(ENV_NONCE, hex(&nonce))
142            .env(ENV_PARENT_PID, std::process::id().to_string());
143        let child = command.spawn().map_err(|_| LinuxError::Bootstrap)?;
144        let expected = PeerCredentials {
145            pid: child.id(),
146            // SAFETY: scalar identity syscalls have no preconditions.
147            uid: unsafe { libc::geteuid() },
148            // SAFETY: scalar identity syscalls have no preconditions.
149            gid: unsafe { libc::getegid() },
150        };
151        guard.child = Some(child);
152        let deadline = Instant::now() + Duration::from_secs(10);
153        let stream = loop {
154            match listener.accept() {
155                Ok((stream, _)) => {
156                    if peer_credentials(&stream)? == expected {
157                        break stream;
158                    }
159                }
160                Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
161                    if Instant::now() >= deadline {
162                        return Err(LinuxError::Bootstrap);
163                    }
164                    std::thread::sleep(Duration::from_millis(5));
165                }
166                Err(_) => return Err(LinuxError::Bootstrap),
167            }
168        };
169        let channel = AuthenticatedChannel::new(stream, expected, nonce)?;
170        let (bootstrap_dir, child) = guard.disarm();
171        Ok(Self {
172            child,
173            channel,
174            bootstrap_dir,
175        })
176    }
177
178    /// Authenticated capability-transfer channel.
179    pub const fn channel(&self) -> &AuthenticatedChannel {
180        &self.channel
181    }
182
183    /// Exclusive transaction access to the authenticated capability channel.
184    pub const fn channel_mut(&mut self) -> &mut AuthenticatedChannel {
185        &mut self.channel
186    }
187
188    /// Child process identifier held live and unreaped by this session.
189    pub fn child_id(&self) -> u32 {
190        self.child.id()
191    }
192
193    /// Requests termination and reaps the owned child.
194    pub fn terminate(mut self) -> Result<(), LinuxError> {
195        self.child.kill().map_err(|_| LinuxError::Bootstrap)?;
196        self.child.wait().map_err(|_| LinuxError::Bootstrap)?;
197        Ok(())
198    }
199}
200
201impl Drop for ChildSession {
202    fn drop(&mut self) {
203        let _ = self.child.kill();
204        let _ = self.child.wait();
205        let _ = std::fs::remove_dir_all(&self.bootstrap_dir);
206    }
207}
208
209/// Connects the spawned helper using inherited freshness and expected-parent data.
210pub fn connect_spawned_helper() -> Result<AuthenticatedChannel, LinuxError> {
211    let path = std::env::var_os(ENV_SOCKET).ok_or(LinuxError::Bootstrap)?;
212    let nonce = parse_nonce(&std::env::var(ENV_NONCE).map_err(|_| LinuxError::Bootstrap)?)?;
213    let parent_pid = std::env::var(ENV_PARENT_PID)
214        .map_err(|_| LinuxError::Bootstrap)?
215        .parse()
216        .map_err(|_| LinuxError::Bootstrap)?;
217    let stream = UnixStream::connect(path).map_err(|_| LinuxError::Bootstrap)?;
218    let expected = PeerCredentials {
219        pid: parent_pid,
220        // SAFETY: scalar identity syscalls have no preconditions.
221        uid: unsafe { libc::geteuid() },
222        // SAFETY: scalar identity syscalls have no preconditions.
223        gid: unsafe { libc::getegid() },
224    };
225    AuthenticatedChannel::new_with_identity(stream, expected, nonce, parent_pid, std::process::id())
226}
227
228impl fmt::Display for LinuxError {
229    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
230        write!(formatter, "Linux transport failed: {self:?}")
231    }
232}
233
234impl std::error::Error for LinuxError {}
235impl From<native_ipc_core::layout::LayoutError> for LinuxError {
236    fn from(value: native_ipc_core::layout::LayoutError) -> Self {
237        Self::Layout(value)
238    }
239}
240impl From<BindingError> for LinuxError {
241    fn from(value: BindingError) -> Self {
242        Self::Binding(value)
243    }
244}
245
246/// Kernel-authenticated Unix peer identity.
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub struct PeerCredentials {
249    /// Process ID captured by the kernel at connection time.
250    pub pid: u32,
251    /// Effective user identity.
252    pub uid: u32,
253    /// Effective group identity.
254    pub gid: u32,
255}
256
257/// Authenticated private control channel with a pollable peer-lifecycle handle.
258pub struct AuthenticatedChannel {
259    stream: UnixStream,
260    nonce: [u8; 32],
261    peer: PeerCredentials,
262    pidfd: OwnedFd,
263    parent_pid: u32,
264    child_pid: u32,
265    next_transfer_id: u64,
266    poisoned: bool,
267}
268
269impl AuthenticatedChannel {
270    /// Authenticates an already-private post-exec Unix connection.
271    pub fn new(
272        stream: UnixStream,
273        expected: PeerCredentials,
274        nonce: [u8; 32],
275    ) -> Result<Self, LinuxError> {
276        Self::new_with_identity(stream, expected, nonce, std::process::id(), expected.pid)
277    }
278
279    fn new_with_identity(
280        stream: UnixStream,
281        expected: PeerCredentials,
282        nonce: [u8; 32],
283        parent_pid: u32,
284        child_pid: u32,
285    ) -> Result<Self, LinuxError> {
286        let peer = peer_credentials(&stream)?;
287        if peer != expected || nonce == [0; 32] {
288            return Err(LinuxError::WrongPeer);
289        }
290        stream
291            .set_read_timeout(Some(Duration::from_secs(10)))
292            .map_err(|_| LinuxError::Bootstrap)?;
293        stream
294            .set_write_timeout(Some(Duration::from_secs(10)))
295            .map_err(|_| LinuxError::Bootstrap)?;
296        let pidfd = pidfd_open(peer.pid)?;
297        Ok(Self {
298            stream,
299            nonce,
300            peer,
301            pidfd,
302            parent_pid,
303            child_pid,
304            next_transfer_id: 1,
305            poisoned: false,
306        })
307    }
308
309    /// Returns the kernel-authenticated peer credentials.
310    pub const fn peer(&self) -> PeerCredentials {
311        self.peer
312    }
313
314    /// Returns whether the peer pidfd currently reports exit without blocking.
315    pub fn peer_exited(&self) -> Result<bool, LinuxError> {
316        let mut poll = libc::pollfd {
317            fd: self.pidfd.as_raw_fd(),
318            events: libc::POLLIN,
319            revents: 0,
320        };
321        // SAFETY: `poll` points to one initialized entry for the call.
322        let result = unsafe { libc::poll(&mut poll, 1, 0) };
323        if result < 0 {
324            return Err(last_os("poll(pidfd)"));
325        }
326        Ok(result == 1 && poll.revents != 0)
327    }
328
329    /// Transfers a prepared reader capability and withholds the local writer
330    /// until authenticated peer validation completes the READY/COMMIT barrier.
331    ///
332    /// `prepared` must come from [`QuiescentRegion::prepare_writer`]. This call
333    /// owns the complete serialized transaction and returns only after the peer
334    /// validates the exact manifest and accepts COMMIT.
335    ///
336    /// # Errors
337    ///
338    /// Returns an error for peer exit, timeout, malformed or stale control
339    /// frames, descriptor-transfer failure, or a mismatched READY transcript.
340    /// Any ambiguous failure poisons the channel and terminates an owned helper.
341    pub fn transfer_writer(
342        &mut self,
343        prepared: PreparedWriter,
344    ) -> Result<WriterRegion<LinuxWriterMapping>, LinuxError> {
345        let result = (|| {
346            self.ensure_live()?;
347            let manifest = self.manifest(prepared.expected, prepared.len, PeerAccess::ReadOnly)?;
348            send_fd(&self.stream, &manifest, prepared.capability.fd.as_raw_fd())?;
349            receive_control(&self.stream, READY_MAGIC, &manifest)?;
350            send_control(&self.stream, COMMIT_MAGIC, &manifest)?;
351            let writer = prepared.region;
352            self.next_transfer_id = self
353                .next_transfer_id
354                .checked_add(1)
355                .ok_or(LinuxError::InvalidFrame)?;
356            Ok(writer)
357        })();
358        if result.is_err() {
359            self.poison();
360        }
361        result
362    }
363
364    /// Receives, validates, maps, and binds one read-only capability.
365    ///
366    /// `expected_len` is the exact page-rounded native capability length;
367    /// `expected` and `topology` identify the canonical region. The returned
368    /// reader remains unavailable internally until COMMIT is received.
369    ///
370    /// # Errors
371    ///
372    /// Returns an error for peer exit, timeout, malformed ancillary data,
373    /// invalid seals or length, layout rejection, or a mismatched COMMIT.
374    pub fn receive_reader(
375        &mut self,
376        expected_len: usize,
377        expected: ValidationExpectations,
378        topology: RegionSetLayout,
379    ) -> Result<ReaderRegion<LinuxReaderMapping>, LinuxError> {
380        let result = (|| {
381            self.ensure_live()?;
382            let manifest = self.manifest(expected, expected_len, PeerAccess::ReadOnly)?;
383            let fd = receive_fd(&self.stream, &manifest)?;
384            let pending = LinuxReaderMapping::import(fd, expected_len, expected, topology)?;
385            send_control(&self.stream, READY_MAGIC, &manifest)?;
386            receive_control(&self.stream, COMMIT_MAGIC, &manifest)?;
387            let reader = pending.bind();
388            self.next_transfer_id = self
389                .next_transfer_id
390                .checked_add(1)
391                .ok_or(LinuxError::InvalidFrame)?;
392            Ok(reader)
393        })();
394        if result.is_err() {
395            self.poison();
396        }
397        result
398    }
399
400    fn manifest(
401        &self,
402        expected: ValidationExpectations,
403        len: usize,
404        access: PeerAccess,
405    ) -> Result<TransferManifest, LinuxError> {
406        let entry =
407            ManifestEntry::validated(expected, len, access).ok_or(LinuxError::InvalidFrame)?;
408        TransferManifest::new(
409            self.nonce,
410            self.parent_pid,
411            self.child_pid,
412            self.next_transfer_id,
413            vec![entry],
414        )
415        .ok_or(LinuxError::InvalidFrame)
416    }
417
418    fn ensure_live(&self) -> Result<(), LinuxError> {
419        if self.poisoned || self.peer_exited()? {
420            Err(LinuxError::Bootstrap)
421        } else {
422            Ok(())
423        }
424    }
425
426    fn poison(&mut self) {
427        self.poisoned = true;
428        let _ = self.stream.shutdown(std::net::Shutdown::Both);
429        if std::process::id() == self.parent_pid {
430            // SAFETY: pidfd identifies the exact authenticated helper; null
431            // siginfo and zero flags request an ordinary SIGKILL delivery.
432            let _ = unsafe {
433                libc::syscall(
434                    libc::SYS_pidfd_send_signal,
435                    self.pidfd.as_raw_fd(),
436                    libc::SIGKILL,
437                    std::ptr::null::<libc::siginfo_t>(),
438                    0,
439                )
440            };
441        }
442    }
443}
444
445/// Quiescent owner of an anonymous, page-rounded, writable memfd mapping.
446pub struct QuiescentRegion {
447    fd: OwnedFd,
448    mapping: Mapping,
449    logical_len: usize,
450}
451
452impl QuiescentRegion {
453    /// Allocates and zeroes an anonymous sealable memfd mapping.
454    pub fn new(logical_len: usize) -> Result<Self, LinuxError> {
455        let len = page_align(logical_len)?;
456        let name = c"native-ipc";
457        // SAFETY: static name is NUL-terminated and flags are valid.
458        let raw = unsafe {
459            libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING)
460        };
461        if raw < 0 {
462            return Err(last_os("memfd_create"));
463        }
464        // SAFETY: successful syscall returned a new owned descriptor.
465        let fd = unsafe { OwnedFd::from_raw_fd(raw) };
466        // SAFETY: descriptor is live and length was checked.
467        if unsafe { libc::ftruncate(fd.as_raw_fd(), len as libc::off_t) } != 0 {
468            return Err(last_os("ftruncate"));
469        }
470        let mapping = Mapping::map(fd.as_raw_fd(), len, libc::PROT_READ | libc::PROT_WRITE)?;
471        // SAFETY: new mapping is exclusive and completely initialized by zero fill.
472        unsafe { std::slice::from_raw_parts_mut(mapping.base.as_ptr(), len) }.fill(0);
473        mapping.advise()?;
474        Ok(Self {
475            fd,
476            mapping,
477            logical_len,
478        })
479    }
480
481    /// Exact page-rounded capability length.
482    pub const fn len(&self) -> usize {
483        self.mapping.len
484    }
485    /// Returns whether the capability is empty (always false for valid values).
486    pub const fn is_empty(&self) -> bool {
487        false
488    }
489    /// Requested logical layout length.
490    pub const fn logical_len(&self) -> usize {
491        self.logical_len
492    }
493    /// Quiescent initialization bytes covering the full capability.
494    pub fn as_bytes(&self) -> &[u8] {
495        // SAFETY: this typestate has no exported fd or peer mapping.
496        unsafe { std::slice::from_raw_parts(self.mapping.base.as_ptr(), self.mapping.len) }
497    }
498    /// Mutable quiescent initialization bytes covering the full capability.
499    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
500        // SAFETY: `&mut self` and quiescent typestate provide exclusivity.
501        unsafe { std::slice::from_raw_parts_mut(self.mapping.base.as_ptr(), self.mapping.len) }
502    }
503
504    /// Validates, seals, and prepares the sole writer plus export capability.
505    pub fn prepare_writer(
506        self,
507        expected: ValidationExpectations,
508        topology: RegionSetLayout,
509    ) -> Result<PreparedWriter, LinuxError> {
510        // SAFETY: no descriptor or mapping has escaped this quiescent owner.
511        let layout =
512            unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
513        // SAFETY: descriptor is live and seal mask is valid.
514        if unsafe { libc::fcntl(self.fd.as_raw_fd(), libc::F_ADD_SEALS, REQUIRED_SEALS) } < 0 {
515            return Err(last_os("fcntl(F_ADD_SEALS)"));
516        }
517        validate_fd(self.fd.as_raw_fd(), self.mapping.len)?;
518        let mapping = LinuxWriterMapping {
519            fd: self.fd,
520            mapping: self.mapping,
521        };
522        let raw = unsafe { libc::fcntl(mapping.fd.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
523        if raw < 0 {
524            return Err(last_os("fcntl(F_DUPFD_CLOEXEC)"));
525        }
526        let len = mapping.mapping.len;
527        let region = WriterRegion::new(mapping, layout, topology)?;
528        Ok(PreparedWriter {
529            region,
530            capability: ExportedReaderCapability {
531                // SAFETY: successful fcntl returned a new owned descriptor.
532                fd: unsafe { OwnedFd::from_raw_fd(raw) },
533            },
534            expected,
535            len,
536        })
537    }
538}
539
540/// Sealed sole-writer mapping awaiting export/commit.
541///
542/// ```compile_fail
543/// use native_ipc_platform::linux::PreparedWriter;
544/// fn publish_early(mut pending: PreparedWriter) {
545///     pending.publish(0, 1, None, b"too early").unwrap();
546/// }
547/// ```
548pub struct PreparedWriter {
549    region: WriterRegion<LinuxWriterMapping>,
550    capability: ExportedReaderCapability,
551    expected: ValidationExpectations,
552    len: usize,
553}
554
555/// Sealed descriptor intended for one authenticated reader transfer.
556pub struct ExportedReaderCapability {
557    fd: OwnedFd,
558}
559
560/// Platform-minted sole-writer witness retaining the memfd and mapping lifetime.
561pub struct LinuxWriterMapping {
562    fd: OwnedFd,
563    mapping: Mapping,
564}
565// SAFETY: construction requires an exclusive pre-seal mapping; FUTURE_WRITE
566// prevents all later writable mappings and write-like fd operations.
567unsafe impl SoleWriterMapping for LinuxWriterMapping {
568    fn base(&self) -> NonNull<u8> {
569        self.mapping.base
570    }
571    fn len(&self) -> usize {
572        self.mapping.len
573    }
574}
575
576/// Platform-minted read-only witness retaining the received fd and mapping.
577pub struct LinuxReaderMapping {
578    _fd: OwnedFd,
579    mapping: Mapping,
580}
581
582struct PendingLinuxReader {
583    region: ReaderRegion<LinuxReaderMapping>,
584}
585
586impl PendingLinuxReader {
587    fn bind(self) -> ReaderRegion<LinuxReaderMapping> {
588        self.region
589    }
590}
591// SAFETY: import maps only PROT_READ after exact anonymous-seal validation.
592unsafe impl ReadOnlyMapping for LinuxReaderMapping {
593    fn base(&self) -> NonNull<u8> {
594        self.mapping.base
595    }
596    fn len(&self) -> usize {
597        self.mapping.len
598    }
599}
600
601impl LinuxReaderMapping {
602    fn import(
603        fd: OwnedFd,
604        len: usize,
605        expected: ValidationExpectations,
606        topology: RegionSetLayout,
607    ) -> Result<PendingLinuxReader, LinuxError> {
608        validate_fd(fd.as_raw_fd(), len)?;
609        let mapping = Mapping::map(fd.as_raw_fd(), len, libc::PROT_READ)?;
610        mapping.advise()?;
611        // SAFETY: READY/COMMIT protocol keeps the writer quiescent during import.
612        let bytes = unsafe { std::slice::from_raw_parts(mapping.base.as_ptr(), len) };
613        let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
614        Ok(PendingLinuxReader {
615            region: ReaderRegion::new(Self { _fd: fd, mapping }, layout, topology)?,
616        })
617    }
618}
619
620struct Mapping {
621    base: NonNull<u8>,
622    len: usize,
623}
624impl Mapping {
625    fn map(fd: RawFd, len: usize, protection: libc::c_int) -> Result<Self, LinuxError> {
626        // SAFETY: arguments describe a checked file-backed shared mapping.
627        let pointer = unsafe {
628            libc::mmap(
629                std::ptr::null_mut(),
630                len,
631                protection,
632                libc::MAP_SHARED,
633                fd,
634                0,
635            )
636        };
637        if pointer == libc::MAP_FAILED {
638            return Err(last_os("mmap"));
639        }
640        let base = NonNull::new(pointer.cast()).ok_or(LinuxError::InvalidCapability)?;
641        Ok(Self { base, len })
642    }
643    fn advise(&self) -> Result<(), LinuxError> {
644        for advice in [libc::MADV_DONTDUMP, libc::MADV_DONTFORK] {
645            // SAFETY: mapping range is live for the complete call.
646            if unsafe { libc::madvise(self.base.as_ptr().cast(), self.len, advice) } != 0 {
647                return Err(last_os("madvise"));
648            }
649        }
650        Ok(())
651    }
652}
653impl Drop for Mapping {
654    fn drop(&mut self) {
655        // SAFETY: this object uniquely owns the live mapping.
656        let _ = unsafe { libc::munmap(self.base.as_ptr().cast(), self.len) };
657    }
658}
659
660fn validate_fd(fd: RawFd, expected_len: usize) -> Result<(), LinuxError> {
661    // SAFETY: output structures are valid for the live descriptor.
662    let mut stat: libc::stat = unsafe { zeroed() };
663    if unsafe { libc::fstat(fd, &mut stat) } != 0 {
664        return Err(last_os("fstat"));
665    }
666    let mut statfs: libc::statfs = unsafe { zeroed() };
667    if unsafe { libc::fstatfs(fd, &mut statfs) } != 0 {
668        return Err(last_os("fstatfs"));
669    }
670    // SAFETY: descriptor is live and command takes no pointer argument.
671    let seals = unsafe { libc::fcntl(fd, libc::F_GET_SEALS) };
672    if seals < 0
673        || seals & REQUIRED_SEALS != REQUIRED_SEALS
674        || stat.st_size != expected_len as libc::off_t
675        || stat.st_nlink != 0
676        || stat.st_mode & libc::S_IFMT != libc::S_IFREG
677        || statfs.f_type != TMPFS_MAGIC
678    {
679        return Err(LinuxError::InvalidCapability);
680    }
681    Ok(())
682}
683
684fn peer_credentials(stream: &UnixStream) -> Result<PeerCredentials, LinuxError> {
685    let mut credentials: libc::ucred = unsafe { zeroed() };
686    let mut length = size_of::<libc::ucred>() as libc::socklen_t;
687    // SAFETY: output buffer and length pointer are valid.
688    if unsafe {
689        libc::getsockopt(
690            stream.as_raw_fd(),
691            libc::SOL_SOCKET,
692            libc::SO_PEERCRED,
693            (&mut credentials as *mut libc::ucred).cast(),
694            &mut length,
695        )
696    } != 0
697        || length as usize != size_of::<libc::ucred>()
698    {
699        return Err(last_os("getsockopt(SO_PEERCRED)"));
700    }
701    Ok(PeerCredentials {
702        pid: credentials.pid as u32,
703        uid: credentials.uid,
704        gid: credentials.gid,
705    })
706}
707
708fn pidfd_open(pid: u32) -> Result<OwnedFd, LinuxError> {
709    // SAFETY: syscall has scalar arguments and returns a new fd on success.
710    let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0) } as libc::c_int;
711    if raw < 0 {
712        return Err(last_os("pidfd_open"));
713    }
714    // SAFETY: successful syscall returned an owned descriptor.
715    Ok(unsafe { OwnedFd::from_raw_fd(raw) })
716}
717
718fn send_fd(stream: &UnixStream, manifest: &TransferManifest, fd: RawFd) -> Result<(), LinuxError> {
719    let mut frame = manifest.encode(CAPABILITY_MAGIC);
720    let mut iovec = libc::iovec {
721        iov_base: frame.as_mut_ptr().cast(),
722        iov_len: frame.len(),
723    };
724    let control_len = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as u32) } as usize;
725    let mut control = vec![0_u8; control_len];
726    let mut message: libc::msghdr = unsafe { zeroed() };
727    message.msg_iov = &mut iovec;
728    message.msg_iovlen = 1;
729    message.msg_control = control.as_mut_ptr().cast();
730    message.msg_controllen = control.len();
731    // SAFETY: message owns a suitably sized control buffer.
732    unsafe {
733        let header = libc::CMSG_FIRSTHDR(&message);
734        (*header).cmsg_level = libc::SOL_SOCKET;
735        (*header).cmsg_type = libc::SCM_RIGHTS;
736        (*header).cmsg_len = libc::CMSG_LEN(size_of::<RawFd>() as u32) as usize;
737        std::ptr::write(libc::CMSG_DATA(header).cast::<RawFd>(), fd);
738    }
739    // SAFETY: iovec/control buffers remain live for the call.
740    let sent = loop {
741        // SAFETY: iovec/control buffers remain live for the call. Retrying only
742        // on EINTR is safe because a failing sendmsg transferred no bytes/fd.
743        let sent = unsafe { libc::sendmsg(stream.as_raw_fd(), &message, libc::MSG_NOSIGNAL) };
744        if sent >= 0 {
745            break sent as usize;
746        }
747        if io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
748            return Err(last_os("sendmsg"));
749        }
750    };
751    if sent == 0 || sent > CONTROL_FRAME_LEN {
752        return Err(LinuxError::InvalidFrame);
753    }
754    if sent < CONTROL_FRAME_LEN {
755        // SCM_RIGHTS is attached to the first byte. Once that byte is sent,
756        // complete the stream frame without attaching the descriptor again.
757        let mut stream = stream;
758        stream
759            .write_all(&frame[sent..])
760            .map_err(|_| LinuxError::InvalidFrame)?;
761    }
762    Ok(())
763}
764
765fn receive_fd(stream: &UnixStream, manifest: &TransferManifest) -> Result<OwnedFd, LinuxError> {
766    let (frame, mut descriptors, flags, ancillary_valid) = receive_message(stream)?;
767    if flags & (libc::MSG_TRUNC | libc::MSG_CTRUNC) != 0
768        || !manifest.matches_frame(CAPABILITY_MAGIC, &frame)
769    {
770        return Err(LinuxError::InvalidFrame);
771    }
772    if !ancillary_valid || descriptors.len() != 1 {
773        return Err(LinuxError::InvalidAncillaryData);
774    }
775    Ok(descriptors.pop().expect("exactly one descriptor"))
776}
777
778fn send_control(
779    stream: &UnixStream,
780    magic: [u8; 8],
781    manifest: &TransferManifest,
782) -> Result<(), LinuxError> {
783    let frame = manifest.encode(magic);
784    let mut stream = stream;
785    stream
786        .write_all(&frame)
787        .map_err(|_| LinuxError::InvalidFrame)
788}
789
790fn receive_control(
791    stream: &UnixStream,
792    magic: [u8; 8],
793    manifest: &TransferManifest,
794) -> Result<(), LinuxError> {
795    let (frame, descriptors, flags, ancillary_valid) = receive_message(stream)?;
796    if flags & (libc::MSG_TRUNC | libc::MSG_CTRUNC) != 0 || !manifest.matches_frame(magic, &frame) {
797        return Err(LinuxError::InvalidFrame);
798    }
799    if !ancillary_valid || !descriptors.is_empty() {
800        return Err(LinuxError::InvalidAncillaryData);
801    }
802    Ok(())
803}
804
805fn receive_message(
806    stream: &UnixStream,
807) -> Result<([u8; CONTROL_FRAME_LEN], Vec<OwnedFd>, libc::c_int, bool), LinuxError> {
808    let mut frame = [0_u8; CONTROL_FRAME_LEN];
809    // Leave enough room to adopt and close a bounded set of excess descriptors.
810    // MSG_CTRUNC still fails closed after every descriptor that fit is owned.
811    const MAX_RECEIVED_FDS: u32 = 16;
812    let control_len =
813        unsafe { libc::CMSG_SPACE((size_of::<RawFd>() * MAX_RECEIVED_FDS as usize) as u32) }
814            as usize;
815    let mut descriptors = Vec::new();
816    let mut ancillary_valid = true;
817    let mut flags = 0;
818    let mut offset = 0;
819    while offset < frame.len() {
820        let mut iovec = libc::iovec {
821            // SAFETY: `offset` remains within `frame` and the remaining length
822            // describes the initialized output capacity for recvmsg.
823            iov_base: unsafe { frame.as_mut_ptr().add(offset) }.cast(),
824            iov_len: frame.len() - offset,
825        };
826        let mut control = vec![0_u8; control_len];
827        let mut message: libc::msghdr = unsafe { zeroed() };
828        message.msg_iov = &mut iovec;
829        message.msg_iovlen = 1;
830        message.msg_control = control.as_mut_ptr().cast();
831        message.msg_controllen = control.len();
832        let received = loop {
833            // SAFETY: iovec/control buffers remain valid for the call.
834            let received =
835                unsafe { libc::recvmsg(stream.as_raw_fd(), &mut message, libc::MSG_CMSG_CLOEXEC) };
836            if received >= 0 {
837                break received as usize;
838            }
839            if io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
840                return Err(last_os("recvmsg"));
841            }
842        };
843
844        // Adopt every installed descriptor from every stream fragment before
845        // any fallible frame validation. Drop closes all on rejection.
846        // SAFETY: the kernel initialized the chain within `msg_controllen`.
847        let mut header = unsafe { libc::CMSG_FIRSTHDR(&message) };
848        while !header.is_null() {
849            // SAFETY: `header` is part of the kernel-produced chain.
850            let current = unsafe { &*header };
851            let minimum = unsafe { libc::CMSG_LEN(0) } as usize;
852            if current.cmsg_len < minimum || current.cmsg_len > message.msg_controllen {
853                ancillary_valid = false;
854                break;
855            }
856            if current.cmsg_level != libc::SOL_SOCKET || current.cmsg_type != libc::SCM_RIGHTS {
857                ancillary_valid = false;
858            } else {
859                let payload_len = current.cmsg_len - minimum;
860                if payload_len == 0 || !payload_len.is_multiple_of(size_of::<RawFd>()) {
861                    ancillary_valid = false;
862                } else {
863                    let count = payload_len / size_of::<RawFd>();
864                    for index in 0..count {
865                        // SAFETY: cmsg length proves this payload element exists.
866                        let raw = unsafe {
867                            std::ptr::read_unaligned(
868                                libc::CMSG_DATA(header).cast::<RawFd>().add(index),
869                            )
870                        };
871                        if raw < 0 {
872                            ancillary_valid = false;
873                        } else {
874                            // SAFETY: SCM_RIGHTS installed this new descriptor.
875                            descriptors.push(unsafe { OwnedFd::from_raw_fd(raw) });
876                        }
877                    }
878                }
879            }
880            // SAFETY: advances within this kernel-produced control buffer.
881            header = unsafe { libc::CMSG_NXTHDR(&message, header) };
882        }
883        flags |= message.msg_flags;
884        if received == 0 || received > frame.len() - offset {
885            return Err(LinuxError::InvalidFrame);
886        }
887        offset += received;
888    }
889    Ok((frame, descriptors, flags, ancillary_valid))
890}
891
892fn page_align(size: usize) -> Result<usize, LinuxError> {
893    if size == 0 {
894        return Err(LinuxError::InvalidSize(size));
895    }
896    // SAFETY: sysconf has no pointer arguments.
897    let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
898    if page <= 0 {
899        return Err(last_os("sysconf(_SC_PAGESIZE)"));
900    }
901    let page = page as usize;
902    size.checked_add(page - 1)
903        .map(|value| value & !(page - 1))
904        .filter(|value| *value <= isize::MAX as usize)
905        .ok_or(LinuxError::InvalidSize(size))
906}
907
908fn hex(bytes: &[u8]) -> String {
909    const DIGITS: &[u8; 16] = b"0123456789abcdef";
910    let mut encoded = String::with_capacity(bytes.len() * 2);
911    for byte in bytes {
912        encoded.push(DIGITS[(byte >> 4) as usize] as char);
913        encoded.push(DIGITS[(byte & 0x0f) as usize] as char);
914    }
915    encoded
916}
917
918fn parse_nonce(encoded: &str) -> Result<[u8; 32], LinuxError> {
919    if encoded.len() != 64 {
920        return Err(LinuxError::Bootstrap);
921    }
922    let mut nonce = [0_u8; 32];
923    for (output, pair) in nonce.iter_mut().zip(encoded.as_bytes().chunks_exact(2)) {
924        let high = (pair[0] as char)
925            .to_digit(16)
926            .ok_or(LinuxError::Bootstrap)?;
927        let low = (pair[1] as char)
928            .to_digit(16)
929            .ok_or(LinuxError::Bootstrap)?;
930        *output = ((high << 4) | low) as u8;
931    }
932    Ok(nonce)
933}
934
935fn last_os(operation: &'static str) -> LinuxError {
936    LinuxError::Os {
937        operation,
938        code: io::Error::last_os_error().raw_os_error().unwrap_or(-1),
939    }
940}
941
942/// Reports an enforced sealed-memfd backend on supported Linux kernels.
943pub const fn status() -> BackendStatus {
944    BackendStatus::Available
945}
946
947#[cfg(test)]
948mod tests {
949    use super::*;
950    use native_ipc_core::layout::{
951        AcknowledgementRouteSpec, Endpoint, LayoutError, LayoutLimits, RegionSpec, RoleId,
952    };
953
954    fn send_fragmented_with_fds(
955        stream: &UnixStream,
956        frame: &[u8; CONTROL_FRAME_LEN],
957        fds: &[RawFd],
958        first_bytes: usize,
959    ) {
960        send_chunk_with_fds(stream, &frame[..first_bytes], fds);
961        let mut stream = stream;
962        stream.write_all(&frame[first_bytes..]).unwrap();
963    }
964
965    fn send_chunk_with_fds(stream: &UnixStream, bytes: &[u8], fds: &[RawFd]) {
966        let mut prefix = bytes.to_vec();
967        let mut iovec = libc::iovec {
968            iov_base: prefix.as_mut_ptr().cast(),
969            iov_len: prefix.len(),
970        };
971        let control_len = unsafe { libc::CMSG_SPACE(std::mem::size_of_val(fds) as u32) } as usize;
972        let mut control = vec![0_u8; control_len];
973        let mut message: libc::msghdr = unsafe { zeroed() };
974        message.msg_iov = &mut iovec;
975        message.msg_iovlen = 1;
976        message.msg_control = control.as_mut_ptr().cast();
977        message.msg_controllen = control.len();
978        // SAFETY: the control allocation exactly fits the supplied descriptor slice.
979        unsafe {
980            let header = libc::CMSG_FIRSTHDR(&message);
981            (*header).cmsg_level = libc::SOL_SOCKET;
982            (*header).cmsg_type = libc::SCM_RIGHTS;
983            (*header).cmsg_len = libc::CMSG_LEN(std::mem::size_of_val(fds) as u32) as usize;
984            std::ptr::copy_nonoverlapping(
985                fds.as_ptr(),
986                libc::CMSG_DATA(header).cast::<RawFd>(),
987                fds.len(),
988            );
989            assert_eq!(
990                libc::sendmsg(stream.as_raw_fd(), &message, libc::MSG_NOSIGNAL),
991                bytes.len() as isize
992            );
993        }
994    }
995
996    fn open_fd_count() -> usize {
997        std::fs::read_dir("/proc/self/fd").unwrap().count()
998    }
999
1000    #[test]
1001    fn backend_reports_available() {
1002        assert_eq!(status(), BackendStatus::Available);
1003    }
1004
1005    #[test]
1006    fn peer_credentials_are_kernel_derived() {
1007        let (left, right) = UnixStream::pair().unwrap();
1008        let expected = peer_credentials(&right).unwrap();
1009        let channel = AuthenticatedChannel::new(left, expected, [7; 32]).unwrap();
1010        assert_eq!(channel.peer(), expected);
1011    }
1012
1013    #[test]
1014    fn invalid_sizes_capabilities_and_peer_identity_fail_exactly() {
1015        assert!(matches!(
1016            QuiescentRegion::new(0),
1017            Err(LinuxError::InvalidSize(0))
1018        ));
1019        assert!(matches!(
1020            QuiescentRegion::new(usize::MAX),
1021            Err(LinuxError::InvalidSize(usize::MAX))
1022        ));
1023
1024        let unsealed = QuiescentRegion::new(1).unwrap();
1025        assert!(matches!(
1026            validate_fd(unsealed.fd.as_raw_fd(), unsealed.len()),
1027            Err(LinuxError::InvalidCapability)
1028        ));
1029
1030        let (left, right) = UnixStream::pair().unwrap();
1031        let actual = peer_credentials(&right).unwrap();
1032        let wrong = PeerCredentials {
1033            pid: actual.pid.wrapping_add(1),
1034            ..actual
1035        };
1036        assert!(matches!(
1037            AuthenticatedChannel::new(left, wrong, [9; 32]),
1038            Err(LinuxError::WrongPeer)
1039        ));
1040
1041        let (left, right) = UnixStream::pair().unwrap();
1042        let actual = peer_credentials(&right).unwrap();
1043        assert!(matches!(
1044            AuthenticatedChannel::new(left, actual, [0; 32]),
1045            Err(LinuxError::WrongPeer)
1046        ));
1047    }
1048
1049    #[test]
1050    fn fragmented_stream_frame_preserves_ancillary_ownership() {
1051        let expected = ValidationExpectations {
1052            schema_id: [3; 32],
1053            generation: 9,
1054            role: RoleId::new(7).unwrap(),
1055            writer: Endpoint::Initiator,
1056            maximum_mapping_size: 4096,
1057        };
1058        let entry = ManifestEntry::validated(expected, 4096, PeerAccess::ReadOnly).unwrap();
1059        let nonce = [4; 32];
1060        let manifest = TransferManifest::new(nonce, 1, 2, 1, vec![entry]).unwrap();
1061        let frame = manifest.encode(CAPABILITY_MAGIC);
1062        let file = std::fs::File::open("/dev/null").unwrap();
1063        let (sender, receiver) = UnixStream::pair().unwrap();
1064        let received = std::thread::scope(|scope| {
1065            let task = scope.spawn(|| receive_fd(&receiver, &manifest));
1066            send_fragmented_with_fds(&sender, &frame, &[file.as_raw_fd()], 1);
1067            task.join().unwrap().unwrap()
1068        });
1069        assert!(received.as_raw_fd() >= 0);
1070    }
1071
1072    #[test]
1073    #[ignore = "spawned in an isolated process by descriptor_cleanup_is_zero_growth"]
1074    fn malformed_extra_descriptor_frame_has_zero_fd_growth() {
1075        let before = open_fd_count();
1076        {
1077            let expected = ValidationExpectations {
1078                schema_id: [5; 32],
1079                generation: 11,
1080                role: RoleId::new(8).unwrap(),
1081                writer: Endpoint::Responder,
1082                maximum_mapping_size: 4096,
1083            };
1084            let entry = ManifestEntry::validated(expected, 4096, PeerAccess::ReadOnly).unwrap();
1085            let nonce = [6; 32];
1086            let manifest = TransferManifest::new(nonce, 1, 2, 1, vec![entry]).unwrap();
1087            let frame = manifest.encode(CAPABILITY_MAGIC);
1088            let first = std::fs::File::open("/dev/null").unwrap();
1089            let second = std::fs::File::open("/dev/null").unwrap();
1090            let (sender, receiver) = UnixStream::pair().unwrap();
1091            std::thread::scope(|scope| {
1092                let task = scope.spawn(|| receive_fd(&receiver, &manifest));
1093                send_fragmented_with_fds(
1094                    &sender,
1095                    &frame,
1096                    &[first.as_raw_fd(), second.as_raw_fd()],
1097                    7,
1098                );
1099                assert!(matches!(
1100                    task.join().unwrap(),
1101                    Err(LinuxError::InvalidAncillaryData)
1102                ));
1103            });
1104        }
1105        assert_eq!(open_fd_count(), before);
1106    }
1107
1108    #[test]
1109    #[ignore = "spawned in an isolated process by descriptor_cleanup_is_zero_growth"]
1110    fn ancillary_on_later_stream_fragment_is_adopted_and_rejected() {
1111        let before = open_fd_count();
1112        {
1113            let expected = ValidationExpectations {
1114                schema_id: [7; 32],
1115                generation: 13,
1116                role: RoleId::new(9).unwrap(),
1117                writer: Endpoint::Initiator,
1118                maximum_mapping_size: 4096,
1119            };
1120            let entry = ManifestEntry::validated(expected, 4096, PeerAccess::ReadOnly).unwrap();
1121            let manifest = TransferManifest::new([8; 32], 1, 2, 1, vec![entry]).unwrap();
1122            let frame = manifest.encode(CAPABILITY_MAGIC);
1123            let first = std::fs::File::open("/dev/null").unwrap();
1124            let second = std::fs::File::open("/dev/null").unwrap();
1125            let (sender, receiver) = UnixStream::pair().unwrap();
1126            std::thread::scope(|scope| {
1127                let task = scope.spawn(|| receive_fd(&receiver, &manifest));
1128                send_chunk_with_fds(&sender, &frame[..7], &[first.as_raw_fd()]);
1129                send_chunk_with_fds(&sender, &frame[7..], &[second.as_raw_fd()]);
1130                assert!(matches!(
1131                    task.join().unwrap(),
1132                    Err(LinuxError::InvalidAncillaryData)
1133                ));
1134            });
1135        }
1136        assert_eq!(open_fd_count(), before);
1137    }
1138
1139    #[test]
1140    fn descriptor_cleanup_is_zero_growth() {
1141        let executable = std::env::current_exe().unwrap();
1142        for test in [
1143            "linux::tests::malformed_extra_descriptor_frame_has_zero_fd_growth",
1144            "linux::tests::ancillary_on_later_stream_fragment_is_adopted_and_rejected",
1145        ] {
1146            let status = Command::new(&executable)
1147                .args(["--exact", test, "--ignored", "--nocapture"])
1148                .status()
1149                .unwrap();
1150            assert!(status.success(), "isolated descriptor test failed: {test}");
1151        }
1152    }
1153
1154    #[test]
1155    fn sealed_capability_transfers_and_binds_payload_path() {
1156        let producer = RoleId::new(1).unwrap();
1157        let peer = RoleId::new(2).unwrap();
1158        let specs = [
1159            RegionSpec {
1160                role: producer,
1161                writer: Endpoint::Initiator,
1162                slot_count: 1,
1163                payload_bytes: 16,
1164                acknowledgement_count: 1,
1165            },
1166            RegionSpec {
1167                role: peer,
1168                writer: Endpoint::Responder,
1169                slot_count: 1,
1170                payload_bytes: 16,
1171                acknowledgement_count: 1,
1172            },
1173        ];
1174        let routes = [
1175            AcknowledgementRouteSpec {
1176                owner: peer,
1177                target: producer,
1178                slot_index: 0,
1179                cell_index: 0,
1180            },
1181            AcknowledgementRouteSpec {
1182                owner: producer,
1183                target: peer,
1184                slot_index: 0,
1185                cell_index: 0,
1186            },
1187        ];
1188        let topology = RegionSetLayout::calculate(
1189            [5; 32],
1190            13,
1191            &specs,
1192            &routes,
1193            LayoutLimits {
1194                maximum_mapping_size: 1 << 20,
1195                maximum_slot_count: 2,
1196                maximum_acknowledgement_count: 2,
1197                maximum_payload_bytes: 64,
1198            },
1199        )
1200        .unwrap();
1201        let layout = topology.region(producer).unwrap();
1202        let mut owner = QuiescentRegion::new(layout.total_size() as usize).unwrap();
1203        layout.encode_into(owner.as_bytes_mut()).unwrap();
1204        let expected = ValidationExpectations {
1205            schema_id: [5; 32],
1206            generation: 13,
1207            role: producer,
1208            writer: Endpoint::Initiator,
1209            maximum_mapping_size: owner.len() as u64,
1210        };
1211        let prepared = owner.prepare_writer(expected, topology.clone()).unwrap();
1212        let transfer_len = prepared.len;
1213        let capability = &prepared.capability;
1214
1215        // A sealed exported fd cannot create another writable mapping.
1216        let denied = unsafe {
1217            libc::mmap(
1218                std::ptr::null_mut(),
1219                transfer_len,
1220                libc::PROT_READ | libc::PROT_WRITE,
1221                libc::MAP_SHARED,
1222                capability.fd.as_raw_fd(),
1223                0,
1224            )
1225        };
1226        assert_eq!(denied, libc::MAP_FAILED);
1227        assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
1228
1229        // Seals also deny descriptor writes and any size change with exact EPERM.
1230        let byte = 0xff_u8;
1231        let written =
1232            unsafe { libc::pwrite(capability.fd.as_raw_fd(), (&raw const byte).cast(), 1, 0) };
1233        assert_eq!(written, -1);
1234        assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
1235        assert_eq!(
1236            unsafe {
1237                libc::ftruncate(
1238                    capability.fd.as_raw_fd(),
1239                    transfer_len.saturating_add(4096) as libc::off_t,
1240                )
1241            },
1242            -1
1243        );
1244        assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
1245        assert_eq!(unsafe { libc::ftruncate(capability.fd.as_raw_fd(), 1) }, -1);
1246        assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
1247
1248        let (left, right) = UnixStream::pair().unwrap();
1249        let credentials = peer_credentials(&left).unwrap();
1250        let mut sender = AuthenticatedChannel::new(left, credentials, [8; 32]).unwrap();
1251        let mut receiver = AuthenticatedChannel::new(right, credentials, [8; 32]).unwrap();
1252        std::thread::scope(|scope| {
1253            let received = scope.spawn(|| {
1254                let reader = receiver
1255                    .receive_reader(transfer_len, expected, topology.clone())
1256                    .unwrap();
1257                for _ in 0..10_000 {
1258                    if let Ok(payload) = reader.copy_payload(0, 1) {
1259                        assert_eq!(payload, b"linux");
1260                        return;
1261                    }
1262                    std::thread::yield_now();
1263                }
1264                panic!("reader never observed publication");
1265            });
1266            let mut writer = sender.transfer_writer(prepared).unwrap();
1267            assert_eq!(
1268                writer.publish(0, 1, None, &[0xaa; 17]).unwrap_err(),
1269                BindingError::Layout(LayoutError::PayloadOutOfBounds {
1270                    length: 17,
1271                    capacity: 16,
1272                })
1273            );
1274            writer.publish(0, 1, None, b"linux").unwrap();
1275            received.join().unwrap();
1276        });
1277    }
1278
1279    // Serializes every test that creates a bootstrap directory so the
1280    // zero-growth assertions cannot observe another test's live session.
1281    static BOOTSTRAP_DIR_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1282
1283    fn bootstrap_dirs() -> std::collections::BTreeSet<PathBuf> {
1284        std::fs::read_dir(std::env::temp_dir())
1285            .unwrap()
1286            .filter_map(Result::ok)
1287            .map(|entry| entry.path())
1288            .filter(|path| {
1289                path.file_name()
1290                    .and_then(|name| name.to_str())
1291                    .is_some_and(|name| name.starts_with("native-ipc-"))
1292            })
1293            .collect()
1294    }
1295
1296    #[test]
1297    fn spawned_helper_is_pid_authenticated_and_owned() {
1298        let _serial = BOOTSTRAP_DIR_LOCK
1299            .lock()
1300            .unwrap_or_else(std::sync::PoisonError::into_inner);
1301        let executable = std::env::current_exe().unwrap();
1302        let arguments = [
1303            OsStr::new("--exact"),
1304            OsStr::new("linux::tests::spawned_helper_entry"),
1305            OsStr::new("--ignored"),
1306            OsStr::new("--nocapture"),
1307        ];
1308        let session = ChildSession::spawn(executable.as_os_str(), &arguments).unwrap();
1309        assert_eq!(session.channel().peer().pid, session.child_id());
1310        session.terminate().unwrap();
1311    }
1312
1313    #[test]
1314    fn failed_spawn_cleans_bootstrap_resources() {
1315        let _serial = BOOTSTRAP_DIR_LOCK
1316            .lock()
1317            .unwrap_or_else(std::sync::PoisonError::into_inner);
1318        let before = bootstrap_dirs();
1319        let Err(error) =
1320            ChildSession::spawn(OsStr::new("/definitely/not/a/real/native-ipc-helper"), &[])
1321        else {
1322            panic!("spawning a nonexistent helper must fail");
1323        };
1324        assert!(matches!(error, LinuxError::Bootstrap));
1325        assert_eq!(bootstrap_dirs(), before);
1326    }
1327
1328    #[test]
1329    fn timed_out_helper_cleans_bootstrap_resources_and_child() {
1330        let _serial = BOOTSTRAP_DIR_LOCK
1331            .lock()
1332            .unwrap_or_else(std::sync::PoisonError::into_inner);
1333        let before = bootstrap_dirs();
1334        // The helper never connects, so spawn reaches its accept deadline; the
1335        // child must be killed and reaped and the directory removed.
1336        let Err(error) = ChildSession::spawn(OsStr::new("/bin/sleep"), &[OsStr::new("30")]) else {
1337            panic!("a helper that never connects must time out");
1338        };
1339        assert!(matches!(error, LinuxError::Bootstrap));
1340        assert_eq!(bootstrap_dirs(), before);
1341    }
1342
1343    #[test]
1344    #[ignore = "spawned only by the owned-child integration test"]
1345    fn spawned_helper_entry() {
1346        let channel = connect_spawned_helper().unwrap();
1347        assert_ne!(channel.peer().pid, 0);
1348        std::thread::sleep(Duration::from_secs(30));
1349    }
1350}