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