1#[cfg(test)]
12#[path = "namespace/own_getters_tests.rs"]
13mod own_getters_tests;
14#[path = "namespace/own_links.rs"]
15mod own_links;
16
17use std::ffi::OsStr;
18use std::os::unix::ffi::OsStrExt;
19use std::path::Component;
20use std::path::Path;
21use std::path::PathBuf;
22use std::sync::Arc;
23
24use reverie::Errno;
25use reverie::Error;
26use reverie::Guest;
27use reverie::Stack;
28use reverie::syscalls;
29use reverie::syscalls::AddrMut;
30use reverie::syscalls::MemoryAccess;
31use reverie::syscalls::PathPtr;
32use reverie::syscalls::ReadAddr;
33use reverie::syscalls::Syscall;
34
35use super::deterministic_stdio_inode;
36use super::deterministic_stdio_inode_for_resource;
37use crate::record_or_replay::RecordOrReplay;
38use crate::tool_global::determinize_inode;
39use crate::tool_local::Detcore;
40use crate::types::DetInode;
41use crate::types::RawInode;
42
43fn is_proc_id(component: &OsStr) -> bool {
46 component == "self"
47 || component == "thread-self"
48 || component.to_str().is_some_and(|value| {
49 !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit())
50 })
51}
52
53fn canonical_namespace_name(name: &OsStr) -> Option<&'static [u8]> {
56 match name.to_str()? {
57 "cgroup" => Some(b"cgroup:[4026531835]"),
58 "ipc" => Some(b"ipc:[4026531839]"),
59 "mnt" => Some(b"mnt:[4026531841]"),
60 "net" => Some(b"net:[4026531840]"),
61 "pid" | "pid_for_children" => Some(b"pid:[4026531836]"),
62 "time" | "time_for_children" => Some(b"time:[4026531834]"),
63 "user" => Some(b"user:[4026531837]"),
64 "uts" => Some(b"uts:[4026531838]"),
65 _ => None,
66 }
67}
68
69fn canonical_namespace_target(path: &Path) -> Option<&'static [u8]> {
72 if !path.is_absolute() {
73 return None;
74 }
75
76 let mut parts = Vec::new();
77 for component in path.components() {
78 match component {
79 Component::RootDir => {}
80 Component::Normal(part) => parts.push(part),
81 Component::CurDir | Component::ParentDir | Component::Prefix(_) => return None,
82 }
83 }
84
85 let namespace = match parts.as_slice() {
86 [proc, subject, ns, namespace] if *proc == "proc" && is_proc_id(subject) && *ns == "ns" => {
87 namespace
88 }
89 [proc, subject, task, tid, ns, namespace]
90 if *proc == "proc"
91 && is_proc_id(subject)
92 && *task == "task"
93 && is_proc_id(tid)
94 && *ns == "ns" =>
95 {
96 namespace
97 }
98 _ => return None,
99 };
100 canonical_namespace_name(namespace)
101}
102
103fn normalized_absolute_parts(path: &Path) -> Option<Vec<&OsStr>> {
106 if !path.is_absolute() {
107 return None;
108 }
109
110 let mut parts = Vec::new();
111 for component in path.components() {
112 match component {
113 Component::RootDir | Component::CurDir => {}
114 Component::Normal(part) => parts.push(part),
115 Component::ParentDir => {
116 parts.pop()?;
117 }
118 Component::Prefix(_) => return None,
119 }
120 }
121 Some(parts)
122}
123
124fn decimal_u32(component: &OsStr) -> Option<u32> {
125 let value = component.to_str()?;
126 (!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
127 .then(|| value.parse().ok())?
128}
129
130pub(crate) fn decimal_fd(component: &OsStr) -> Option<i32> {
131 let value = component.to_str()?;
132 (!value.is_empty()
134 && value.bytes().all(|byte| byte.is_ascii_digit())
135 && (value == "0" || !value.starts_with('0')))
136 .then(|| value.parse().ok())?
137}
138
139pub(crate) fn saved_link_bytes<G: Guest<Detcore<T>>, T: RecordOrReplay>(
142 guest: &G,
143 buffer: Option<AddrMut<'_, libc::c_char>>,
144 buffer_len: usize,
145) -> Option<Vec<u8>> {
146 saved_link_prefix(
149 guest,
150 buffer,
151 buffer_len.min(ANONYMOUS_PROC_FD_TARGET_CAPACITY),
152 )
153}
154
155fn saved_exe_link_bytes<G: Guest<Detcore<T>>, T: RecordOrReplay>(
160 guest: &G,
161 buffer: Option<AddrMut<'_, libc::c_char>>,
162 buffer_len: usize,
163) -> Option<Vec<u8>> {
164 saved_link_prefix(guest, buffer, buffer_len.min(libc::PATH_MAX as usize))
165}
166
167fn saved_link_prefix<G: Guest<Detcore<T>>, T: RecordOrReplay>(
169 guest: &G,
170 buffer: Option<AddrMut<'_, libc::c_char>>,
171 len: usize,
172) -> Option<Vec<u8>> {
173 let buffer = buffer?;
174 let mut saved = vec![0; len];
175 if guest
176 .memory()
177 .read_exact(buffer.cast::<u8>(), &mut saved)
178 .is_ok()
179 {
180 return Some(saved);
181 }
182 const PAGE: usize = 4096;
185 let start = buffer.as_raw();
186 let mut read = 0;
187 while read < saved.len() {
188 let end = (((start + read) / PAGE + 1) * PAGE - start).min(saved.len());
189 let chunk = unsafe { buffer.cast::<u8>().add(read) };
192 if guest
193 .memory()
194 .read_exact(chunk, &mut saved[read..end])
195 .is_err()
196 {
197 break;
198 }
199 read = end;
200 }
201 saved.truncate(read);
202 (read > 0).then_some(saved)
203}
204
205pub(crate) fn restore_link_tail<G: Guest<Detcore<T>>, T: RecordOrReplay>(
208 guest: &mut G,
209 buffer: AddrMut<'_, libc::c_char>,
210 written: usize,
211 kernel_len: usize,
212 saved: Option<&[u8]>,
213) -> Result<(), Error> {
214 if let Some(saved) = saved {
215 let end = kernel_len.min(saved.len());
216 if written < end {
217 let tail = unsafe { buffer.cast::<u8>().add(written) };
221 guest.memory().write_exact(tail, &saved[written..end])?;
222 }
223 }
224 Ok(())
225}
226
227fn names_task_descriptor_link(path: &Path) -> bool {
230 let Some(parts) = normalized_absolute_parts(path) else {
231 return false;
232 };
233 matches!(
234 parts.as_slice(),
235 [proc, _, task, _, fd_dir, fd]
236 if *proc == "proc" && *task == "task" && *fd_dir == "fd" && decimal_fd(fd).is_some()
237 )
238}
239
240pub(crate) fn proc_fd_target(path: &Path) -> Option<(Option<u32>, i32)> {
242 let parts = normalized_absolute_parts(path)?;
243 match parts.as_slice() {
244 [dev, fd_dir, fd] if *dev == "dev" && *fd_dir == "fd" => Some((None, decimal_fd(fd)?)),
245 [proc, subject, fd_dir, fd] if *proc == "proc" && *fd_dir == "fd" => {
246 let subject = match subject.to_str()? {
247 "self" | "thread-self" => None,
248 _ => Some(decimal_u32(subject)?),
249 };
250 Some((subject, decimal_fd(fd)?))
251 }
252 _ => None,
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub(crate) enum DescriptorTable {
259 Process,
264 Thread,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub(crate) struct DescriptorLink {
273 pub(crate) fd: i32,
274 pub(crate) table: DescriptorTable,
275}
276
277pub(crate) fn own_descriptor_link(path: &Path, own: Option<(i32, i32)>) -> Option<DescriptorLink> {
292 if !path.is_absolute() {
293 return None;
294 }
295 let mut parts = Vec::new();
296 for component in path.components() {
297 match component {
298 Component::RootDir | Component::CurDir => {}
299 Component::Normal(part) => parts.push(part),
300 Component::ParentDir | Component::Prefix(_) => return None,
301 }
302 }
303 let own_process = |part: &OsStr| {
304 part == "self" || own.is_some_and(|(pid, _)| part.to_str() == Some(&pid.to_string()))
305 };
306 let own_thread =
307 |part: &OsStr| own.is_some_and(|(_, tid)| part.to_str() == Some(&tid.to_string()));
308 let link = |fd: &OsStr, table| decimal_fd(fd).map(|fd| DescriptorLink { fd, table });
309 match parts.as_slice() {
310 [dev, fd_dir, fd] if *dev == "dev" && *fd_dir == "fd" => link(fd, DescriptorTable::Process),
311 [proc, subject, fd_dir, fd]
312 if *proc == "proc" && *fd_dir == "fd" && *subject == "thread-self" =>
313 {
314 link(fd, DescriptorTable::Thread)
315 }
316 [proc, subject, fd_dir, fd]
317 if *proc == "proc" && *fd_dir == "fd" && own_process(subject) =>
318 {
319 link(fd, DescriptorTable::Process)
320 }
321 [proc, subject, task, thread, fd_dir, fd]
322 if *proc == "proc"
323 && *task == "task"
324 && *fd_dir == "fd"
325 && own_process(subject)
326 && own_thread(thread) =>
327 {
328 link(fd, DescriptorTable::Thread)
329 }
330 _ => None,
331 }
332}
333
334pub(crate) fn names_through_caller_selector(path: &Path) -> bool {
344 if !path.is_absolute() {
345 return true;
346 }
347 let mut parts = Vec::new();
348 for component in path.components() {
349 match component {
350 Component::RootDir | Component::CurDir => {}
351 Component::Normal(part) => parts.push(part),
352 Component::ParentDir | Component::Prefix(_) => return true,
353 }
354 }
355 match parts.as_slice() {
356 [proc, selector, ..] if *proc == "proc" => {
357 ["self", "thread-self", "mounts", "net"].contains(&selector.to_str().unwrap_or(""))
358 }
359 [dev, selector, ..] if *dev == "dev" => {
360 ["fd", "stdin", "stdout", "stderr"].contains(&selector.to_str().unwrap_or(""))
361 }
362 _ => false,
363 }
364}
365
366pub(crate) fn proc_fd_target_with_leading_zeros(path: &Path) -> Option<(Option<u32>, i32)> {
370 let parts = normalized_absolute_parts(path)?;
371 let (subject, fd) = match parts.as_slice() {
372 [dev, fd_dir, fd] if *dev == "dev" && *fd_dir == "fd" => (None, fd),
373 [proc, subject, fd_dir, fd] if *proc == "proc" && *fd_dir == "fd" => (Some(subject), fd),
374 _ => return None,
375 };
376 let fd = fd.to_str()?;
377 if fd.len() < 2 || !fd.starts_with('0') || !fd.bytes().all(|byte| byte.is_ascii_digit()) {
378 return None;
379 }
380 let subject = match subject {
381 None => None,
382 Some(subject) => match subject.to_str()? {
383 "self" | "thread-self" => None,
384 _ => Some(decimal_u32(subject)?),
385 },
386 };
387 Some((subject, fd.parse().ok()?))
388}
389
390#[derive(Debug, PartialEq, Eq)]
393enum DecidedLink {
394 Target(Vec<u8>),
396 SelfFd(i32),
399 Followed(Errno),
403}
404
405pub(crate) fn readlink_size(buffer_len: usize) -> Result<usize, Errno> {
408 let size = buffer_len as u32 as i32;
409 if size <= 0 {
410 return Err(Errno::EINVAL);
411 }
412 Ok(size as usize)
413}
414
415fn decided_procfs_link(path: &[u8], pid: i32, tid: i32) -> Option<DecidedLink> {
428 let rest = path.strip_prefix(b"/proc/")?;
429 let mut parts = Vec::new();
430 let mut follows = false;
431 for part in rest.split(|byte| *byte == b'/') {
432 match part {
433 b"" | b"." => follows = !parts.is_empty(),
434 b".." => return None,
435 part => {
436 parts.push(part);
437 follows = false;
438 }
439 }
440 }
441 if follows {
442 return match decided_procfs_link_parts(&parts, pid, tid)? {
445 DecidedLink::Target(_) => Some(DecidedLink::Followed(
446 if matches!(parts.as_slice(), [b"self" | b"thread-self" | b"net"]) {
447 Errno::EINVAL
448 } else {
449 Errno::ENOTDIR
450 },
451 )),
452 followed @ DecidedLink::Followed(_) => Some(followed),
454 DecidedLink::SelfFd(_) => None,
455 };
456 }
457 decided_procfs_link_parts(&parts, pid, tid)
458}
459
460fn decided_procfs_link_parts(parts: &[&[u8]], pid: i32, tid: i32) -> Option<DecidedLink> {
463 let pid_name = pid.to_string();
464 let tid_name = tid.to_string();
465 let own_process = |part: &[u8]| part == b"self" || part == pid_name.as_bytes();
466 let own_thread = |part: &[u8]| part == tid_name.as_bytes();
467 let namespace = |parts: &[&[u8]]| {
468 let spelled = std::iter::once(b"/proc".as_slice())
469 .chain(parts.iter().copied())
470 .collect::<Vec<_>>()
471 .join(b"/".as_slice());
472 canonical_namespace_target(Path::new(OsStr::from_bytes(&spelled)))
473 .map(|target| DecidedLink::Target(target.to_vec()))
474 };
475 match parts {
476 [b"self"] => Some(DecidedLink::Target(pid_name.into_bytes())),
477 [b"thread-self"] => Some(DecidedLink::Target(
478 format!("{pid}/task/{tid}").into_bytes(),
479 )),
480 [b"mounts"] => Some(DecidedLink::Target(b"self/mounts".to_vec())),
481 [b"net"] => Some(DecidedLink::Target(b"self/net".to_vec())),
482 [process, b"ns", _] if own_process(process) || *process == b"thread-self" => {
483 namespace(parts)
484 }
485 [process, b"task", thread, b"ns", _] if own_process(process) && own_thread(thread) => {
486 namespace(parts)
487 }
488 [process, b"task", thread, b"fd", fd] if own_process(process) && own_thread(thread) => {
489 decimal_fd(OsStr::from_bytes(fd)).map(DecidedLink::SelfFd)
490 }
491 [process] if *process == pid_name.as_bytes() => Some(DecidedLink::Followed(Errno::EINVAL)),
497 [process, b"task"] if own_process(process) => Some(DecidedLink::Followed(Errno::EINVAL)),
498 [process, b"task", thread] if own_process(process) && own_thread(thread) => {
499 Some(DecidedLink::Followed(Errno::EINVAL))
500 }
501 _ => None,
502 }
503}
504
505pub(crate) fn guest_task_procfs(
514 backend: &reverie::BackendCapabilities,
515 tid: i32,
516 changed_root: bool,
517) -> Option<PathBuf> {
518 if !crate::procfs_inode::tool_can_name_guest_descriptors(backend) || changed_root {
519 return None;
520 }
521 if backend.tool_shares_guest_descriptor_table {
522 return Some(PathBuf::from("/proc/thread-self"));
523 }
524 let task = PathBuf::from(format!("/proc/{tid}"));
525 let namespace = |path: &Path| crate::util::raw_stat(path).ok().map(|stat| stat.st_ino);
528 (namespace(&task.join("ns/mnt"))? == namespace(Path::new("/proc/self/ns/mnt"))?).then_some(task)
529}
530
531pub(crate) fn exe_link_candidate(path: &Path) -> bool {
537 path.as_os_str().is_empty() || path.file_name() == Some(OsStr::new("exe"))
538}
539
540fn restore_saved_link_bytes<G: Guest<Detcore<T>>, T: RecordOrReplay>(
543 guest: &mut G,
544 buffer: AddrMut<'_, libc::c_char>,
545 written: usize,
546 saved: Option<&[u8]>,
547) {
548 if let Some(saved) = saved {
549 let end = written.min(saved.len());
550 let _ = guest
551 .memory()
552 .write_exact(buffer.cast::<u8>(), &saved[..end]);
553 }
554}
555
556#[derive(Clone, Copy, Debug, PartialEq, Eq)]
558pub(crate) enum ExeLinkSubject {
559 Own,
562 Other(i32),
564}
565
566pub(crate) fn exe_link_subject(path: &[u8], pid: i32) -> Option<ExeLinkSubject> {
575 let rest = path.strip_prefix(b"/proc/")?;
576 if path.ends_with(b"/") || path.ends_with(b"/.") {
577 return None;
578 }
579 let mut parts = Vec::new();
580 for part in rest.split(|byte| *byte == b'/') {
581 match part {
582 b"" | b"." => {}
583 b".." => return None,
584 part => parts.push(part),
585 }
586 }
587 let number = |part: &[u8]| -> Option<i32> {
588 let digits = std::str::from_utf8(part).ok()?;
589 (!digits.starts_with('0') && digits.bytes().all(|byte| byte.is_ascii_digit()))
591 .then(|| digits.parse().ok())?
592 };
593 let process = |part: &[u8]| -> Option<ExeLinkSubject> {
594 if part == b"self" {
595 return Some(ExeLinkSubject::Own);
596 }
597 let process = number(part)?;
598 Some(if process == pid {
599 ExeLinkSubject::Own
600 } else {
601 ExeLinkSubject::Other(process)
602 })
603 };
604 match parts.as_slice() {
605 [b"thread-self", b"exe"] => Some(ExeLinkSubject::Own),
606 [subject, b"exe"] => process(subject),
607 [subject, b"task", thread, b"exe"] => {
608 number(thread)?;
609 process(subject)
610 }
611 _ => None,
612 }
613}
614
615#[cfg(target_arch = "x86_64")]
617fn host_self_proc_fd_alias(path: &Path, current_pid: i64) -> Option<PathBuf> {
618 let (Some(subject), fd) = proc_fd_target(path)? else {
619 return None;
620 };
621 (i64::from(subject) == current_pid).then(|| PathBuf::from(format!("/proc/self/fd/{fd}")))
622}
623
624#[derive(Debug, Eq, PartialEq)]
625struct AnonymousProcFdIdentity {
626 kind: &'static str,
627 raw_inode: u64,
630}
631
632#[derive(Clone, Copy, Debug, Eq, PartialEq)]
638pub(crate) struct AnonymousObjectDevices {
639 pipe: u64,
640 socket: u64,
641}
642
643pub(crate) fn anonymous_object_devices() -> &'static Result<AnonymousObjectDevices, String> {
650 static DEVICES: std::sync::OnceLock<Result<AnonymousObjectDevices, String>> =
651 std::sync::OnceLock::new();
652 DEVICES.get_or_init(|| retained_probe_outcome(probe_anonymous_object_devices()))
653}
654
655fn retained_probe_outcome(
659 probe: std::io::Result<AnonymousObjectDevices>,
660) -> Result<AnonymousObjectDevices, String> {
661 probe.map_err(|error| crate::util::describe_io_error(&error))
662}
663
664fn probe_anonymous_object_devices() -> std::io::Result<AnonymousObjectDevices> {
665 use crate::util::raw_syscall;
666 use crate::util::raw_zero_result;
667 let close = |fds: [i32; 2]| {
670 for fd in fds {
671 unsafe { raw_syscall(libc::SYS_close, [fd as u64, 0, 0, 0, 0, 0]) };
672 }
673 };
674 let device_of = |fd: i32| -> std::io::Result<u64> {
675 let mut metadata: libc::stat = unsafe { std::mem::zeroed() };
676 raw_zero_result(unsafe {
677 raw_syscall(
678 libc::SYS_fstat,
679 [fd as u64, (&raw mut metadata) as u64, 0, 0, 0, 0],
680 )
681 })?;
682 Ok(metadata.st_dev)
683 };
684 let mut pipe = [0_i32; 2];
685 raw_zero_result(unsafe {
686 raw_syscall(
687 libc::SYS_pipe2,
688 [pipe.as_mut_ptr() as u64, libc::O_CLOEXEC as u64, 0, 0, 0, 0],
689 )
690 })?;
691 let pipe_device = device_of(pipe[0]);
692 close(pipe);
693 let mut sockets = [0_i32; 2];
694 raw_zero_result(unsafe {
695 raw_syscall(
696 libc::SYS_socketpair,
697 [
698 libc::AF_UNIX as u64,
699 (libc::SOCK_DGRAM | libc::SOCK_CLOEXEC) as u64,
700 0,
701 sockets.as_mut_ptr() as u64,
702 0,
703 0,
704 ],
705 )
706 })?;
707 let socket_device = device_of(sockets[0]);
708 close(sockets);
709 Ok(AnonymousObjectDevices {
710 pipe: pipe_device?,
711 socket: socket_device?,
712 })
713}
714
715fn anonymous_object_device(
722 devices: &Result<AnonymousObjectDevices, String>,
723 kind: &str,
724) -> Result<u64, Error> {
725 let devices = devices.as_ref().map_err(|error| {
726 Error::Tool(anyhow::anyhow!(
727 "Detcore could not read the pipe and socket filesystem devices when it started \
728 ({error}), so it cannot identify the {kind} a /proc fd link names"
729 ))
730 })?;
731 Ok(match kind {
732 "pipe" => devices.pipe,
733 "socket" => devices.socket,
734 _ => unreachable!("anonymous_proc_fd_identity names only pipes and sockets, not {kind}"),
735 })
736}
737
738const ANONYMOUS_PROC_FD_TARGET_CAPACITY: usize = crate::procfs::PLACEHOLDER_LINK_CAPACITY;
742
743fn needs_anonymous_proc_fd_scratch(buffer_present: bool, buffer_len: usize) -> bool {
744 buffer_present && buffer_len != 0 && buffer_len < ANONYMOUS_PROC_FD_TARGET_CAPACITY
745}
746
747fn anonymous_proc_fd_identity(target: &[u8]) -> Option<AnonymousProcFdIdentity> {
749 for (kind, prefix) in [
750 ("pipe", b"pipe:[".as_slice()),
751 ("socket", b"socket:[".as_slice()),
752 ] {
753 let Some(digits) = target
754 .strip_prefix(prefix)
755 .and_then(|rest| rest.strip_suffix(b"]"))
756 else {
757 continue;
758 };
759 if digits.is_empty() || !digits.iter().all(u8::is_ascii_digit) {
760 continue;
761 }
762 let raw_inode = std::str::from_utf8(digits).ok()?.parse().ok()?;
763 return Some(AnonymousProcFdIdentity { kind, raw_inode });
764 }
765 None
766}
767
768fn deterministic_stdio_inode_for_raw(
777 raw_inode: RawInode,
778 stdio_raw_inodes: &[Option<RawInode>; 3],
779) -> Option<DetInode> {
780 let fd = stdio_raw_inodes
781 .iter()
782 .position(|cached| *cached == Some(raw_inode))?;
783 deterministic_stdio_inode(fd as i32)
784}
785
786fn canonical_anonymous_proc_fd_target(
787 identity: &AnonymousProcFdIdentity,
788 inode: DetInode,
789 buffer_len: usize,
790) -> Vec<u8> {
791 let mut target = format!("{}:[{}]", identity.kind, inode.as_raw()).into_bytes();
792 target.truncate(buffer_len);
793 target
794}
795
796impl<T: RecordOrReplay> Detcore<T> {
797 async fn canonicalize_other_proc_fd_target<G>(
798 &self,
799 guest: &mut G,
800 link: (i32, Option<PathPtr<'_>>),
801 raw_target: &[u8],
802 buffer: Option<AddrMut<'_, libc::c_char>>,
803 buffer_len: usize,
804 saved: Option<&[u8]>,
805 ) -> Result<Option<i64>, Error>
806 where
807 G: Guest<Self>,
808 {
809 if let Some(path) = self.placeholder_link_within_level(guest, raw_target)
810 && self.placeholder_link_confirmed(guest, link).await
811 {
812 let link = std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str());
815 let written = link.len().min(buffer_len);
816 let buffer = buffer.ok_or(Errno::EFAULT)?;
817 guest
818 .memory()
819 .write_exact(buffer.cast(), &link[..written])?;
820 restore_link_tail(guest, buffer, written, raw_target.len(), saved)?;
821 return Ok(Some(written as i64));
822 }
823 let Some(identity) = anonymous_proc_fd_identity(raw_target) else {
824 return Ok(None);
825 };
826 let mut stdio_raw_inodes = [None; 3];
827 for fd in libc::STDIN_FILENO..=libc::STDERR_FILENO {
828 stdio_raw_inodes[fd as usize] = guest
829 .thread_state()
830 .with_detfd(fd, |detfd| {
831 deterministic_stdio_inode_for_resource(fd, detfd.resource())?;
832 detfd.stat().map(|stat| stat.raw_inode())
833 })
834 .ok()
835 .flatten();
836 }
837 let raw_inode = RawInode::new(
838 anonymous_object_device(anonymous_object_devices(), identity.kind)?,
839 identity.raw_inode,
840 );
841 let pooled = determinize_inode(guest, raw_inode).await.0;
847 let inode =
848 deterministic_stdio_inode_for_raw(raw_inode, &stdio_raw_inodes).unwrap_or(pooled);
849 let target = canonical_anonymous_proc_fd_target(&identity, inode, buffer_len);
850 let buffer = buffer.ok_or(Errno::EFAULT)?;
851 guest.memory().write_exact(buffer.cast(), &target)?;
852 restore_link_tail(guest, buffer, target.len(), raw_target.len(), saved)?;
855 Ok(Some(target.len() as i64))
856 }
857
858 async fn canonicalize_other_proc_fd_readlink<G>(
862 &self,
863 guest: &mut G,
864 link: (i32, Option<PathPtr<'_>>),
865 buffer: Option<AddrMut<'_, libc::c_char>>,
866 buffer_len: usize,
867 result: i64,
868 saved: Option<&[u8]>,
869 ) -> Result<i64, Error>
870 where
871 G: Guest<Self>,
872 {
873 let buffer = buffer.expect("a successful readlink requires a non-null buffer");
874 let observed_len = usize::try_from(result)
875 .expect("a positive readlink result must fit usize")
876 .min(buffer_len);
877 let mut observed = vec![0; observed_len];
878 guest.memory().read_exact(buffer.cast(), &mut observed)?;
879 Ok(self
880 .canonicalize_other_proc_fd_target(
881 guest,
882 link,
883 &observed,
884 Some(buffer),
885 buffer_len,
886 saved,
887 )
888 .await?
889 .unwrap_or(result))
890 }
891
892 async fn canonicalize_namespace_readlink_result<G>(
893 &self,
894 guest: &mut G,
895 path: PathBuf,
896 buffer: Option<AddrMut<'_, libc::c_char>>,
897 buffer_len: usize,
898 result: i64,
899 ) -> Result<i64, Error>
900 where
901 G: Guest<Self>,
902 {
903 if result <= 0 {
904 return Ok(result);
905 }
906
907 let target = if let Some(target) = canonical_namespace_target(&path) {
908 target.to_vec()
909 } else if let Some((subject, fd)) = proc_fd_target(&path) {
910 if let Some(subject) = subject {
911 let current_pid = self.guest_visible_own_pid(guest).await?;
912 if current_pid != i64::from(subject) {
913 return self
914 .canonicalize_other_proc_fd_readlink(
915 guest,
916 (libc::AT_FDCWD, None),
917 buffer,
918 buffer_len,
919 result,
920 None,
921 )
922 .await;
923 }
924 }
925
926 let stat = self.inject_fstat(guest, fd).await?;
927 let kind = match stat.st_mode & libc::S_IFMT {
928 libc::S_IFIFO => "pipe",
929 libc::S_IFSOCK => "socket",
930 _ => return Ok(result),
931 };
932 let inode_override = guest
933 .thread_state()
934 .with_detfd(fd, |detfd| {
935 deterministic_stdio_inode_for_resource(fd, detfd.resource())
936 })
937 .ok()
938 .flatten();
939 let inode = match inode_override {
940 Some(inode) => inode,
941 None => {
942 determinize_inode(guest, RawInode::new(stat.st_dev, stat.st_ino))
943 .await
944 .0
945 }
946 };
947 format!("{kind}:[{inode}]").into_bytes()
948 } else {
949 return Ok(result);
950 };
951
952 let written = target.len().min(buffer_len);
953 let buffer = buffer.expect("a successful readlink requires a non-null buffer");
954 guest
955 .memory()
956 .write_exact(buffer.cast(), &target[..written])?;
957 Ok(written as i64)
958 }
959 async fn write_other_proc_fd_target<G>(
960 &self,
961 guest: &mut G,
962 link: (i32, Option<PathPtr<'_>>),
963 raw_target: &[u8],
964 buffer: Option<AddrMut<'_, libc::c_char>>,
965 buffer_len: usize,
966 ) -> Result<i64, Error>
967 where
968 G: Guest<Self>,
969 {
970 if let Some(result) = self
971 .canonicalize_other_proc_fd_target(guest, link, raw_target, buffer, buffer_len, None)
972 .await?
973 {
974 return Ok(result);
975 }
976 let written = raw_target.len().min(buffer_len);
977 let buffer = buffer.ok_or(Errno::EFAULT)?;
978 guest
979 .memory()
980 .write_exact(buffer.cast(), &raw_target[..written])?;
981 Ok(written as i64)
982 }
983
984 #[cfg(target_arch = "x86_64")]
985 async fn finish_other_proc_fd_readlink<G>(
986 &self,
987 guest: &mut G,
988 call: syscalls::Readlink,
989 ) -> Result<i64, Error>
990 where
991 G: Guest<Self>,
992 {
993 let link = (libc::AT_FDCWD, call.path());
994 let buffer = call.buf();
995 let buffer_len = call.bufsize();
996 if !needs_anonymous_proc_fd_scratch(buffer.is_some(), buffer_len) {
997 let saved = saved_link_bytes(guest, buffer, buffer_len);
998 let result = self.record_or_replay(guest, call).await?;
999 if result <= 0 {
1000 return Ok(result);
1001 }
1002 return self
1003 .canonicalize_other_proc_fd_readlink(
1004 guest,
1005 link,
1006 buffer,
1007 buffer_len,
1008 result,
1009 saved.as_deref(),
1010 )
1011 .await;
1012 }
1013
1014 let mut stack = guest.stack().await;
1015 let scratch = stack
1016 .reserve::<[u8; ANONYMOUS_PROC_FD_TARGET_CAPACITY]>()
1017 .cast::<libc::c_char>();
1018 let guard = stack.commit()?;
1019 let physical_call = call
1020 .with_buf(Some(scratch))
1021 .with_bufsize(ANONYMOUS_PROC_FD_TARGET_CAPACITY);
1022 let result = self.record_or_replay(guest, physical_call).await?;
1023 let length = usize::try_from(result)
1024 .expect("a successful readlink result must fit usize")
1025 .min(ANONYMOUS_PROC_FD_TARGET_CAPACITY);
1026 let mut raw_target = vec![0; length];
1027 guest.memory().read_exact(scratch.cast(), &mut raw_target)?;
1028 drop(guard);
1029 self.write_other_proc_fd_target(guest, link, &raw_target, buffer, buffer_len)
1030 .await
1031 }
1032
1033 async fn finish_other_proc_fd_readlinkat<G>(
1034 &self,
1035 guest: &mut G,
1036 call: syscalls::Readlinkat,
1037 ) -> Result<i64, Error>
1038 where
1039 G: Guest<Self>,
1040 {
1041 let link = (call.dirfd(), call.path());
1042 let buffer = call.buf();
1043 let buffer_len = call.buf_len();
1044 if !needs_anonymous_proc_fd_scratch(buffer.is_some(), buffer_len) {
1045 let saved = saved_link_bytes(guest, buffer, buffer_len);
1046 let result = self.record_or_replay(guest, call).await?;
1047 if result <= 0 {
1048 return Ok(result);
1049 }
1050 return self
1051 .canonicalize_other_proc_fd_readlink(
1052 guest,
1053 link,
1054 buffer,
1055 buffer_len,
1056 result,
1057 saved.as_deref(),
1058 )
1059 .await;
1060 }
1061
1062 let mut stack = guest.stack().await;
1063 let scratch = stack
1064 .reserve::<[u8; ANONYMOUS_PROC_FD_TARGET_CAPACITY]>()
1065 .cast::<libc::c_char>();
1066 let guard = stack.commit()?;
1067 let physical_call = call
1068 .with_buf(Some(scratch))
1069 .with_buf_len(ANONYMOUS_PROC_FD_TARGET_CAPACITY);
1070 let result = self.record_or_replay(guest, physical_call).await?;
1071 let length = usize::try_from(result)
1072 .expect("a successful readlinkat result must fit usize")
1073 .min(ANONYMOUS_PROC_FD_TARGET_CAPACITY);
1074 let mut raw_target = vec![0; length];
1075 guest.memory().read_exact(scratch.cast(), &mut raw_target)?;
1076 drop(guard);
1077 self.write_other_proc_fd_target(guest, link, &raw_target, buffer, buffer_len)
1078 .await
1079 }
1080
1081 async fn placeholder_behind_link<G: Guest<Self>>(
1084 &self,
1085 guest: &mut G,
1086 path: &Path,
1087 ) -> Result<Option<&'static Path>, Error> {
1088 const PATH_CAPACITY: usize = 64;
1089 let bytes = path.as_os_str().as_bytes();
1090 if bytes.len() >= PATH_CAPACITY {
1091 return Ok(None);
1092 }
1093 let mut path_buffer = [0_u8; PATH_CAPACITY];
1094 path_buffer[..bytes.len()].copy_from_slice(bytes);
1095 let mut stack = guest.stack().await;
1096 let path_address = stack.push(path_buffer).cast::<libc::c_char>();
1097 let scratch = stack
1098 .reserve::<[u8; ANONYMOUS_PROC_FD_TARGET_CAPACITY]>()
1099 .cast::<libc::c_char>();
1100 let guard = stack.commit()?;
1101 let length = guest
1102 .inject(
1103 syscalls::Readlinkat::new()
1104 .with_dirfd(libc::AT_FDCWD)
1105 .with_path(PathPtr::from_ptr(
1106 path_address.as_raw() as *const libc::c_char
1107 ))
1108 .with_buf(Some(scratch))
1109 .with_buf_len(ANONYMOUS_PROC_FD_TARGET_CAPACITY),
1110 )
1111 .await;
1112 let rendered = match length {
1113 Ok(length) => {
1114 let mut target = vec![0; (length as usize).min(ANONYMOUS_PROC_FD_TARGET_CAPACITY)];
1115 guest.memory().read_exact(scratch.cast(), &mut target)?;
1116 self.placeholder_link_within_level(guest, &target)
1117 }
1118 Err(_) => None,
1119 };
1120 drop(guard);
1121 Ok(rendered)
1122 }
1123
1124 async fn finish_namespace_readlink<G, S>(
1127 &self,
1128 guest: &mut G,
1129 link: (i32, Option<PathPtr<'_>>),
1130 path: PathBuf,
1131 buffer: Option<AddrMut<'_, libc::c_char>>,
1132 buffer_len: usize,
1133 syscall: S,
1134 ) -> Result<i64, Error>
1135 where
1136 G: Guest<Self>,
1137 S: Into<Syscall>,
1138 {
1139 let saved = saved_link_bytes(guest, buffer, buffer_len);
1140 let result = self.record_or_replay(guest, syscall).await?;
1141 self.finish_namespace_readlink_result(
1142 guest,
1143 link,
1144 path,
1145 buffer,
1146 buffer_len,
1147 (result, saved.as_deref()),
1148 )
1149 .await
1150 }
1151
1152 async fn finish_namespace_readlink_result<G>(
1155 &self,
1156 guest: &mut G,
1157 link: (i32, Option<PathPtr<'_>>),
1158 path: PathBuf,
1159 buffer: Option<AddrMut<'_, libc::c_char>>,
1160 buffer_len: usize,
1161 (result, saved): (i64, Option<&[u8]>),
1162 ) -> Result<i64, Error>
1163 where
1164 G: Guest<Self>,
1165 {
1166 let filled_like_a_placeholder = match (buffer, usize::try_from(result)) {
1171 (Some(address), Ok(length)) if length == buffer_len => {
1172 let prefix = b"/memfd:hermit-procfs:";
1173 let mut head = vec![0; length.min(prefix.len())];
1174 guest
1175 .memory()
1176 .read_exact(address.cast::<u8>(), &mut head)
1177 .is_ok()
1178 && prefix.starts_with(&head)
1179 }
1180 _ => false,
1181 };
1182 let result = match usize::try_from(result) {
1183 Ok(length)
1184 if length == buffer_len
1185 && needs_anonymous_proc_fd_scratch(buffer.is_some(), buffer_len)
1186 && !self.cfg.recordreplay_modes
1187 && (names_task_descriptor_link(&path) || filled_like_a_placeholder) =>
1188 {
1189 match self.placeholder_behind_link(guest, &path).await? {
1190 Some(rendered) if self.placeholder_link_confirmed(guest, link).await => {
1191 let link = rendered.as_os_str().as_bytes();
1192 let written = link.len().min(buffer_len);
1193 let address = buffer.ok_or(Errno::EFAULT)?;
1194 guest
1195 .memory()
1196 .write_exact(address.cast(), &link[..written])?;
1197 restore_link_tail(guest, address, written, length, saved)?;
1198 written as i64
1199 }
1200 _ => result,
1201 }
1202 }
1203 _ => {
1204 self.rewrite_placeholder_link(guest, link, buffer, buffer_len, result, saved)
1205 .await?
1206 }
1207 };
1208 self.canonicalize_namespace_readlink_result(guest, path, buffer, buffer_len, result)
1209 .await
1210 }
1211
1212 pub(crate) async fn guest_visible_own_pid<G: Guest<Self>>(
1217 &self,
1218 guest: &mut G,
1219 ) -> Result<i64, Error> {
1220 match self.procfs_caller_ids(guest) {
1221 Some((pid, _)) => Ok(i64::from(pid)),
1222 None => Ok(guest.inject(syscalls::Getpid::new()).await?),
1223 }
1224 }
1225
1226 pub(crate) fn procfs_caller_ids<G: Guest<Self>>(&self, guest: &G) -> Option<(i32, i32)> {
1250 let state = guest.thread_state();
1251 (!state.nested_pid_namespace)
1252 .then(|| (self.own_detpid(guest).as_raw(), state.dettid.as_raw()))
1253 }
1254
1255 pub(crate) fn guest_task_directory<G: Guest<Self>>(&self, guest: &G) -> Option<PathBuf> {
1258 guest_task_procfs(
1259 &self.cfg.backend,
1260 guest.tid().as_raw(),
1261 guest
1262 .thread_state()
1263 .changed_root
1264 .load(std::sync::atomic::Ordering::SeqCst),
1265 )
1266 }
1267
1268 pub(crate) fn guest_root<G: Guest<Self>>(&self, guest: &G) -> Option<PathBuf> {
1271 crate::util::raw_read_link(&self.guest_task_directory(guest)?.join("root")).ok()
1272 }
1273
1274 pub(crate) fn exe_link_spelling<G: Guest<Self>>(
1281 &self,
1282 guest: &G,
1283 dirfd: i32,
1284 path: &Path,
1285 ) -> Option<PathBuf> {
1286 if path.is_absolute() {
1287 return Some(path.to_path_buf());
1288 }
1289 if path.as_os_str().is_empty() {
1290 return None;
1291 }
1292 let task = self.guest_task_directory(guest)?;
1293 let directory = if dirfd == libc::AT_FDCWD {
1294 task.join("cwd")
1295 } else {
1296 task.join("fd").join(dirfd.to_string())
1297 };
1298 let directory = std::fs::read_link(directory).ok()?;
1299 let root = std::fs::read_link(task.join("root")).ok()?;
1300 Some(
1301 Path::new("/")
1302 .join(directory.strip_prefix(&root).ok()?)
1303 .join(path),
1304 )
1305 }
1306
1307 pub(crate) fn resolve_exe_subject<G: Guest<Self>>(
1315 &self,
1316 guest: &G,
1317 spelled: &Path,
1318 follow: bool,
1319 ) -> Option<ExeLinkSubject> {
1320 let root = self.guest_root(guest)?;
1321 let inside = |path: &Path| -> Option<PathBuf> {
1322 Some(Path::new("/").join(path.strip_prefix(&root).ok()?))
1323 };
1324 let numbered_procfs = !self.cfg.backend.tool_shares_guest_descriptor_table;
1330 let spelled = if numbered_procfs {
1331 let (pid, tid) = (guest.pid().as_raw(), guest.tid().as_raw());
1332 let mut parts = spelled.components();
1333 match (parts.next(), parts.next(), parts.next()) {
1334 (Some(Component::RootDir), Some(proc), Some(own)) if proc.as_os_str() == "proc" => {
1335 let own = match own.as_os_str().to_str() {
1336 Some("self") => PathBuf::from(format!("/proc/{pid}")),
1337 Some("thread-self") => PathBuf::from(format!("/proc/{pid}/task/{tid}")),
1338 _ => spelled.to_path_buf(),
1339 };
1340 if own.as_path() == spelled {
1341 own
1342 } else {
1343 own.join(parts.as_path())
1344 }
1345 }
1346 _ => spelled.to_path_buf(),
1347 }
1348 } else {
1349 spelled.to_path_buf()
1350 };
1351 let spelled = spelled.as_path();
1352 let pid = if numbered_procfs {
1356 guest.pid().as_raw()
1357 } else if guest.thread_state().nested_pid_namespace {
1358 let own = crate::util::canonicalize(&root.join("proc/thread-self")).ok()?;
1359 let own = inside(&own)?;
1360 let mut parts = own.components().skip(2);
1361 parts.next()?.as_os_str().to_str()?.parse().ok()?
1362 } else {
1363 self.own_detpid(guest).as_raw()
1364 };
1365 let mut spelled = spelled.to_path_buf();
1366 for _ in 0..40 {
1368 let name = spelled.file_name()?.to_owned();
1369 let parent = spelled.parent()?;
1370 let directory = inside(
1371 &crate::util::canonicalize(&root.join(parent.strip_prefix("/").ok()?)).ok()?,
1372 )?;
1373 let candidate = directory.join(&name);
1374 if let Some(subject) = exe_link_subject(candidate.as_os_str().as_bytes(), pid) {
1375 return Some(subject);
1376 }
1377 if !follow {
1378 return None;
1379 }
1380 let link = root.join(candidate.strip_prefix("/").ok()?);
1381 if !std::fs::symlink_metadata(&link)
1382 .ok()?
1383 .file_type()
1384 .is_symlink()
1385 {
1386 return None;
1387 }
1388 spelled = directory.join(std::fs::read_link(&link).ok()?);
1389 }
1390 None
1391 }
1392
1393 pub(crate) async fn launched_exe<G: Guest<Self>>(
1405 &self,
1406 guest: &mut G,
1407 (dirfd, path): (i32, &Path),
1408 follow: bool,
1409 ) -> Option<Option<Arc<crate::exec_record::ExecRecord>>> {
1410 self.cfg.launch_image.as_ref()?.launcher.as_ref()?;
1411 let spelled = self.exe_link_spelling(guest, dirfd, path)?;
1412 let subject = self.resolve_exe_subject(guest, &spelled, follow)?;
1413 if guest.thread_state().nested_pid_namespace && matches!(subject, ExeLinkSubject::Other(_))
1414 {
1415 return Some(None);
1417 }
1418 Some(match subject {
1419 ExeLinkSubject::Own => guest.thread_state().exe.clone(),
1420 ExeLinkSubject::Other(pid) => {
1421 crate::tool_global::process_image(guest, crate::types::DetPid::from_raw(pid))
1422 .await
1423 .map(Arc::new)
1424 }
1425 })
1426 }
1427
1428 pub(crate) async fn refuse_launched_exe<G: Guest<Self>>(
1433 &self,
1434 guest: &mut G,
1435 sysno: reverie::syscalls::Sysno,
1436 exe: Option<&crate::exec_record::ExecRecord>,
1437 ) -> Result<i64, Error> {
1438 let reason = match exe {
1439 None => "unsupported: a /proc/<pid>/exe link of an image Detcore has no record of, \
1440 where the kernel's link names the launcher"
1441 .to_owned(),
1442 Some(exe) => format!(
1443 "unsupported: a /proc/<pid>/exe link of {:?}, no longer at its path, \
1444 where the kernel's link names the launcher",
1445 exe.exe
1446 ),
1447 };
1448 self.refuse_launched_exe_because(guest, sysno, reason).await
1449 }
1450
1451 pub(crate) async fn refuse_launched_exe_because<G: Guest<Self>>(
1453 &self,
1454 guest: &mut G,
1455 sysno: reverie::syscalls::Sysno,
1456 reason: String,
1457 ) -> Result<i64, Error> {
1458 tracing::error!("[tid {}] {}", guest.tid(), reason);
1459 let _ = std::io::Write::write_fmt(
1462 &mut crate::util::RetryingStderr,
1463 format_args!("hermit: {reason}\n"),
1464 );
1465 self.refuse_unserviceable_operation_because(guest, sysno, Errno::EOPNOTSUPP, &reason)
1466 .await
1467 }
1468
1469 async fn refuse_launcher_link<G: Guest<Self>>(
1481 &self,
1482 guest: &mut G,
1483 (sysno, path, link): (reverie::syscalls::Sysno, &Path, (i32, Option<PathPtr<'_>>)),
1484 (buffer, buffer_len): (Option<AddrMut<'_, libc::c_char>>, usize),
1485 result: i64,
1486 saved: Option<&[u8]>,
1487 ) -> Result<i64, Error> {
1488 let (Some(launcher), Some(buffer), Ok(len)) =
1489 (self.declared_launcher(), buffer, usize::try_from(result))
1490 else {
1491 return Ok(result);
1492 };
1493 if len == 0 || !exe_link_candidate(path) {
1494 return Ok(result);
1495 }
1496 let launcher = launcher.as_os_str().as_bytes();
1497 let truncated = len == buffer_len && len < launcher.len();
1498 if len > launcher.len() || (len < launcher.len() && !truncated) {
1499 return Ok(result);
1500 }
1501 let mut answer = vec![0; len];
1502 if guest
1503 .memory()
1504 .read_exact(buffer.cast::<u8>(), &mut answer)
1505 .is_err()
1506 || !launcher.starts_with(&answer)
1507 {
1508 return Ok(result);
1509 }
1510 if truncated && self.whole_link(guest, link).await.as_deref() != Some(launcher) {
1511 return Ok(result);
1512 }
1513 restore_saved_link_bytes(guest, buffer, len, saved);
1514 self.refuse_launched_exe(guest, sysno, None).await
1515 }
1516
1517 async fn whole_link<G: Guest<Self>>(
1522 &self,
1523 guest: &mut G,
1524 (dirfd, path): (i32, Option<PathPtr<'_>>),
1525 ) -> Option<Vec<u8>> {
1526 const CAPACITY: usize = libc::PATH_MAX as usize;
1527 if let Some(Ok(spelling)) = path.map(|path| path.read(&guest.memory())) {
1532 let spelling: PathBuf = spelling;
1533 Self::prepare_guest_cwd(guest, dirfd, &spelling).await;
1534 }
1535 let mut stack = guest.stack().await;
1536 if stack.capacity() < CAPACITY {
1537 return None;
1538 }
1539 let scratch = stack.reserve::<[u8; CAPACITY]>().cast::<libc::c_char>();
1540 let guard = stack.commit().ok()?;
1541 let call = syscalls::Readlinkat::new()
1542 .with_dirfd(dirfd)
1543 .with_path(path)
1544 .with_buf(Some(scratch))
1545 .with_buf_len(CAPACITY);
1546 let length = usize::try_from(
1549 Self::inject_translated(guest, Syscall::from(call))
1550 .await
1551 .ok()?,
1552 )
1553 .ok()?;
1554 let mut target = vec![0; length.min(CAPACITY)];
1555 let read = guest.memory().read_exact(scratch.cast(), &mut target);
1556 drop(guard);
1557 read.ok().map(|()| target)
1558 }
1559
1560 pub(crate) fn declared_launcher(&self) -> Option<PathBuf> {
1563 let launcher = self.cfg.launch_image.as_ref()?.launcher.as_ref()?;
1564 Some(crate::util::canonicalize(launcher).unwrap_or_else(|_| launcher.clone()))
1565 }
1566
1567 async fn substitute_exe_link<G: Guest<Self>>(
1575 &self,
1576 guest: &mut G,
1577 (sysno, exe): (
1578 reverie::syscalls::Sysno,
1579 Option<Arc<crate::exec_record::ExecRecord>>,
1580 ),
1581 (buffer, buffer_len): (Option<AddrMut<'_, libc::c_char>>, usize),
1582 result: i64,
1583 saved: Option<&[u8]>,
1584 ) -> Result<i64, Error> {
1585 let (Some(buffer), Ok(kernel_len)) = (buffer, usize::try_from(result)) else {
1586 return Ok(result);
1587 };
1588 let present = exe.as_ref().filter(|exe| {
1589 self.guest_root(guest)
1590 .is_some_and(|root| exe.is_present(&root))
1591 });
1592 let Some(exe) = present else {
1593 restore_saved_link_bytes(guest, buffer, kernel_len, saved);
1594 return self.refuse_launched_exe(guest, sysno, exe.as_deref()).await;
1595 };
1596 let target = exe.exe.as_os_str().as_bytes();
1597 let written = target.len().min(readlink_size(buffer_len)?);
1598 guest
1599 .memory()
1600 .write_exact(buffer.cast::<u8>(), &target[..written])
1601 .map_err(|_| Errno::EFAULT)?;
1602 restore_link_tail(guest, buffer, written, kernel_len, saved)?;
1603 Ok(written as i64)
1604 }
1605
1606 fn recorded_image_layout<G: Guest<Self>>(
1610 &self,
1611 guest: &G,
1612 ) -> Result<(crate::exec_record::ElfLayout, u64), &'static str> {
1613 let exe = guest.thread_state().exe.clone().ok_or("no exec record")?;
1614 let root = self
1615 .guest_root(guest)
1616 .ok_or("the guest's root is unreadable")?;
1617 let path = root.join(
1618 exe.exe
1619 .strip_prefix("/")
1620 .map_err(|_| "a relative image path")?,
1621 );
1622 let pinned = crate::util::RawFd::open(&path, libc::O_PATH | libc::O_NOFOLLOW)
1628 .map_err(|_| "the image is unreadable")?;
1629 let stat = pinned.stat().map_err(|_| "the image is unreadable")?;
1630 if stat.st_mode & libc::S_IFMT != libc::S_IFREG
1631 || (stat.st_dev, stat.st_ino) != exe.identity()
1632 {
1633 return Err("the image's path names another file since the exec");
1634 }
1635 let file = crate::util::RawFd::open(
1638 Path::new(&format!("/proc/self/fd/{}", pinned.raw())),
1639 libc::O_RDONLY,
1640 )
1641 .map_err(|_| "the image is unreadable")?;
1642 let layout = crate::exec_record::ElfLayout::read_file(&file)
1643 .ok_or("the image is not an ELF64 executable")?;
1644 Ok((layout, stat.st_ino))
1645 }
1646
1647 fn modeled_start_brk<G: Guest<Self>>(&self, guest: &G) -> Result<u64, &'static str> {
1651 let (layout, _) = self.recorded_image_layout(guest)?;
1652 let auxv = self
1653 .guest_task_directory(guest)
1654 .and_then(|directory| crate::util::raw_read_file(&directory.join("auxv")).ok())
1655 .ok_or("the auxiliary vector is unreadable")?;
1656 let at_phdr = crate::exec_record::auxv_entry(&auxv, libc::AT_PHDR)
1657 .ok_or("the auxiliary vector has no AT_PHDR")?;
1658 layout.start_brk(at_phdr)
1659 }
1660
1661 pub(crate) async fn check_brk_model<G: Guest<Self>>(&self, guest: &mut G) {
1691 use crate::exec_record::BrkCheck;
1692 let dettid = guest.thread_state().dettid;
1693 if self.declares_launcher() {
1694 let model = self.launched_brk_model(guest).await;
1695 match &model {
1696 Ok(model) => tracing::debug!(
1697 "[detcore, dtid {}] program break serviced from a model starting at {:#x}: the run declares a launcher",
1698 dettid,
1699 model.start,
1700 ),
1701 Err(reason) => tracing::warn!(
1702 "[detcore, dtid {}] program break not modeled: {}; a brk is refused by name",
1703 dettid,
1704 reason,
1705 ),
1706 }
1707 let mut metadata = guest
1708 .thread_state()
1709 .memory_metadata
1710 .lock()
1711 .expect("memory metadata mutex poisoned");
1712 metadata.brk_unmodeled = model.as_ref().err().map(|reason| (*reason).to_owned());
1713 metadata.brk_model = model.ok();
1714 return;
1715 }
1716 let check = {
1717 match self.modeled_start_brk(guest) {
1718 Err(reason) => BrkCheck::Unmodeled(reason),
1719 model => BrkCheck::of(
1720 model,
1721 self.break_randomized(guest),
1722 self.kernel_start_brk(guest),
1723 ),
1724 }
1725 };
1726 if let BrkCheck::Differs { .. } = check {
1727 tracing::error!("[detcore, dtid {}] program break {}", dettid, check);
1728 } else {
1729 tracing::debug!("[detcore, dtid {}] program break {}", dettid, check);
1730 }
1731 }
1732
1733 pub(crate) fn declares_launcher(&self) -> bool {
1737 self.cfg
1738 .launch_image
1739 .as_ref()
1740 .is_some_and(|image| image.launcher.is_some())
1741 }
1742
1743 async fn launched_brk_model<G: Guest<Self>>(
1749 &self,
1750 guest: &mut G,
1751 ) -> Result<crate::memory::BrkModel, &'static str> {
1752 if !self.cfg.sequentialize_threads {
1756 return Err("threads are not sequentialized");
1757 }
1758 let exe = guest.thread_state().exe.clone().ok_or("no exec record")?;
1759 let (layout, ino) = self.recorded_image_layout(guest)?;
1760 let maps = self
1761 .guest_task_directory(guest)
1762 .and_then(|directory| crate::util::raw_read_file(&directory.join("maps")).ok())
1763 .ok_or("the mappings are unreadable")?;
1764 let text = layout
1765 .text
1766 .ok_or("an image without an executable segment")?;
1767 let bias = crate::exec_record::load_bias(&maps, &exe.exe, ino, text)?;
1768 let start = layout.start_brk_at_bias(bias)?;
1769 let kernel_start = self
1774 .kernel_start_brk(guest)
1775 .ok_or("the kernel's start of the break is unreadable")?;
1776 let kernel_break = guest
1777 .inject_with_retry(reverie::syscalls::Syscall::Brk(
1778 reverie::syscalls::Brk::new().with_addr(None),
1779 ))
1780 .await
1781 .map_err(|_| "the kernel's break is unreadable")? as u64;
1782 if kernel_break != kernel_start {
1783 return Err("guest code moved the launcher's break before Detcore modeled its own");
1784 }
1785 tracing::debug!(
1786 target: "detcore::brk_diagnostic",
1787 dettid = guest.thread_state().dettid.as_raw(),
1788 image = ?exe.exe,
1789 identity = ?exe.identity(),
1790 image_inode = ino,
1791 layout = ?layout,
1792 bias,
1793 start,
1794 kernel_start,
1795 kernel_break,
1796 "BRK_MODEL_V1",
1797 );
1798 Ok(crate::memory::BrkModel {
1799 start,
1800 current: start,
1801 data_span: layout.data_span,
1802 launcher_break: kernel_start,
1803 })
1804 }
1805
1806 fn break_randomized<G: Guest<Self>>(&self, guest: &G) -> Option<bool> {
1811 let personality =
1812 crate::util::raw_read_file(&self.guest_task_directory(guest)?.join("personality"))
1813 .ok()?;
1814 let personality =
1815 u64::from_str_radix(std::str::from_utf8(&personality).ok()?.trim(), 16).ok()?;
1816 if personality & libc::ADDR_NO_RANDOMIZE as u64 != 0 {
1817 return Some(false);
1818 }
1819 let level =
1820 crate::util::raw_read_file(Path::new("/proc/sys/kernel/randomize_va_space")).ok()?;
1821 Some(
1822 std::str::from_utf8(&level)
1823 .ok()?
1824 .trim()
1825 .parse::<u32>()
1826 .ok()?
1827 > 1,
1828 )
1829 }
1830
1831 fn kernel_start_brk<G: Guest<Self>>(&self, guest: &G) -> Option<u64> {
1835 let stat =
1836 crate::util::raw_read_file(&self.guest_task_directory(guest)?.join("stat")).ok()?;
1837 crate::exec_record::stat_start_brk(&String::from_utf8_lossy(&stat))
1839 }
1840
1841 pub(crate) fn own_detpid<G: Guest<Self>>(&self, guest: &G) -> crate::types::DetPid {
1843 guest.thread_state().detpid.unwrap_or(self.detpid)
1844 }
1845
1846 pub(crate) fn project_own_getter_result<G: Guest<Self>>(
1856 &self,
1857 guest: &G,
1858 call: Syscall,
1859 result: i64,
1860 ) -> i64 {
1861 if result <= 0 || guest.thread_state().nested_pid_namespace {
1862 return result;
1863 }
1864 match call {
1865 Syscall::Getpid(_) => i64::from(self.own_detpid(guest).as_raw()),
1866 Syscall::Gettid(_) => i64::from(guest.thread_state().dettid.as_raw()),
1867 _ => unreachable!("only own-ID getters have a shared result projection"),
1868 }
1869 }
1870
1871 async fn readlink_decided_link<G: Guest<Self>>(
1882 &self,
1883 guest: &mut G,
1884 path: &Path,
1885 (spelled, link): (&Path, (i32, Option<PathPtr<'_>>)),
1886 buffer: Option<AddrMut<'_, libc::c_char>>,
1887 buffer_len: usize,
1888 ) -> Result<Option<i64>, Error> {
1889 let state = guest.thread_state();
1890 if state.nested_pid_namespace
1891 || state.changed_root.load(std::sync::atomic::Ordering::SeqCst)
1892 || spelled.as_os_str().len() >= libc::PATH_MAX as usize
1893 {
1894 return Ok(None);
1895 }
1896 let decided = decided_procfs_link(
1897 path.as_os_str().as_bytes(),
1898 self.own_detpid(guest).as_raw(),
1899 state.dettid.as_raw(),
1900 );
1901 let Some(decided) = decided else {
1902 return Ok(None);
1903 };
1904 let size = readlink_size(buffer_len)?;
1907 if let DecidedLink::SelfFd(fd) = decided {
1908 return self
1909 .readlink_own_descriptor_link(guest, fd, link, buffer, size)
1910 .await
1911 .map(Some);
1912 }
1913 match decided {
1914 DecidedLink::Target(target) => {
1915 let copied = target.len().min(size);
1916 let address = buffer.ok_or(Errno::EFAULT)?;
1917 guest
1920 .memory()
1921 .write_exact(address.cast::<u8>(), &target[..copied])
1922 .map_err(|_| Errno::EFAULT)?;
1923 Ok(Some(copied as i64))
1924 }
1925 DecidedLink::Followed(errno) => Err(errno.into()),
1926 DecidedLink::SelfFd(_) => unreachable!("answered above"),
1927 }
1928 }
1929
1930 async fn readlink_own_descriptor_link<G: Guest<Self>>(
1939 &self,
1940 guest: &mut G,
1941 fd: i32,
1942 link: (i32, Option<PathPtr<'_>>),
1943 buffer: Option<AddrMut<'_, libc::c_char>>,
1944 size: usize,
1945 ) -> Result<i64, Error> {
1946 let directory = if crate::procfs_inode::tool_can_name_guest_descriptors(&self.cfg.backend) {
1947 "thread-self"
1948 } else {
1949 "self"
1950 };
1951 let physical = PathBuf::from(format!("/proc/{directory}/fd/{fd}"));
1952 if let Some(result) = self
1953 .readlink_rendered_procfs(guest, &physical, buffer, size)
1954 .await?
1955 {
1956 return Ok(result);
1957 }
1958 let bytes = physical.as_os_str().as_bytes();
1959 let mut path_buffer = [0_u8; 64];
1960 path_buffer[..bytes.len()].copy_from_slice(bytes);
1961 let saved = saved_link_bytes(guest, buffer, size);
1962 let mut stack = guest.stack().await;
1963 let path_address = stack.push(path_buffer).cast::<libc::c_char>();
1964 let stack_guard = stack.commit()?;
1965 let path = PathPtr::from_ptr(path_address.as_raw() as *const libc::c_char);
1966 #[cfg(target_arch = "x86_64")]
1967 let call = syscalls::Readlink::new()
1968 .with_path(path)
1969 .with_buf(buffer)
1970 .with_bufsize(size);
1971 #[cfg(not(target_arch = "x86_64"))]
1973 let call = syscalls::Readlinkat::new()
1974 .with_dirfd(libc::AT_FDCWD)
1975 .with_path(path)
1976 .with_buf(buffer)
1977 .with_buf_len(size);
1978 let result = self.record_or_replay(guest, call).await?;
1979 drop(stack_guard);
1982 self.finish_namespace_readlink_result(
1983 guest,
1984 link,
1985 physical,
1986 buffer,
1987 size,
1988 (result, saved.as_deref()),
1989 )
1990 .await
1991 }
1992
1993 fn placeholder_link_spelling<G: Guest<Self>>(
2005 &self,
2006 guest: &G,
2007 dirfd: i32,
2008 path: &Path,
2009 ) -> Option<PathBuf> {
2010 if path.is_absolute() {
2011 return Some(path.to_path_buf());
2012 }
2013 if let Some(spelled) = Self::spelling_below_placeholder(guest, dirfd, path) {
2014 return Some(spelled);
2015 }
2016 (dirfd == libc::AT_FDCWD && !path.as_os_str().is_empty())
2017 .then(|| Self::observed_path(guest, dirfd, path).ok())
2018 .flatten()
2019 .filter(|observed| observed.is_absolute())
2020 }
2021
2022 async fn answer_placeholder_link_at<G: Guest<Self>>(
2028 &self,
2029 guest: &mut G,
2030 dirfd: i32,
2031 path: &Path,
2032 buf: Option<reverie::syscalls::AddrMut<'_, libc::c_char>>,
2033 buf_len: usize,
2034 ) -> Option<Result<i64, Error>> {
2035 Self::prepare_guest_cwd(guest, dirfd, path).await;
2036 let spelled = self.placeholder_link_spelling(guest, dirfd, path)?;
2037 self.answer_descriptor_directory_link(guest, &spelled, buf, buf_len)
2038 .await
2039 }
2040
2041 #[cfg(target_arch = "x86_64")]
2045 pub async fn handle_readlink<G: Guest<Self>>(
2046 &self,
2047 guest: &mut G,
2048 call: syscalls::Readlink,
2049 ) -> Result<i64, Error> {
2050 readlink_size(call.bufsize())?;
2052 let path: PathBuf = call.path().ok_or(Errno::EFAULT)?.read(&guest.memory())?;
2053 if let Some(answer) = Box::pin(self.answer_placeholder_link_at(
2054 guest,
2055 libc::AT_FDCWD,
2056 &path,
2057 call.buf(),
2058 call.bufsize(),
2059 ))
2060 .await
2061 {
2062 return answer;
2063 }
2064 if let Some(result) = self
2065 .readlink_own_task_link(
2066 guest,
2067 reverie::syscalls::Sysno::readlink,
2068 &path,
2069 (call.buf(), call.bufsize()),
2070 )
2071 .await?
2072 {
2073 return Ok(result);
2074 }
2075 if let Some(exe) = self
2076 .launched_exe(guest, (libc::AT_FDCWD, &path), false)
2077 .await
2078 {
2079 let saved = saved_exe_link_bytes(guest, call.buf(), call.bufsize());
2080 let kernel = syscalls::Readlinkat::new()
2084 .with_dirfd(libc::AT_FDCWD)
2085 .with_path(call.path())
2086 .with_buf(call.buf())
2087 .with_buf_len(call.bufsize());
2088 let result = self.record_or_replay(guest, kernel).await?;
2089 let buffer = (call.buf(), call.bufsize());
2090 return self
2091 .substitute_exe_link(
2092 guest,
2093 (reverie::syscalls::Sysno::readlink, exe),
2094 buffer,
2095 result,
2096 saved.as_deref(),
2097 )
2098 .await;
2099 }
2100 let saved = self
2101 .declared_launcher()
2102 .and_then(|_| saved_exe_link_bytes(guest, call.buf(), call.bufsize()));
2103 let result = self.readlink_unlaunched(guest, call, path.clone()).await?;
2104 self.refuse_launcher_link(
2105 guest,
2106 (
2107 reverie::syscalls::Sysno::readlink,
2108 &path,
2109 (libc::AT_FDCWD, call.path()),
2110 ),
2111 (call.buf(), call.bufsize()),
2112 result,
2113 saved.as_deref(),
2114 )
2115 .await
2116 }
2117
2118 #[cfg(target_arch = "x86_64")]
2121 async fn readlink_unlaunched<G: Guest<Self>>(
2122 &self,
2123 guest: &mut G,
2124 call: syscalls::Readlink,
2125 path: PathBuf,
2126 ) -> Result<i64, Error> {
2127 if self
2128 .readlink_names_runtime_fd(guest, libc::AT_FDCWD, &path)
2129 .await
2130 {
2131 return Err(Errno::ENOENT.into());
2132 }
2133 if let Some(result) = self
2134 .readlink_rendered_procfs(guest, &path, call.buf(), call.bufsize())
2135 .await?
2136 {
2137 return Ok(result);
2138 }
2139 if let Some(result) = self
2140 .readlink_decided_link(
2141 guest,
2142 &path,
2143 (&path, (libc::AT_FDCWD, call.path())),
2144 call.buf(),
2145 call.bufsize(),
2146 )
2147 .await?
2148 {
2149 return Ok(result);
2150 }
2151 let (host_path, other_proc_fd) = if let Some((Some(subject), _)) = proc_fd_target(&path) {
2152 let current_pid = self.guest_visible_own_pid(guest).await?;
2153 (
2154 host_self_proc_fd_alias(&path, current_pid),
2155 current_pid != i64::from(subject),
2156 )
2157 } else {
2158 (None, false)
2159 };
2160 if let Some(host_path) = host_path {
2161 let bytes = host_path.as_os_str().as_bytes();
2162 let mut path_buffer = [0_u8; 64];
2163 path_buffer[..bytes.len()].copy_from_slice(bytes);
2164 let mut stack = guest.stack().await;
2165 let path_address = stack.push(path_buffer).cast::<libc::c_char>();
2166 let stack_guard = stack.commit()?;
2167 let physical_call = call.with_path(PathPtr::from_ptr(
2168 path_address.as_raw() as *const libc::c_char
2169 ));
2170 let result = self.record_or_replay(guest, physical_call).await?;
2171 drop(stack_guard);
2172 return self
2173 .canonicalize_namespace_readlink_result(
2174 guest,
2175 path,
2176 call.buf(),
2177 call.bufsize(),
2178 result,
2179 )
2180 .await;
2181 }
2182 if other_proc_fd {
2183 return self.finish_other_proc_fd_readlink(guest, call).await;
2184 }
2185 self.finish_namespace_readlink(
2186 guest,
2187 (libc::AT_FDCWD, call.path()),
2188 path,
2189 call.buf(),
2190 call.bufsize(),
2191 call,
2192 )
2193 .await
2194 }
2195
2196 pub async fn handle_readlinkat<G: Guest<Self>>(
2200 &self,
2201 guest: &mut G,
2202 call: syscalls::Readlinkat,
2203 ) -> Result<i64, Error> {
2204 readlink_size(call.buf_len())?;
2206 let path: PathBuf = call.path().ok_or(Errno::EFAULT)?.read(&guest.memory())?;
2207 if let Some(answer) = Box::pin(self.answer_placeholder_link_at(
2208 guest,
2209 call.dirfd(),
2210 &path,
2211 call.buf(),
2212 call.buf_len(),
2213 ))
2214 .await
2215 {
2216 return answer;
2217 }
2218 if path.is_absolute()
2219 && let Some(result) = self
2220 .readlink_own_task_link(
2221 guest,
2222 reverie::syscalls::Sysno::readlinkat,
2223 &path,
2224 (call.buf(), call.buf_len()),
2225 )
2226 .await?
2227 {
2228 return Ok(result);
2229 }
2230 if let Some(exe) = self.launched_exe(guest, (call.dirfd(), &path), false).await {
2231 let saved = saved_exe_link_bytes(guest, call.buf(), call.buf_len());
2232 let result = self.record_or_replay(guest, call).await?;
2233 let buffer = (call.buf(), call.buf_len());
2234 return self
2235 .substitute_exe_link(
2236 guest,
2237 (reverie::syscalls::Sysno::readlinkat, exe),
2238 buffer,
2239 result,
2240 saved.as_deref(),
2241 )
2242 .await;
2243 }
2244 let saved = self
2245 .declared_launcher()
2246 .and_then(|_| saved_exe_link_bytes(guest, call.buf(), call.buf_len()));
2247 let result = self
2248 .readlinkat_unlaunched(guest, call, path.clone())
2249 .await?;
2250 self.refuse_launcher_link(
2251 guest,
2252 (
2253 reverie::syscalls::Sysno::readlinkat,
2254 &path,
2255 (call.dirfd(), call.path()),
2256 ),
2257 (call.buf(), call.buf_len()),
2258 result,
2259 saved.as_deref(),
2260 )
2261 .await
2262 }
2263
2264 async fn readlinkat_unlaunched<G: Guest<Self>>(
2267 &self,
2268 guest: &mut G,
2269 call: syscalls::Readlinkat,
2270 path: PathBuf,
2271 ) -> Result<i64, Error> {
2272 let below_directory =
2281 !path.is_absolute() && !path.as_os_str().is_empty() && call.dirfd() != libc::AT_FDCWD;
2282 let recorded = if path.is_absolute() || call.dirfd() == libc::AT_FDCWD {
2283 None
2284 } else {
2285 guest
2286 .thread_state()
2287 .with_detfd(call.dirfd(), |detfd| detfd.path())
2288 .unwrap_or(None)
2289 };
2290 let lexical = if path.is_absolute() || call.dirfd() == libc::AT_FDCWD {
2291 path.clone()
2292 } else {
2293 recorded
2294 .as_ref()
2295 .map_or(path.clone(), |directory| directory.join(&path))
2296 };
2297 let observed_path = if below_directory {
2298 self.procfs_spelling_at(guest, call.dirfd(), &path).await?
2299 } else {
2300 lexical.clone()
2301 };
2302 if self
2303 .readlink_names_runtime_fd(guest, call.dirfd(), &path)
2304 .await
2305 {
2306 return Err(Errno::ENOENT.into());
2307 }
2308 let named_by_directory = below_directory && observed_path != lexical;
2310 if let Some(result) = self
2311 .readlink_rendered_procfs(guest, &observed_path, call.buf(), call.buf_len())
2312 .await?
2313 {
2314 return Ok(result);
2315 }
2316 let decidable = path.is_absolute()
2324 || named_by_directory
2325 || (below_directory
2326 && recorded
2327 .as_deref()
2328 .is_some_and(|directory| directory == Path::new("/proc")));
2329 if decidable
2332 && !path.is_absolute()
2333 && let Some(result) = self
2334 .readlink_own_task_link(
2335 guest,
2336 reverie::syscalls::Sysno::readlinkat,
2337 &observed_path,
2338 (call.buf(), call.buf_len()),
2339 )
2340 .await?
2341 {
2342 return Ok(result);
2343 }
2344 if decidable
2345 && let Some(result) = self
2346 .readlink_decided_link(
2347 guest,
2348 &observed_path,
2349 (&path, (call.dirfd(), call.path())),
2350 call.buf(),
2351 call.buf_len(),
2352 )
2353 .await?
2354 {
2355 return Ok(result);
2356 }
2357 if let Some((Some(subject), _)) = proc_fd_target(&observed_path) {
2358 let current_pid = self.guest_visible_own_pid(guest).await?;
2359 if current_pid != i64::from(subject) {
2360 return self.finish_other_proc_fd_readlinkat(guest, call).await;
2361 }
2362 }
2363 self.finish_namespace_readlink(
2364 guest,
2365 (call.dirfd(), call.path()),
2366 observed_path,
2367 call.buf(),
2368 call.buf_len(),
2369 call,
2370 )
2371 .await
2372 }
2373}
2374
2375#[cfg(test)]
2376mod tests {
2377 #[test]
2381 fn a_failed_device_probe_is_kept_without_the_c_library_message() {
2382 let failure = || std::io::Error::from_raw_os_error(libc::EMFILE);
2383 assert!(failure().to_string().contains("Too many open files"));
2384 let kept = super::retained_probe_outcome(Err(failure())).unwrap_err();
2385 assert!(kept.ends_with("(errno 24)"), "{kept}");
2386 assert!(!kept.contains("Too many open files"), "{kept}");
2387 }
2388
2389 use super::*;
2390
2391 #[test]
2394 fn anonymous_object_devices_are_the_pipe_and_socket_filesystems() {
2395 let read = anonymous_object_devices();
2396 let devices = read.as_ref().unwrap();
2397 assert_eq!(&probe_anonymous_object_devices().unwrap(), devices);
2398 assert_ne!(devices.pipe, devices.socket);
2399 assert_eq!(anonymous_object_device(read, "pipe").unwrap(), devices.pipe);
2400 assert_eq!(
2401 anonymous_object_device(read, "socket").unwrap(),
2402 devices.socket
2403 );
2404 let unread = Err("Too many open files (os error 24)".to_string());
2408 for kind in ["pipe", "socket"] {
2409 let error = anonymous_object_device(&unread, kind).unwrap_err();
2410 assert!(matches!(error, Error::Tool(_)), "{error:?}");
2411 assert!(error.to_string().contains("os error 24"), "{error}");
2412 }
2413 }
2414
2415 #[test]
2416 fn names_whose_exe_link_a_spelling_reads() {
2417 let subject = |path: &str| exe_link_subject(path.as_bytes(), 3);
2418 for path in [
2419 "/proc/self/exe",
2420 "/proc/thread-self/exe",
2421 "/proc/3/exe",
2422 "/proc//self/./exe",
2423 "/proc/self/task/5/exe",
2424 "/proc/3/task/7/exe",
2425 ] {
2426 assert_eq!(subject(path), Some(ExeLinkSubject::Own), "{path}");
2427 }
2428 assert_eq!(subject("/proc/4/exe"), Some(ExeLinkSubject::Other(4)));
2429 assert_eq!(
2430 subject("/proc/4/task/9/exe"),
2431 Some(ExeLinkSubject::Other(4))
2432 );
2433 for path in [
2434 "/proc/self/exe/",
2435 "/proc/self/exe/.",
2436 "/proc/self/../self/exe",
2437 "/proc/04/exe",
2438 "/proc/self/cwd",
2439 "/proc/thread-self/task/5/exe",
2440 "/proc/self/task/x/exe",
2441 "/proc/exe",
2442 "proc/self/exe",
2443 "/tmp/self/exe",
2444 ] {
2445 assert_eq!(subject(path), None, "{path}");
2446 }
2447 }
2448
2449 #[test]
2450 fn decides_the_links_detcore_knows_the_targets_of() {
2451 let decided = |path: &str| decided_procfs_link(path.as_bytes(), 3, 5);
2452 let target = |bytes: &[u8]| Some(DecidedLink::Target(bytes.to_vec()));
2453 assert_eq!(decided("/proc/self"), target(b"3"));
2454 assert_eq!(decided("/proc/./self"), target(b"3"));
2455 assert_eq!(decided("/proc/thread-self"), target(b"3/task/5"));
2456 assert_eq!(decided("/proc/mounts"), target(b"self/mounts"));
2457 assert_eq!(decided("/proc/net"), target(b"self/net"));
2458 assert_eq!(decided("/proc/self/ns/pid"), target(b"pid:[4026531836]"));
2459 assert_eq!(decided("/proc/3/ns/mnt"), target(b"mnt:[4026531841]"));
2460 assert_eq!(
2461 decided("/proc/thread-self/ns/user"),
2462 target(b"user:[4026531837]")
2463 );
2464 assert_eq!(
2465 decided("/proc/3/task/5/ns/net"),
2466 decided("/proc/self/ns/net")
2467 );
2468 assert_eq!(
2469 decided("/proc/self/task/5/fd/7"),
2470 Some(DecidedLink::SelfFd(7))
2471 );
2472 assert_eq!(decided("/proc/3/task/5/fd/0"), Some(DecidedLink::SelfFd(0)));
2473 assert_eq!(decided("/proc//self"), target(b"3"));
2474 assert_eq!(readlink_size(0), Err(Errno::EINVAL));
2476 assert_eq!(readlink_size(1 << 31), Err(Errno::EINVAL));
2477 assert_eq!(readlink_size((1 << 32) + 5), Ok(5));
2478 assert_eq!(readlink_size(i32::MAX as usize), Ok(i32::MAX as usize));
2479 assert_eq!(decided("/proc/self/ns/./pid"), decided("/proc/self/ns/pid"));
2480 let followed = |errno| Some(DecidedLink::Followed(errno));
2483 for path in [
2484 "/proc/self/",
2485 "/proc/self/.",
2486 "/proc/self//",
2487 "/proc/thread-self/",
2488 "/proc/net/",
2489 ] {
2490 assert_eq!(decided(path), followed(Errno::EINVAL), "{path}");
2491 }
2492 for path in [
2494 "/proc/3",
2495 "/proc/3/",
2496 "/proc/3/.",
2497 "/proc/3/task",
2498 "/proc/self/task",
2499 "/proc/3/task/5",
2500 "/proc/self/task/5/",
2501 ] {
2502 assert_eq!(decided(path), followed(Errno::EINVAL), "{path}");
2503 }
2504 for path in [
2505 "/proc/mounts/",
2506 "/proc/mounts/.",
2507 "/proc/self/ns/pid/",
2508 "/proc/thread-self/ns/pid/.",
2509 "/proc/3/task/5/ns/net/",
2510 ] {
2511 assert_eq!(decided(path), followed(Errno::ENOTDIR), "{path}");
2512 }
2513 for path in [
2517 "/proc/self/task/5/fd/7/",
2518 "/proc/4/",
2519 "/proc/",
2520 "/proc/self/../self",
2521 "/proc/4/ns/pid",
2522 "/proc/self/task/6/ns/pid",
2523 "/proc/self/task/6/fd/7",
2524 "/proc/4/task",
2525 "/proc/3/task/6",
2526 "/proc/5",
2527 "/proc/self/ns/unknown",
2528 "/proc/self/fd/7",
2529 "/proc/self/exe",
2530 "/proc/self/cwd",
2531 "/proc/selfx",
2532 "/tmp/self",
2533 "proc/self",
2534 ] {
2535 assert_eq!(decided(path), None, "{path}");
2536 }
2537 }
2538
2539 #[test]
2540 fn recognizes_process_and_thread_namespace_links() {
2541 assert_eq!(
2542 canonical_namespace_target(Path::new("/proc/self/ns/mnt")),
2543 Some(b"mnt:[4026531841]".as_slice())
2544 );
2545 assert_eq!(
2546 canonical_namespace_target(Path::new("/proc/123/task/456/ns/user")),
2547 Some(b"user:[4026531837]".as_slice())
2548 );
2549 assert_eq!(
2550 canonical_namespace_target(Path::new("/proc/thread-self/ns/pid_for_children")),
2551 Some(b"pid:[4026531836]".as_slice())
2552 );
2553 }
2554
2555 #[test]
2556 fn leaves_non_namespace_and_relative_links_untouched() {
2557 assert_eq!(
2558 canonical_namespace_target(Path::new("/proc/self/exe")),
2559 None
2560 );
2561 assert_eq!(canonical_namespace_target(Path::new("/tmp/ns/mnt")), None);
2562 assert_eq!(
2563 canonical_namespace_target(Path::new("proc/self/ns/mnt")),
2564 None
2565 );
2566 assert_eq!(
2567 canonical_namespace_target(Path::new("/proc/self/ns/unknown")),
2568 None
2569 );
2570 }
2571
2572 #[test]
2573 fn recognizes_proc_fd_aliases_and_lexical_normalization() {
2574 for (path, expected) in [
2575 ("/proc/self/fd/1", (None, 1)),
2576 ("/proc/thread-self/fd/20", (None, 20)),
2577 ("/proc/123/fd/7", (Some(123), 7)),
2578 ("/proc/self/fd/../fd/9", (None, 9)),
2579 ("/dev/fd/3", (None, 3)),
2580 ] {
2581 assert_eq!(proc_fd_target(Path::new(path)), Some(expected), "{path}");
2582 }
2583 }
2584
2585 #[test]
2586 fn rewrites_only_numeric_virtual_self_proc_fd_aliases() {
2587 assert_eq!(
2588 host_self_proc_fd_alias(Path::new("/proc/123/fd/7"), 123),
2589 Some(PathBuf::from("/proc/self/fd/7"))
2590 );
2591 assert_eq!(
2592 host_self_proc_fd_alias(Path::new("/proc/124/fd/7"), 123),
2593 None
2594 );
2595 assert_eq!(
2596 host_self_proc_fd_alias(Path::new("/proc/self/fd/7"), 123),
2597 None
2598 );
2599 }
2600
2601 #[test]
2606 fn own_descriptor_links_name_the_callers_descriptor() {
2607 use DescriptorTable::Process;
2608 use DescriptorTable::Thread;
2609 let own = Some((3, 5));
2610 for (path, fd, table) in [
2611 ("/dev/fd/7", 7, Process),
2612 ("/proc/self/fd/7", 7, Process),
2613 ("/proc/thread-self/fd/7", 7, Thread),
2614 ("/proc/3/fd/0", 0, Process),
2615 ("/proc/3/task/5/fd/12", 12, Thread),
2616 ("/proc/self/task/5/fd/12", 12, Thread),
2617 ("/proc/self/fd/7/", 7, Process),
2618 ("/proc/./self/fd/7", 7, Process),
2619 ("//dev//fd//7", 7, Process),
2620 ("/proc/thread-self/fd/7/.", 7, Thread),
2621 ] {
2622 assert_eq!(
2623 own_descriptor_link(Path::new(path), own),
2624 Some(DescriptorLink { fd, table }),
2625 "{path}"
2626 );
2627 }
2628 for path in [
2629 "/proc/4/fd/7",
2630 "/proc/3/task/6/fd/7",
2631 "/proc/thread-self/task/5/fd/7",
2632 "/proc/self/fd/07",
2633 "/proc/self/fd/7/stat",
2634 "/proc/self/fd",
2635 "/proc/self/../self/fd/7",
2636 "/dev/fd/../fd/7",
2637 "proc/self/fd/7",
2638 "/proc/self/fdinfo/7",
2639 "/dev/fd/x",
2640 ] {
2641 assert_eq!(own_descriptor_link(Path::new(path), own), None, "{path}");
2642 }
2643 let fd = |fd, table| Some(DescriptorLink { fd, table });
2646 assert_eq!(
2647 own_descriptor_link(Path::new("/proc/self/fd/7"), None),
2648 fd(7, Process)
2649 );
2650 assert_eq!(
2651 own_descriptor_link(Path::new("/proc/thread-self/fd/7"), None),
2652 fd(7, Thread)
2653 );
2654 assert_eq!(
2655 own_descriptor_link(Path::new("/dev/fd/7"), None),
2656 fd(7, Process)
2657 );
2658 assert_eq!(own_descriptor_link(Path::new("/proc/3/fd/7"), None), None);
2659 assert_eq!(
2660 own_descriptor_link(Path::new("/proc/self/task/5/fd/7"), None),
2661 None
2662 );
2663 }
2664
2665 #[test]
2668 fn caller_selectors_are_the_ones_procfs_resolves_per_caller() {
2669 for path in [
2670 "/proc/self",
2671 "/proc/self/",
2672 "/proc/self/task",
2673 "/proc/thread-self",
2674 "/proc/thread-self/fd",
2675 "/proc/mounts",
2676 "/proc/net",
2677 "/proc/net/tcp",
2678 "/dev/fd",
2679 "/dev/fd/3",
2680 "/dev/stdin",
2681 "/dev/stdout",
2682 "/dev/stderr",
2683 "//proc/./self",
2684 "/proc/sys/../self",
2685 "/tmp/../proc/stat",
2686 "relative/dir",
2687 ] {
2688 assert!(names_through_caller_selector(Path::new(path)), "{path}");
2689 }
2690 for path in [
2691 "/",
2692 "/proc",
2693 "/proc/",
2694 "/proc/1",
2695 "/proc/12/task/13",
2696 "/proc/stat",
2697 "/proc/sys/kernel",
2698 "/proc/selfish",
2699 "/dev",
2700 "/dev/null",
2701 "/tmp/self",
2702 ] {
2703 assert!(!names_through_caller_selector(Path::new(path)), "{path}");
2704 }
2705 }
2706
2707 #[test]
2708 fn rejects_non_proc_fd_targets() {
2709 for path in [
2710 "/proc/self/fd/",
2711 "/proc/self/fd/stdout",
2712 "/proc/self/fd/1/status",
2713 "/proc/not-a-pid/fd/1",
2714 "/dev/fd/-1",
2715 "proc/self/fd/1",
2716 ] {
2717 assert_eq!(proc_fd_target(Path::new(path)), None, "{path}");
2718 }
2719 }
2720
2721 #[test]
2722 fn recognizes_only_anonymous_pipe_and_socket_targets() {
2723 assert_eq!(
2724 anonymous_proc_fd_identity(b"pipe:[987654321]"),
2725 Some(AnonymousProcFdIdentity {
2726 kind: "pipe",
2727 raw_inode: 987_654_321,
2728 })
2729 );
2730 assert_eq!(
2731 anonymous_proc_fd_identity(b"socket:[42]"),
2732 Some(AnonymousProcFdIdentity {
2733 kind: "socket",
2734 raw_inode: 42,
2735 })
2736 );
2737
2738 for target in [
2739 b"pip".as_slice(),
2740 b"socket:[12345".as_slice(),
2741 b"/tmp/regular-file".as_slice(),
2742 b"anon_inode:[eventpoll]".as_slice(),
2743 b"pipe:[]".as_slice(),
2744 b"pipe:[12]suffix".as_slice(),
2745 b"socket:[not-a-number]".as_slice(),
2746 ] {
2747 assert_eq!(anonymous_proc_fd_identity(target), None, "{target:?}");
2748 }
2749 }
2750
2751 #[test]
2752 fn short_buffers_use_scratch_large_enough_for_every_anonymous_target() {
2753 assert!(!needs_anonymous_proc_fd_scratch(false, 1));
2754 assert!(!needs_anonymous_proc_fd_scratch(true, 0));
2755 assert!(needs_anonymous_proc_fd_scratch(true, 1));
2756 assert!(needs_anonymous_proc_fd_scratch(
2757 true,
2758 ANONYMOUS_PROC_FD_TARGET_CAPACITY - 1
2759 ));
2760 assert!(!needs_anonymous_proc_fd_scratch(
2761 true,
2762 ANONYMOUS_PROC_FD_TARGET_CAPACITY
2763 ));
2764
2765 let maximum = format!("socket:[{}]", u64::MAX);
2766 assert!(maximum.len() < ANONYMOUS_PROC_FD_TARGET_CAPACITY);
2767 let longest = format!("/memfd:{} (deleted)", "x".repeat(249));
2770 assert!(longest.len() < ANONYMOUS_PROC_FD_TARGET_CAPACITY);
2771 let all = crate::procfs::RenderedFiles::ALL;
2776 for path in crate::syscalls::rendered_procfs::rendered_paths(all) {
2777 let path = std::path::Path::new(path.to_str().unwrap());
2778 let rerendered = crate::procfs::ProcfsFile::from_path(path)
2779 .unwrap()
2780 .snapshot_is_rerendered();
2781 let snapshot = match rerendered {
2782 true => Vec::new(),
2783 false => b"00000000-0000-4000-8000-000000000000\n".to_vec(),
2784 };
2785 let name = crate::procfs::placeholder_name(path, &snapshot);
2786 assert!(name.len() <= 249, "{name}");
2787 let placeholder = format!("/memfd:{name} (deleted)");
2788 assert!(crate::procfs::placeholder_link_path(placeholder.as_bytes(), all).is_some());
2789 }
2790 assert_eq!(
2791 anonymous_proc_fd_identity(maximum.as_bytes()),
2792 Some(AnonymousProcFdIdentity {
2793 kind: "socket",
2794 raw_inode: u64::MAX,
2795 })
2796 );
2797 }
2798
2799 #[test]
2800 fn stdio_identity_requires_a_raw_inode_match_and_preserves_alias_precedence() {
2801 let raw = |ino| RawInode::new(9, ino);
2802 let stdio = [Some(raw(11)), Some(raw(22)), Some(raw(33))];
2803 assert_eq!(
2804 deterministic_stdio_inode_for_raw(raw(22), &stdio),
2805 Some(DetInode::mint(1001))
2806 );
2807 assert_eq!(deterministic_stdio_inode_for_raw(raw(44), &stdio), None);
2808 assert_eq!(
2810 deterministic_stdio_inode_for_raw(RawInode::new(10, 22), &stdio),
2811 None
2812 );
2813
2814 let aliased = [None, Some(raw(55)), Some(raw(55))];
2815 assert_eq!(
2816 deterministic_stdio_inode_for_raw(raw(55), &aliased),
2817 Some(DetInode::mint(1001))
2818 );
2819 let inherited = [Some(raw(66)); 3];
2821 assert_eq!(
2822 deterministic_stdio_inode_for_raw(raw(66), &inherited),
2823 Some(DetInode::mint(1000))
2824 );
2825 }
2826
2827 #[test]
2828 fn anonymous_target_rewrite_ignores_raw_inode_width_and_truncates_to_buffer() {
2829 let short_raw = anonymous_proc_fd_identity(b"pipe:[42]").unwrap();
2830 let long_raw = anonymous_proc_fd_identity(b"pipe:[987654321]").unwrap();
2831 let short_rewrite =
2832 canonical_anonymous_proc_fd_target(&short_raw, DetInode::mint(1001), usize::MAX);
2833 let long_rewrite =
2834 canonical_anonymous_proc_fd_target(&long_raw, DetInode::mint(1001), usize::MAX);
2835 assert_eq!(short_rewrite, b"pipe:[1001]");
2836 assert_eq!(long_rewrite, short_rewrite);
2837
2838 assert_eq!(
2839 canonical_anonymous_proc_fd_target(&long_raw, DetInode::mint(1001), 8),
2840 b"pipe:[10"
2841 );
2842 }
2843}