1use 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#[derive(Debug)]
36pub enum LinuxError {
37 Os {
39 operation: &'static str,
41 code: i32,
43 },
44 InvalidSize(usize),
46 WrongPeer,
48 InvalidFrame,
50 InvalidAncillaryData,
52 InvalidCapability,
54 Bootstrap,
56 Layout(native_ipc_core::layout::LayoutError),
58 Binding(BindingError),
60}
61
62pub struct ChildSession {
64 child: Child,
65 channel: AuthenticatedChannel,
66 bootstrap_dir: PathBuf,
67}
68
69impl ChildSession {
70 pub fn spawn(program: &OsStr, arguments: &[&OsStr]) -> Result<Self, LinuxError> {
72 let mut nonce = [0_u8; 32];
73 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 uid: unsafe { libc::geteuid() },
103 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 pub const fn channel(&self) -> &AuthenticatedChannel {
135 &self.channel
136 }
137
138 pub const fn channel_mut(&mut self) -> &mut AuthenticatedChannel {
140 &mut self.channel
141 }
142
143 pub fn child_id(&self) -> u32 {
145 self.child.id()
146 }
147
148 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
164pub 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 uid: unsafe { libc::geteuid() },
177 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub struct PeerCredentials {
204 pub pid: u32,
206 pub uid: u32,
208 pub gid: u32,
210}
211
212pub 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 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 pub const fn peer(&self) -> PeerCredentials {
266 self.peer
267 }
268
269 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 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 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 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 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
400pub struct QuiescentRegion {
402 fd: OwnedFd,
403 mapping: Mapping,
404 logical_len: usize,
405}
406
407impl QuiescentRegion {
408 pub fn new(logical_len: usize) -> Result<Self, LinuxError> {
410 let len = page_align(logical_len)?;
411 let name = c"native-ipc";
412 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 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
421 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 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 pub const fn len(&self) -> usize {
438 self.mapping.len
439 }
440 pub const fn is_empty(&self) -> bool {
442 false
443 }
444 pub const fn logical_len(&self) -> usize {
446 self.logical_len
447 }
448 pub fn as_bytes(&self) -> &[u8] {
450 unsafe { std::slice::from_raw_parts(self.mapping.base.as_ptr(), self.mapping.len) }
452 }
453 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
455 unsafe { std::slice::from_raw_parts_mut(self.mapping.base.as_ptr(), self.mapping.len) }
457 }
458
459 pub fn prepare_writer(
461 self,
462 expected: ValidationExpectations,
463 topology: RegionSetLayout,
464 ) -> Result<PreparedWriter, LinuxError> {
465 let layout =
467 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
468 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 fd: unsafe { OwnedFd::from_raw_fd(raw) },
488 },
489 expected,
490 len,
491 })
492 }
493}
494
495pub struct PreparedWriter {
504 region: WriterRegion<LinuxWriterMapping>,
505 capability: ExportedReaderCapability,
506 expected: ValidationExpectations,
507 len: usize,
508}
509
510pub struct ExportedReaderCapability {
512 fd: OwnedFd,
513}
514
515pub struct LinuxWriterMapping {
517 fd: OwnedFd,
518 mapping: Mapping,
519}
520unsafe 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
531pub 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}
546unsafe 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 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 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 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 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 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 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 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 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 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 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 let sent = loop {
696 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 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 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 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 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 let mut header = unsafe { libc::CMSG_FIRSTHDR(&message) };
803 while !header.is_null() {
804 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 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 descriptors.push(unsafe { OwnedFd::from_raw_fd(raw) });
831 }
832 }
833 }
834 }
835 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 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
897pub 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 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 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 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}