1use std::ffi::OsStr;
4use std::fmt;
5use std::io;
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;
22
23const REQUIRED_SEALS: libc::c_int =
24 libc::F_SEAL_GROW | libc::F_SEAL_SHRINK | libc::F_SEAL_FUTURE_WRITE | libc::F_SEAL_SEAL;
25const FRAME_MAGIC: [u8; 8] = *b"NIPCFD\0\0";
26const FRAME_LEN: usize = 48;
27const TMPFS_MAGIC: libc::c_long = 0x0102_1994;
28const ENV_SOCKET: &str = "NATIVE_IPC_LINUX_SOCKET";
29const ENV_NONCE: &str = "NATIVE_IPC_SESSION_NONCE";
30const ENV_PARENT_PID: &str = "NATIVE_IPC_PARENT_PID";
31
32#[derive(Debug)]
34pub enum LinuxError {
35 Os {
37 operation: &'static str,
39 code: i32,
41 },
42 InvalidSize(usize),
44 WrongPeer,
46 InvalidFrame,
48 InvalidAncillaryData,
50 InvalidCapability,
52 Bootstrap,
54 Layout(native_ipc_core::layout::LayoutError),
56 Binding(BindingError),
58}
59
60pub struct ChildSession {
62 child: Child,
63 channel: AuthenticatedChannel,
64 bootstrap_dir: PathBuf,
65}
66
67impl ChildSession {
68 pub fn spawn(program: &OsStr, arguments: &[&OsStr]) -> Result<Self, LinuxError> {
70 let mut nonce = [0_u8; 32];
71 if unsafe { libc::getrandom(nonce.as_mut_ptr().cast(), nonce.len(), 0) }
73 != nonce.len() as isize
74 {
75 return Err(last_os("getrandom"));
76 }
77 let suffix = hex(&nonce[..16]);
78 let bootstrap_dir = std::env::temp_dir().join(format!("native-ipc-{suffix}"));
79 std::fs::create_dir(&bootstrap_dir).map_err(|_| LinuxError::Bootstrap)?;
80 std::fs::set_permissions(&bootstrap_dir, std::fs::Permissions::from_mode(0o700))
81 .map_err(|_| LinuxError::Bootstrap)?;
82 let socket_path = bootstrap_dir.join("control.sock");
83 let listener = UnixListener::bind(&socket_path).map_err(|_| LinuxError::Bootstrap)?;
84 std::fs::set_permissions(&socket_path, std::fs::Permissions::from_mode(0o600))
85 .map_err(|_| LinuxError::Bootstrap)?;
86 listener
87 .set_nonblocking(true)
88 .map_err(|_| LinuxError::Bootstrap)?;
89
90 let mut command = Command::new(program);
91 command
92 .args(arguments)
93 .env(ENV_SOCKET, &socket_path)
94 .env(ENV_NONCE, hex(&nonce))
95 .env(ENV_PARENT_PID, std::process::id().to_string());
96 let mut child = command.spawn().map_err(|_| LinuxError::Bootstrap)?;
97 let expected = PeerCredentials {
98 pid: child.id(),
99 uid: unsafe { libc::geteuid() },
101 gid: unsafe { libc::getegid() },
103 };
104 let deadline = Instant::now() + Duration::from_secs(10);
105 let stream = loop {
106 match listener.accept() {
107 Ok((stream, _)) => {
108 if peer_credentials(&stream)? == expected {
109 break stream;
110 }
111 }
112 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
113 if Instant::now() >= deadline {
114 let _ = child.kill();
115 let _ = child.wait();
116 return Err(LinuxError::Bootstrap);
117 }
118 std::thread::sleep(Duration::from_millis(5));
119 }
120 Err(_) => return Err(LinuxError::Bootstrap),
121 }
122 };
123 let channel = AuthenticatedChannel::new(stream, expected, nonce)?;
124 Ok(Self {
125 child,
126 channel,
127 bootstrap_dir,
128 })
129 }
130
131 pub const fn channel(&self) -> &AuthenticatedChannel {
133 &self.channel
134 }
135
136 pub fn child_id(&self) -> u32 {
138 self.child.id()
139 }
140
141 pub fn terminate(mut self) -> Result<(), LinuxError> {
143 self.child.kill().map_err(|_| LinuxError::Bootstrap)?;
144 self.child.wait().map_err(|_| LinuxError::Bootstrap)?;
145 Ok(())
146 }
147}
148
149impl Drop for ChildSession {
150 fn drop(&mut self) {
151 let _ = self.child.kill();
152 let _ = self.child.wait();
153 let _ = std::fs::remove_dir_all(&self.bootstrap_dir);
154 }
155}
156
157pub fn connect_spawned_helper() -> Result<AuthenticatedChannel, LinuxError> {
159 let path = std::env::var_os(ENV_SOCKET).ok_or(LinuxError::Bootstrap)?;
160 let nonce = parse_nonce(&std::env::var(ENV_NONCE).map_err(|_| LinuxError::Bootstrap)?)?;
161 let parent_pid = std::env::var(ENV_PARENT_PID)
162 .map_err(|_| LinuxError::Bootstrap)?
163 .parse()
164 .map_err(|_| LinuxError::Bootstrap)?;
165 let stream = UnixStream::connect(path).map_err(|_| LinuxError::Bootstrap)?;
166 let expected = PeerCredentials {
167 pid: parent_pid,
168 uid: unsafe { libc::geteuid() },
170 gid: unsafe { libc::getegid() },
172 };
173 AuthenticatedChannel::new(stream, expected, nonce)
174}
175
176impl fmt::Display for LinuxError {
177 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(formatter, "Linux transport failed: {self:?}")
179 }
180}
181
182impl std::error::Error for LinuxError {}
183impl From<native_ipc_core::layout::LayoutError> for LinuxError {
184 fn from(value: native_ipc_core::layout::LayoutError) -> Self {
185 Self::Layout(value)
186 }
187}
188impl From<BindingError> for LinuxError {
189 fn from(value: BindingError) -> Self {
190 Self::Binding(value)
191 }
192}
193
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
196pub struct PeerCredentials {
197 pub pid: u32,
199 pub uid: u32,
201 pub gid: u32,
203}
204
205pub struct AuthenticatedChannel {
207 stream: UnixStream,
208 nonce: [u8; 32],
209 peer: PeerCredentials,
210 pidfd: OwnedFd,
211}
212
213impl AuthenticatedChannel {
214 pub fn new(
216 stream: UnixStream,
217 expected: PeerCredentials,
218 nonce: [u8; 32],
219 ) -> Result<Self, LinuxError> {
220 let peer = peer_credentials(&stream)?;
221 if peer != expected || nonce == [0; 32] {
222 return Err(LinuxError::WrongPeer);
223 }
224 let pidfd = pidfd_open(peer.pid)?;
225 Ok(Self {
226 stream,
227 nonce,
228 peer,
229 pidfd,
230 })
231 }
232
233 pub const fn peer(&self) -> PeerCredentials {
235 self.peer
236 }
237
238 pub fn peer_exited(&self) -> Result<bool, LinuxError> {
240 let mut poll = libc::pollfd {
241 fd: self.pidfd.as_raw_fd(),
242 events: libc::POLLIN,
243 revents: 0,
244 };
245 let result = unsafe { libc::poll(&mut poll, 1, 0) };
247 if result < 0 {
248 return Err(last_os("poll(pidfd)"));
249 }
250 Ok(result == 1 && poll.revents != 0)
251 }
252
253 pub fn send(&self, capability: &ExportedReaderCapability) -> Result<(), LinuxError> {
255 send_fd(
256 &self.stream,
257 &self.nonce,
258 capability.len,
259 capability.fd.as_raw_fd(),
260 )
261 }
262
263 pub fn receive_reader(
265 &self,
266 expected_len: usize,
267 expected: ValidationExpectations,
268 topology: RegionSetLayout,
269 ) -> Result<ReaderRegion<LinuxReaderMapping>, LinuxError> {
270 let fd = receive_fd(&self.stream, &self.nonce, expected_len)?;
271 LinuxReaderMapping::import(fd, expected_len, expected, topology)
272 }
273}
274
275pub struct QuiescentRegion {
277 fd: OwnedFd,
278 mapping: Mapping,
279 logical_len: usize,
280}
281
282impl QuiescentRegion {
283 pub fn new(logical_len: usize) -> Result<Self, LinuxError> {
285 let len = page_align(logical_len)?;
286 let name = c"native-ipc";
287 let raw = unsafe {
289 libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC | libc::MFD_ALLOW_SEALING)
290 };
291 if raw < 0 {
292 return Err(last_os("memfd_create"));
293 }
294 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
296 if unsafe { libc::ftruncate(fd.as_raw_fd(), len as libc::off_t) } != 0 {
298 return Err(last_os("ftruncate"));
299 }
300 let mapping = Mapping::map(fd.as_raw_fd(), len, libc::PROT_READ | libc::PROT_WRITE)?;
301 unsafe { std::slice::from_raw_parts_mut(mapping.base.as_ptr(), len) }.fill(0);
303 mapping.advise()?;
304 Ok(Self {
305 fd,
306 mapping,
307 logical_len,
308 })
309 }
310
311 pub const fn len(&self) -> usize {
313 self.mapping.len
314 }
315 pub const fn is_empty(&self) -> bool {
317 false
318 }
319 pub const fn logical_len(&self) -> usize {
321 self.logical_len
322 }
323 pub fn as_bytes(&self) -> &[u8] {
325 unsafe { std::slice::from_raw_parts(self.mapping.base.as_ptr(), self.mapping.len) }
327 }
328 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
330 unsafe { std::slice::from_raw_parts_mut(self.mapping.base.as_ptr(), self.mapping.len) }
332 }
333
334 pub fn prepare_writer(
336 self,
337 expected: ValidationExpectations,
338 topology: RegionSetLayout,
339 ) -> Result<PreparedWriter, LinuxError> {
340 let layout =
342 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
343 if unsafe { libc::fcntl(self.fd.as_raw_fd(), libc::F_ADD_SEALS, REQUIRED_SEALS) } < 0 {
345 return Err(last_os("fcntl(F_ADD_SEALS)"));
346 }
347 validate_fd(self.fd.as_raw_fd(), self.mapping.len)?;
348 Ok(PreparedWriter {
349 mapping: LinuxWriterMapping {
350 fd: self.fd,
351 mapping: self.mapping,
352 },
353 layout,
354 topology,
355 })
356 }
357}
358
359pub struct PreparedWriter {
361 mapping: LinuxWriterMapping,
362 layout: ValidatedRegionLayout,
363 topology: RegionSetLayout,
364}
365
366impl PreparedWriter {
367 pub fn export_reader(&self) -> Result<ExportedReaderCapability, LinuxError> {
369 let raw = unsafe { libc::fcntl(self.mapping.fd.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
371 if raw < 0 {
372 return Err(last_os("fcntl(F_DUPFD_CLOEXEC)"));
373 }
374 Ok(ExportedReaderCapability {
375 fd: unsafe { OwnedFd::from_raw_fd(raw) },
377 len: self.mapping.mapping.len,
378 })
379 }
380
381 pub fn bind(self) -> Result<WriterRegion<LinuxWriterMapping>, LinuxError> {
383 Ok(WriterRegion::new(self.mapping, self.layout, self.topology)?)
384 }
385}
386
387pub struct ExportedReaderCapability {
389 fd: OwnedFd,
390 len: usize,
391}
392
393pub struct LinuxWriterMapping {
395 fd: OwnedFd,
396 mapping: Mapping,
397}
398unsafe impl SoleWriterMapping for LinuxWriterMapping {
401 fn base(&self) -> NonNull<u8> {
402 self.mapping.base
403 }
404 fn len(&self) -> usize {
405 self.mapping.len
406 }
407}
408
409pub struct LinuxReaderMapping {
411 _fd: OwnedFd,
412 mapping: Mapping,
413}
414unsafe impl ReadOnlyMapping for LinuxReaderMapping {
416 fn base(&self) -> NonNull<u8> {
417 self.mapping.base
418 }
419 fn len(&self) -> usize {
420 self.mapping.len
421 }
422}
423
424impl LinuxReaderMapping {
425 fn import(
426 fd: OwnedFd,
427 len: usize,
428 expected: ValidationExpectations,
429 topology: RegionSetLayout,
430 ) -> Result<ReaderRegion<Self>, LinuxError> {
431 validate_fd(fd.as_raw_fd(), len)?;
432 let mapping = Mapping::map(fd.as_raw_fd(), len, libc::PROT_READ)?;
433 mapping.advise()?;
434 let bytes = unsafe { std::slice::from_raw_parts(mapping.base.as_ptr(), len) };
436 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
437 Ok(ReaderRegion::new(
438 Self { _fd: fd, mapping },
439 layout,
440 topology,
441 )?)
442 }
443}
444
445struct Mapping {
446 base: NonNull<u8>,
447 len: usize,
448}
449impl Mapping {
450 fn map(fd: RawFd, len: usize, protection: libc::c_int) -> Result<Self, LinuxError> {
451 let pointer = unsafe {
453 libc::mmap(
454 std::ptr::null_mut(),
455 len,
456 protection,
457 libc::MAP_SHARED,
458 fd,
459 0,
460 )
461 };
462 if pointer == libc::MAP_FAILED {
463 return Err(last_os("mmap"));
464 }
465 let base = NonNull::new(pointer.cast()).ok_or(LinuxError::InvalidCapability)?;
466 Ok(Self { base, len })
467 }
468 fn advise(&self) -> Result<(), LinuxError> {
469 for advice in [libc::MADV_DONTDUMP, libc::MADV_DONTFORK] {
470 if unsafe { libc::madvise(self.base.as_ptr().cast(), self.len, advice) } != 0 {
472 return Err(last_os("madvise"));
473 }
474 }
475 Ok(())
476 }
477}
478impl Drop for Mapping {
479 fn drop(&mut self) {
480 let _ = unsafe { libc::munmap(self.base.as_ptr().cast(), self.len) };
482 }
483}
484
485fn validate_fd(fd: RawFd, expected_len: usize) -> Result<(), LinuxError> {
486 let mut stat: libc::stat = unsafe { zeroed() };
488 if unsafe { libc::fstat(fd, &mut stat) } != 0 {
489 return Err(last_os("fstat"));
490 }
491 let mut statfs: libc::statfs = unsafe { zeroed() };
492 if unsafe { libc::fstatfs(fd, &mut statfs) } != 0 {
493 return Err(last_os("fstatfs"));
494 }
495 let seals = unsafe { libc::fcntl(fd, libc::F_GET_SEALS) };
497 if seals < 0
498 || seals & REQUIRED_SEALS != REQUIRED_SEALS
499 || stat.st_size != expected_len as libc::off_t
500 || stat.st_nlink != 0
501 || stat.st_mode & libc::S_IFMT != libc::S_IFREG
502 || statfs.f_type != TMPFS_MAGIC
503 {
504 return Err(LinuxError::InvalidCapability);
505 }
506 Ok(())
507}
508
509fn peer_credentials(stream: &UnixStream) -> Result<PeerCredentials, LinuxError> {
510 let mut credentials: libc::ucred = unsafe { zeroed() };
511 let mut length = size_of::<libc::ucred>() as libc::socklen_t;
512 if unsafe {
514 libc::getsockopt(
515 stream.as_raw_fd(),
516 libc::SOL_SOCKET,
517 libc::SO_PEERCRED,
518 (&mut credentials as *mut libc::ucred).cast(),
519 &mut length,
520 )
521 } != 0
522 || length as usize != size_of::<libc::ucred>()
523 {
524 return Err(last_os("getsockopt(SO_PEERCRED)"));
525 }
526 Ok(PeerCredentials {
527 pid: credentials.pid as u32,
528 uid: credentials.uid,
529 gid: credentials.gid,
530 })
531}
532
533fn pidfd_open(pid: u32) -> Result<OwnedFd, LinuxError> {
534 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0) } as libc::c_int;
536 if raw < 0 {
537 return Err(last_os("pidfd_open"));
538 }
539 Ok(unsafe { OwnedFd::from_raw_fd(raw) })
541}
542
543fn send_fd(stream: &UnixStream, nonce: &[u8; 32], len: usize, fd: RawFd) -> Result<(), LinuxError> {
544 let mut frame = [0_u8; FRAME_LEN];
545 frame[..8].copy_from_slice(&FRAME_MAGIC);
546 frame[8..16].copy_from_slice(&(len as u64).to_le_bytes());
547 frame[16..48].copy_from_slice(nonce);
548 let mut iovec = libc::iovec {
549 iov_base: frame.as_mut_ptr().cast(),
550 iov_len: frame.len(),
551 };
552 let control_len = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as u32) } as usize;
553 let mut control = vec![0_u8; control_len];
554 let mut message: libc::msghdr = unsafe { zeroed() };
555 message.msg_iov = &mut iovec;
556 message.msg_iovlen = 1;
557 message.msg_control = control.as_mut_ptr().cast();
558 message.msg_controllen = control.len();
559 unsafe {
561 let header = libc::CMSG_FIRSTHDR(&message);
562 (*header).cmsg_level = libc::SOL_SOCKET;
563 (*header).cmsg_type = libc::SCM_RIGHTS;
564 (*header).cmsg_len = libc::CMSG_LEN(size_of::<RawFd>() as u32) as usize;
565 std::ptr::write(libc::CMSG_DATA(header).cast::<RawFd>(), fd);
566 }
567 let sent = unsafe { libc::sendmsg(stream.as_raw_fd(), &message, libc::MSG_NOSIGNAL) };
569 if sent != FRAME_LEN as isize {
570 return Err(if sent < 0 {
571 last_os("sendmsg")
572 } else {
573 LinuxError::InvalidFrame
574 });
575 }
576 Ok(())
577}
578
579fn receive_fd(
580 stream: &UnixStream,
581 nonce: &[u8; 32],
582 expected_len: usize,
583) -> Result<OwnedFd, LinuxError> {
584 let mut frame = [0_u8; FRAME_LEN];
585 let mut iovec = libc::iovec {
586 iov_base: frame.as_mut_ptr().cast(),
587 iov_len: frame.len(),
588 };
589 let control_len = unsafe { libc::CMSG_SPACE(size_of::<RawFd>() as u32) } as usize;
590 let mut control = vec![0_u8; control_len];
591 let mut message: libc::msghdr = unsafe { zeroed() };
592 message.msg_iov = &mut iovec;
593 message.msg_iovlen = 1;
594 message.msg_control = control.as_mut_ptr().cast();
595 message.msg_controllen = control.len();
596 let received =
598 unsafe { libc::recvmsg(stream.as_raw_fd(), &mut message, libc::MSG_CMSG_CLOEXEC) };
599 if received != FRAME_LEN as isize
600 || message.msg_flags & (libc::MSG_TRUNC | libc::MSG_CTRUNC) != 0
601 || frame[..8] != FRAME_MAGIC
602 || frame[16..48] != *nonce
603 || u64::from_le_bytes(frame[8..16].try_into().expect("fixed range")) != expected_len as u64
604 {
605 return Err(if received < 0 {
606 last_os("recvmsg")
607 } else {
608 LinuxError::InvalidFrame
609 });
610 }
611 let header = unsafe { libc::CMSG_FIRSTHDR(&message) };
613 if header.is_null()
614 || unsafe { (*header).cmsg_level } != libc::SOL_SOCKET
615 || unsafe { (*header).cmsg_type } != libc::SCM_RIGHTS
616 || unsafe { (*header).cmsg_len }
617 != unsafe { libc::CMSG_LEN(size_of::<RawFd>() as u32) } as usize
618 || !unsafe { libc::CMSG_NXTHDR(&message, header) }.is_null()
619 {
620 return Err(LinuxError::InvalidAncillaryData);
621 }
622 let raw = unsafe { std::ptr::read(libc::CMSG_DATA(header).cast::<RawFd>()) };
624 if raw < 0 {
625 return Err(LinuxError::InvalidAncillaryData);
626 }
627 Ok(unsafe { OwnedFd::from_raw_fd(raw) })
629}
630
631fn page_align(size: usize) -> Result<usize, LinuxError> {
632 if size == 0 {
633 return Err(LinuxError::InvalidSize(size));
634 }
635 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
637 if page <= 0 {
638 return Err(last_os("sysconf(_SC_PAGESIZE)"));
639 }
640 let page = page as usize;
641 size.checked_add(page - 1)
642 .map(|value| value & !(page - 1))
643 .filter(|value| *value <= isize::MAX as usize)
644 .ok_or(LinuxError::InvalidSize(size))
645}
646
647fn hex(bytes: &[u8]) -> String {
648 const DIGITS: &[u8; 16] = b"0123456789abcdef";
649 let mut encoded = String::with_capacity(bytes.len() * 2);
650 for byte in bytes {
651 encoded.push(DIGITS[(byte >> 4) as usize] as char);
652 encoded.push(DIGITS[(byte & 0x0f) as usize] as char);
653 }
654 encoded
655}
656
657fn parse_nonce(encoded: &str) -> Result<[u8; 32], LinuxError> {
658 if encoded.len() != 64 {
659 return Err(LinuxError::Bootstrap);
660 }
661 let mut nonce = [0_u8; 32];
662 for (output, pair) in nonce.iter_mut().zip(encoded.as_bytes().chunks_exact(2)) {
663 let high = (pair[0] as char)
664 .to_digit(16)
665 .ok_or(LinuxError::Bootstrap)?;
666 let low = (pair[1] as char)
667 .to_digit(16)
668 .ok_or(LinuxError::Bootstrap)?;
669 *output = ((high << 4) | low) as u8;
670 }
671 Ok(nonce)
672}
673
674fn last_os(operation: &'static str) -> LinuxError {
675 LinuxError::Os {
676 operation,
677 code: io::Error::last_os_error().raw_os_error().unwrap_or(-1),
678 }
679}
680
681pub const fn status() -> BackendStatus {
683 BackendStatus::Available
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689 use native_ipc_core::layout::{
690 AcknowledgementRouteSpec, Endpoint, LayoutError, LayoutLimits, RegionSpec, RoleId,
691 };
692
693 #[test]
694 fn backend_reports_available() {
695 assert_eq!(status(), BackendStatus::Available);
696 }
697
698 #[test]
699 fn peer_credentials_are_kernel_derived() {
700 let (left, right) = UnixStream::pair().unwrap();
701 let expected = peer_credentials(&right).unwrap();
702 let channel = AuthenticatedChannel::new(left, expected, [7; 32]).unwrap();
703 assert_eq!(channel.peer(), expected);
704 }
705
706 #[test]
707 fn invalid_sizes_capabilities_and_peer_identity_fail_exactly() {
708 assert!(matches!(
709 QuiescentRegion::new(0),
710 Err(LinuxError::InvalidSize(0))
711 ));
712 assert!(matches!(
713 QuiescentRegion::new(usize::MAX),
714 Err(LinuxError::InvalidSize(usize::MAX))
715 ));
716
717 let unsealed = QuiescentRegion::new(1).unwrap();
718 assert!(matches!(
719 validate_fd(unsealed.fd.as_raw_fd(), unsealed.len()),
720 Err(LinuxError::InvalidCapability)
721 ));
722
723 let (left, right) = UnixStream::pair().unwrap();
724 let actual = peer_credentials(&right).unwrap();
725 let wrong = PeerCredentials {
726 pid: actual.pid.wrapping_add(1),
727 ..actual
728 };
729 assert!(matches!(
730 AuthenticatedChannel::new(left, wrong, [9; 32]),
731 Err(LinuxError::WrongPeer)
732 ));
733
734 let (left, right) = UnixStream::pair().unwrap();
735 let actual = peer_credentials(&right).unwrap();
736 assert!(matches!(
737 AuthenticatedChannel::new(left, actual, [0; 32]),
738 Err(LinuxError::WrongPeer)
739 ));
740 }
741
742 #[test]
743 fn sealed_capability_transfers_and_binds_payload_path() {
744 let producer = RoleId::new(1).unwrap();
745 let peer = RoleId::new(2).unwrap();
746 let specs = [
747 RegionSpec {
748 role: producer,
749 writer: Endpoint::Initiator,
750 slot_count: 1,
751 payload_bytes: 16,
752 acknowledgement_count: 1,
753 },
754 RegionSpec {
755 role: peer,
756 writer: Endpoint::Responder,
757 slot_count: 1,
758 payload_bytes: 16,
759 acknowledgement_count: 1,
760 },
761 ];
762 let routes = [
763 AcknowledgementRouteSpec {
764 owner: peer,
765 target: producer,
766 slot_index: 0,
767 cell_index: 0,
768 },
769 AcknowledgementRouteSpec {
770 owner: producer,
771 target: peer,
772 slot_index: 0,
773 cell_index: 0,
774 },
775 ];
776 let topology = RegionSetLayout::calculate(
777 [5; 32],
778 13,
779 &specs,
780 &routes,
781 LayoutLimits {
782 maximum_mapping_size: 1 << 20,
783 maximum_slot_count: 2,
784 maximum_acknowledgement_count: 2,
785 maximum_payload_bytes: 64,
786 },
787 )
788 .unwrap();
789 let layout = topology.region(producer).unwrap();
790 let mut owner = QuiescentRegion::new(layout.total_size() as usize).unwrap();
791 layout.encode_into(owner.as_bytes_mut()).unwrap();
792 let expected = ValidationExpectations {
793 schema_id: [5; 32],
794 generation: 13,
795 role: producer,
796 writer: Endpoint::Initiator,
797 maximum_mapping_size: owner.len() as u64,
798 };
799 let prepared = owner.prepare_writer(expected, topology.clone()).unwrap();
800 let capability = prepared.export_reader().unwrap();
801
802 let denied = unsafe {
804 libc::mmap(
805 std::ptr::null_mut(),
806 capability.len,
807 libc::PROT_READ | libc::PROT_WRITE,
808 libc::MAP_SHARED,
809 capability.fd.as_raw_fd(),
810 0,
811 )
812 };
813 assert_eq!(denied, libc::MAP_FAILED);
814 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
815
816 let byte = 0xff_u8;
818 let written =
819 unsafe { libc::pwrite(capability.fd.as_raw_fd(), (&raw const byte).cast(), 1, 0) };
820 assert_eq!(written, -1);
821 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
822 assert_eq!(
823 unsafe {
824 libc::ftruncate(
825 capability.fd.as_raw_fd(),
826 capability.len.saturating_add(4096) as libc::off_t,
827 )
828 },
829 -1
830 );
831 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
832 assert_eq!(unsafe { libc::ftruncate(capability.fd.as_raw_fd(), 1) }, -1);
833 assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM));
834
835 let (left, right) = UnixStream::pair().unwrap();
836 let credentials = peer_credentials(&left).unwrap();
837 let sender = AuthenticatedChannel::new(left, credentials, [8; 32]).unwrap();
838 let receiver = AuthenticatedChannel::new(right, credentials, [8; 32]).unwrap();
839 let reader = std::thread::scope(|scope| {
840 let sent = scope.spawn(|| sender.send(&capability));
841 let received =
842 receiver.receive_reader(prepared.mapping.mapping.len, expected, topology.clone());
843 sent.join().unwrap().unwrap();
844 received.unwrap()
845 });
846 let mut writer = prepared.bind().unwrap();
847 assert_eq!(
848 writer.publish(0, 1, None, &[0xaa; 17]).unwrap_err(),
849 BindingError::Layout(LayoutError::PayloadOutOfBounds {
850 length: 17,
851 capacity: 16,
852 })
853 );
854 writer.publish(0, 1, None, b"linux").unwrap();
855 assert_eq!(reader.copy_payload(0, 1).unwrap(), b"linux");
856 }
857
858 #[test]
859 fn spawned_helper_is_pid_authenticated_and_owned() {
860 let executable = std::env::current_exe().unwrap();
861 let arguments = [
862 OsStr::new("--exact"),
863 OsStr::new("linux::tests::spawned_helper_entry"),
864 OsStr::new("--ignored"),
865 OsStr::new("--nocapture"),
866 ];
867 let session = ChildSession::spawn(executable.as_os_str(), &arguments).unwrap();
868 assert_eq!(session.channel().peer().pid, session.child_id());
869 session.terminate().unwrap();
870 }
871
872 #[test]
873 #[ignore = "spawned only by the owned-child integration test"]
874 fn spawned_helper_entry() {
875 let channel = connect_spawned_helper().unwrap();
876 assert_ne!(channel.peer().pid, 0);
877 std::thread::sleep(Duration::from_secs(30));
878 }
879}