Skip to main content

detcore/syscalls/
namespace.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Deterministic views of kernel namespace metadata.
10
11#[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
43// AUTONOMOUS-BOT-IMPLEMENTED
44// TODO-HUMAN-REVIEW(#877)
45fn 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
53// AUTONOMOUS-BOT-IMPLEMENTED
54// TODO-HUMAN-REVIEW(#877)
55fn 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
69// AUTONOMOUS-BOT-IMPLEMENTED
70// TODO-HUMAN-REVIEW(#877)
71fn 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
103// AUTONOMOUS-BOT-IMPLEMENTED
104// TODO-HUMAN-REVIEW(PR-972): Review proc-fd path alias coverage.
105fn 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    // procfs names descriptors without leading zeros (`name_to_int`).
133    (!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
139/// The bytes of a readlink buffer that a placeholder's link may cover, read
140/// before the kernel writes the link (`restore_link_tail`).
141pub(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    // Also what a pipe or socket link's canonicalization restores, so it is
147    // read for every link, not only a placeholder's.
148    saved_link_prefix(
149        guest,
150        buffer,
151        buffer_len.min(ANONYMOUS_PROC_FD_TARGET_CAPACITY),
152    )
153}
154
155/// The bytes of a readlink buffer that the kernel's answer for an `exe` link
156/// may cover, read before the kernel writes it: as many as a path Linux
157/// renders can have, so that every byte past a shorter substituted target
158/// is restored (`Detcore::substitute_exe_link`).
159fn 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
167/// The first `len` bytes of a readlink buffer, or its readable prefix.
168fn 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    // The readable prefix, a page at a time: a buffer may run into an
183    // unmapped page past the link the kernel writes.
184    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        // SAFETY: `read < saved.len() <= buffer_len`, so the address stays
190        // inside the guest's buffer; nothing is dereferenced here.
191        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
205/// Put back the caller's bytes after a rewritten link of `written` bytes,
206/// where the kernel had written `kernel_len`: Linux writes only the link.
207pub(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            // SAFETY: `written < end <= saved.len() <= buffer_len`, so the
218            // address stays inside the guest's buffer; nothing is dereferenced
219            // here, only written through the guest memory interface.
220            let tail = unsafe { buffer.cast::<u8>().add(written) };
221            guest.memory().write_exact(tail, &saved[written..end])?;
222        }
223    }
224    Ok(())
225}
226
227/// Whether `path` spells a task's descriptor link,
228/// `/proc/<pid>/task/<tid>/fd/N`.
229fn 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
240/// Returns the optional numeric proc subject and the descriptor number.
241pub(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/// Whose descriptor table a descriptor link names.
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub(crate) enum DescriptorTable {
259    /// The process's: `/proc/self/fd/N`, `/proc/<pid>/fd/N` and `/dev/fd/N`
260    /// (a symbolic link to `/proc/self/fd`). procfs reads it from the
261    /// thread-group leader, whose table a thread cloned without `CLONE_FILES`
262    /// does not share.
263    Process,
264    /// The calling thread's own: `/proc/thread-self/fd/N` and
265    /// `/proc/<pid>/task/<tid>/fd/N`.
266    Thread,
267}
268
269/// A descriptor that a descriptor link of the caller names, by number, and
270/// the table it is read from.
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub(crate) struct DescriptorLink {
273    pub(crate) fd: i32,
274    pub(crate) table: DescriptorTable,
275}
276
277/// The descriptor that the absolute guest path `path` names through one of
278/// the caller's descriptor links, and whose table that is:
279/// `/dev/fd/N`, `/proc/self/fd/N` and `/proc/<pid>/fd/N` for the caller's
280/// own pid name the process's table ([`DescriptorTable::Process`]);
281/// `/proc/thread-self/fd/N` and `/proc/<pid>/task/<tid>/fd/N` (or
282/// `/proc/self/task/<tid>/fd/N`) for its own pid and tid name the calling
283/// thread's ([`DescriptorTable::Thread`]). `own` is the caller's (pid, tid)
284/// as the guest's numeric `/proc` spellings name them (Detcore's, see
285/// `Detcore::procfs_caller_ids`), or `None` where Detcore does not know
286/// those names (in a nested PID namespace), which recognizes only the
287/// spellings without numbers. `None` for a spelling with `..`: the kernel walks it through the
288/// directory the link reaches, not lexically. A sibling thread's directory
289/// is not recognized, since that thread may have a descriptor table of its
290/// own.
291pub(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
334/// Whether the absolute guest path `path` names something through a
335/// selector that procfs or `/dev` resolves for whoever looks it up:
336/// `/proc/self`, `/proc/thread-self`, `/proc/mounts` and `/proc/net` (links
337/// into `self`), `/dev/fd`, and `/dev/stdin`, `/dev/stdout` and `/dev/stderr`
338/// (links into `/proc/self/fd`), or anything below them. A descriptor opened
339/// at such a path is the opener's file, so the path cannot stand for that
340/// descriptor in another process or thread. A spelling with `..`, or a
341/// relative one, is treated the same way, since it is not resolved
342/// lexically.
343pub(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
366/// The optional numeric proc subject and descriptor number of a descriptor
367/// link spelled with leading zeros (`/proc/self/fd/03`, `/dev/fd/03`), which
368/// procfs does not have.
369pub(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/// A `/proc` link whose target Detcore decides itself
391/// ([`decided_procfs_link`]).
392#[derive(Debug, PartialEq, Eq)]
393enum DecidedLink {
394    /// The link's target.
395    Target(Vec<u8>),
396    /// The caller's own descriptor link, by a spelling the backend may not
397    /// answer (`/proc/self/task/<tid>/fd/N`): read as `/proc/self/fd/N`.
398    SelfFd(i32),
399    /// A spelling that follows a decided link (a trailing slash or `.`) and
400    /// so names no link: `EINVAL` where the target is a directory, `ENOTDIR`
401    /// where it is a file.
402    Followed(Errno),
403}
404
405/// The number of bytes `readlink` may copy for a `bufsiz` argument of
406/// `buffer_len`, which Linux takes as an `int`: EINVAL unless positive.
407pub(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
415/// The link at the absolute spelling `path` whose target Detcore decides from
416/// its own model, for a caller whose pid and tid, as the guest sees them, are
417/// `pid` and `tid`: `self` names the pid and `thread-self` the pid's task
418/// `tid`; the `mounts` and `net` links name `self/mounts` and `self/net`; the
419/// caller's namespace links name their canonical constants; and the caller's
420/// own descriptor link spelled through its thread is its `fd/N`. A trailing
421/// slash or `.` follows the link, so such a spelling of a decided target is
422/// [`DecidedLink::Followed`]. The caller's own process directory by number,
423/// its `task` directory and its own thread's directory are no links, so
424/// EINVAL by any of these spellings. `None` for any other path, for a
425/// spelling with `..`, and for a followed descriptor link, which the kernel
426/// resolves.
427fn 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        // `self`, `thread-self` and `net` name directories; `mounts` and the
443        // namespace links name files.
444        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            // A directory is one followed or not.
453            followed @ DecidedLink::Followed(_) => Some(followed),
454            DecidedLink::SelfFd(_) => None,
455        };
456    }
457    decided_procfs_link_parts(&parts, pid, tid)
458}
459
460/// [`decided_procfs_link`] of the components below `/proc` of a spelling
461/// that does not follow its last one.
462fn 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        // The caller's own process directory by number, its `task`
492        // directory, and its own thread's directory: directories, not links,
493        // so `readlink` fails with EINVAL, as on Linux. Decided here so that
494        // no backend's `/proc` answers for them: the KVM executor's is the
495        // supervisor's, where the number names another task or none.
496        [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
505/// The procfs directory of guest thread `tid` as the tool reaches it: its
506/// own `/proc/thread-self` where the tool runs in the guest's process
507/// (`tool_shares_guest_descriptor_table`), `/proc/<tid>` where it is a
508/// separate tracer. `None` where guest threads are not host tasks
509/// ([`crate::procfs_inode::tool_can_name_guest_descriptors`]: KVM), after a
510/// guest `chroot` (`changed_root`), and where a tracer's guest has a mount
511/// namespace of its own: the tool would look paths up in another
512/// filesystem view than the guest's.
513pub(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    // Raw syscalls: in-guest Detcore must not call libc's wrappers
526    // (`crate::util::raw_syscall`).
527    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
531/// Whether the guest's pathname `path` may name an `exe` link Detcore
532/// could not resolve: its final component is `exe`, or it is empty (a
533/// descriptor for the link itself). The launcher guards look only at these
534/// (`Detcore::refuse_launcher_link`, `Detcore::opened_the_launcher`), so
535/// that a guest's own link or open of any other file is never refused.
536pub(crate) fn exe_link_candidate(path: &Path) -> bool {
537    path.as_os_str().is_empty() || path.file_name() == Some(OsStr::new("exe"))
538}
539
540/// Put back the first `written` bytes of a readlink buffer from `saved`,
541/// read before the kernel wrote it, as far as `saved` reaches.
542fn 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/// Whose `exe` link a `/proc` spelling names ([`exe_link_subject`]).
557#[derive(Clone, Copy, Debug, PartialEq, Eq)]
558pub(crate) enum ExeLinkSubject {
559    /// The caller's own process: `self`, `thread-self`, its pid, or a thread
560    /// in its `task` directory.
561    Own,
562    /// Another process, by the pid the guest sees.
563    Other(i32),
564}
565
566/// Whose `exe` link the absolute, already resolved spelling `path` names
567/// (callers resolve its directory first, `Detcore::resolve_exe_subject`),
568/// for a caller whose pid, as the guest sees it, is `pid`: `/proc/<p>/exe` and
569/// `/proc/<p>/task/<t>/exe` name process `p` (`self` and `thread-self` the
570/// caller's). Linux reads a thread's link from its process's image, and
571/// answers a thread outside `p` with `ENOENT`, which the kernel decides
572/// here too. `None` for any other path, for a spelling with `..`, and for
573/// one that follows the link (a trailing slash or `.`).
574pub(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        // procfs names tasks without leading zeros.
590        (!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// TODO-HUMAN-REVIEW(PR-1079): Review numeric virtual-self proc-fd path rewriting.
616#[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    /// The inode number the link names. Its device is the kernel's pipe or
628    /// socket filesystem ([`anonymous_object_device`]).
629    raw_inode: u64,
630}
631
632/// The host devices of the kernel's pipe and socket filesystems, on which
633/// every `pipe:[N]` or `socket:[N]` link's inode lives. Each is one
634/// kernel-wide superblock, so a pipe and a socket made here show them. A host
635/// inode is identified by device AND number, and a pipe's `fstat` reports this
636/// device, so the identity a link names must carry it too.
637#[derive(Clone, Copy, Debug, Eq, PartialEq)]
638pub(crate) struct AnonymousObjectDevices {
639    pipe: u64,
640    socket: u64,
641}
642
643/// The [`AnonymousObjectDevices`], read once per process, or why they could
644/// not be read. Detcore reads them when it starts in a process
645/// ([`crate::Detcore`]'s `Tool::new`), before any guest syscall, so a guest's
646/// readlink never depends on whether the tracer can make a pipe or socket at
647/// that moment, and keeps the outcome either way: a later call neither retries
648/// nor panics.
649pub(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
655/// The probe's outcome as Detcore keeps it. Under in-guest LiteInst the probe
656/// runs inside the guest before `main`, so a failure is described without the
657/// C library's errno message (see [`crate::util::describe_io_error`]).
658fn 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    // Raw syscalls (see crate::util::raw_syscall): Detcore::new runs this
668    // inside the guest under in-guest LiteInst.
669    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
715/// The device of a `kind` ("pipe" or "socket") link's inode, from the
716/// devices read at start (`devices`). When they could not be read, this is a
717/// tool error, not an errno: Detcore cannot give the link a faithful identity,
718/// so the run fails as an infrastructure failure through the backend's own
719/// failure handling, rather than a link identity getting an invented device or
720/// a guest getting an error Linux would not return.
721fn 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
738// Long enough for `socket:[` + every decimal u64 inode + `]`.
739/// Room for a whole pipe or socket link, and for the link of a placeholder
740/// for a procfs file Detcore renders (`crate::procfs::placeholder_link_path`).
741const 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
747/// Recognize only a complete kernel pipe/socket symlink target.
748fn 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
768/// Match a raw identity against cached current-process inherited-stdio identities.
769/// Callers exclude ordinary replacement objects before filling this array.
770///
771/// When stdio descriptors alias, the lowest one wins, as in the maps
772/// sanitizer's `stdio_by_raw_inode` table. Detcore gives all three inherited
773/// descriptors the stat of its own stdin, so they alias until the guest
774/// replaces them: a link to that object is a link to fd 0, and must read as
775/// the `pipe:[1000]` a readlink of fd 0 returns.
776fn 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            // Another process's link to a procfs file Detcore renders names
813            // the file, as Linux's does.
814            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        // Every link sends its numbering request, and a stdio match only
842        // chooses the rendering afterwards, as `/proc/*/maps` does: whether
843        // the raw identity matches stdio is host identity, which must not
844        // decide how many inode numbers the call consumes
845        // (<https://github.com/rrnewton/hermit/issues/2897>).
846        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        // The raw link's longer inode digits must not stay behind the
853        // canonical one: Linux writes only the link.
854        restore_link_tail(guest, buffer, target.len(), raw_target.len(), saved)?;
855        Ok(Some(target.len() as i64))
856    }
857
858    /// Canonicalize a pipe/socket link belonging to another virtual process.
859    /// The target descriptor is not in the caller's table, so its raw readlink
860    /// bytes are the only safe identity evidence available here.
861    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    /// The rendered procfs file that the descriptor link `path` (absolute)
1082    /// names a placeholder for, read whole into scratch space.
1083    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    // AUTONOMOUS-BOT-IMPLEMENTED
1125    // TODO-HUMAN-REVIEW(#877)
1126    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    /// [`Self::finish_namespace_readlink`] after the call, which returned
1153    /// `result` over a buffer that held `saved` before it.
1154    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        // A link that filled a buffer too short for a placeholder's link,
1167        // spelled as a task's descriptor link (`task/<tid>/fd/N`) or starting
1168        // as a placeholder's link does: the whole link shows whether it was
1169        // one.
1170        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    /// The calling process's pid as the guest sees it, which a `/proc/<pid>`
1213    /// spelling names: Detcore's pid for the process outside a nested PID
1214    /// namespace ([`Self::procfs_caller_ids`]), the kernel's answer inside
1215    /// one.
1216    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    /// The calling thread's pid and tid as the guest's `/proc` spellings
1227    /// name them (`/proc/<pid>/...`, `/proc/<pid>/task/<tid>/...`): Detcore's
1228    /// pid for the thread's process and its tid. This is the one rule for
1229    /// whether such a spelling is the caller's own, which every check that
1230    /// recognizes one uses (the descriptor-link recording of an open, and
1231    /// [`Self::guest_visible_own_pid`]'s callers, such as
1232    /// `rendered_procfs_at`), so that they cannot disagree.
1233    ///
1234    /// They are the numbers the guest's own `getpid` and `gettid` return, on
1235    /// every backend: measured on ptrace, DBT and KVM for the root process,
1236    /// one of its threads and its first fork child, an injected `getpid` and
1237    /// `gettid` returned exactly these. And they are the numbers procfs, as
1238    /// the guest reaches it, uses for them: ptrace's and KVM's procfs number
1239    /// processes as Detcore does, and DBT, whose kernel numbers them
1240    /// otherwise, spells each guest number with the kernel's before the
1241    /// kernel looks the path up (`Detcore::translates_procfs_numbers`;
1242    /// DBT refuses record and replay, the one mode that would leave its
1243    /// numbers untranslated). So nothing is injected to learn them.
1244    ///
1245    /// `None` in a nested PID namespace, whose numbers are its own; a caller
1246    /// that needs the namespace's pid there asks the kernel. Detcore's pid is
1247    /// the thread's own record of it, as at thread exit: a DBT fork child
1248    /// keeps its parent's tool.
1249    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    /// The calling thread's procfs directory as the tool reaches it
1256    /// ([`guest_task_procfs`]).
1257    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    /// The calling thread's root directory as the tool sees it, read from its
1269    /// procfs directory ([`Self::guest_task_directory`]).
1270    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    /// The absolute spelling of an `exe` link the caller names by `path`
1275    /// from `dirfd`, read through its own procfs directory: a relative path
1276    /// joined to the directory the kernel holds for its working directory
1277    /// or descriptor, which names the process a descriptor on `/proc/self`
1278    /// was opened for (a child that inherited it reads its parent's link).
1279    /// `None` where the directory cannot be read, and for an empty path.
1280    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    /// Whose `exe` link the absolute spelling `spelled` reaches, decided the
1308    /// way the kernel looks it up: its directory resolved in the guest's
1309    /// view, so `self`, `thread-self`, `..` and symbolic links within it name
1310    /// the process they lead to, and with `follow` (an open or an exec, which
1311    /// follow the final link) a symbolic link in the final component followed
1312    /// too. `None` for any other file, and where the tool cannot read the
1313    /// guest's filesystem view ([`Self::guest_task_directory`]).
1314    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        // Where the tool reads the guest's procfs by number rather than
1325        // through its own `thread-self` (`guest_task_procfs`), name the
1326        // guest's directory by its pid: a tracer's own `self` and
1327        // `thread-self` are not the guest's. In-guest tools take the same
1328        // path, where the numbered directory is their own.
1329        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        // The caller's pid as its procfs numbers it: a tracer's view of it,
1353        // Detcore's, or in a nested PID namespace the namespace's, read from
1354        // its own `thread-self` (the tool shares the guest's process there).
1355        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        // As many links as the kernel follows (`MAXSYMLINKS`).
1367        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    /// How Detcore answers an `exe` link the caller names by `path` from
1394    /// `dirfd`: in a run whose launch exec'd a launcher in the guest's place
1395    /// ([`crate::LaunchImage::launcher`]: SaBRe's loader, DynamoRIO's
1396    /// library), where the kernel's link names the launcher instead of the
1397    /// guest's image,
1398    /// `Some(record)`: the image Detcore recorded at the exec of the process
1399    /// the link reaches ([`crate::exec_record`]), `None` when it has none to
1400    /// give, which the caller refuses by name rather than let the kernel name
1401    /// the launcher. `None` outside such a run and for any other path: the
1402    /// kernel's answer stands, as on every backend whose kernel exec'd the
1403    /// guest itself. `follow` as [`Self::resolve_exe_subject`].
1404    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            // Its pids are not Detcore's.
1416            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    /// Refuse, by name, an `exe` link Detcore cannot answer faithfully in a
1429    /// run whose kernel names the launcher (`Self::launched_exe`): without a
1430    /// record, or for an image no longer at its path, whose current name
1431    /// (or " (deleted)") only the kernel's link to the image could give.
1432    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    /// [`Self::refuse_launched_exe`], for `reason`.
1452    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        // Said here too: a launched run's launcher does not print the reason
1460        // the coordinator records.
1461        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    /// Refuse, by name, a readlink of a possible `exe` link at `path`
1470    /// ([`exe_link_candidate`]) whose answer, `result` bytes in `buffer`,
1471    /// is the launcher, in a run that declared one
1472    /// ([`crate::LaunchImage::launcher`]): a link Detcore could not resolve
1473    /// (after a guest `chroot`, through a descriptor for the link itself)
1474    /// whose kernel answer would be the launcher in the guest's image's
1475    /// place. The answer must be the launcher's whole path; one the kernel
1476    /// truncated to the buffer is read again whole (`link`, the guest's
1477    /// lookup) before it counts. The buffer gets back the bytes in `saved`
1478    /// before the refusal, so a run that lets the guest go on sees it
1479    /// untouched. Every other result passes through.
1480    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    /// The whole target of the link the guest names by `link` (its `dirfd`
1518    /// and pathname), read again by the kernel into a buffer that holds any
1519    /// path Linux renders. `None` where it cannot be read so, or the guest
1520    /// stack cannot hold the buffer.
1521    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        // The working directory a relative spelling needs is read before
1528        // the stack scratch is taken: the translation of the call below can
1529        // reach `Detcore::prepare_guest_cwd`, whose reads take that scratch
1530        // too, which KVM refuses with a panic while it is held.
1531        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        // The guest's path, respelled for this injection where it leads below
1547        // a descriptor directory, as the guest's own call is.
1548        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    /// The launcher a launched run declared ([`crate::LaunchImage::launcher`]),
1561    /// canonical.
1562    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    /// Answer a readlink of an `exe` link from `exe` (`Self::launched_exe`)
1568    /// after the kernel answered the guest's call, with `result`: the kernel
1569    /// decides whether the link is there and every error (an exited
1570    /// thread-group leader's `ENOENT`, a permission refusal, an unwritable
1571    /// buffer), and Detcore what it names, the image's path. The bytes the
1572    /// kernel wrote past the shorter target are restored from `saved`, as
1573    /// Linux leaves them.
1574    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    /// The layout ([`crate::exec_record::ElfLayout`]) of the image the
1607    /// calling thread's exec ran, read from the exec record's file and checked
1608    /// to be the recorded inode, with that inode; or why there is none.
1609    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        // Pin the object at the path without opening it for I/O (`O_PATH`, no
1623        // link followed), and read it only once it is the recorded regular
1624        // file: a FIFO or device put at the path since the exec is never
1625        // opened. Raw syscalls throughout: in-guest Detcore must not call
1626        // libc's wrappers (`crate::util::raw_syscall`).
1627        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        // Reopening the pinned descriptor's own procfs link opens the same
1636        // inode, whatever the path names by now.
1637        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    /// Linux's start of the program break for the image the calling thread's
1648    /// exec ran: load_elf_binary's rule (`Self::recorded_image_layout`) at
1649    /// the load bias the process's `AT_PHDR` shows, or why there is none.
1650    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    /// Check Detcore's model of the program break for the image an exec just
1662    /// ran (`Self::modeled_start_brk`) against the kernel's `start_brk`
1663    /// (`/proc/<pid>/stat` field 47), and log the outcome
1664    /// ([`crate::exec_record::BrkCheck`]). A model that differs is a Detcore
1665    /// defect, reported as an error. The model is compared only where Linux's
1666    /// rule gives the kernel's start: not where the kernel randomized the
1667    /// break (`Self::break_randomized`), nor where that is unknown. The
1668    /// personality read is the process's current one. Every launcher Hermit
1669    /// execs a guest through sets `ADDR_NO_RANDOMIZE` before the exec, and a
1670    /// ptrace guest's own exec is checked at its exec stop, before any of the
1671    /// new image's code. The in-guest backends that declare no launcher
1672    /// (LiteInst, in-guest-trap) check only the image they launched: they
1673    /// refuse a guest's exec today. So a guest can only clear the flag before
1674    /// the check (a skipped comparison), not set it after a randomized exec.
1675    /// Should one of them come to follow an exec, its check would run after
1676    /// the new image's constructors, which could set the flag after a
1677    /// randomized exec; that case would need the exec-time personality. DBT
1678    /// and SaBRe, which follow execs, declare a launcher and take the branch
1679    /// below, which compares nothing with the kernel.
1680    ///
1681    /// Where the run declares a launcher, the kernel exec'd the launcher, so
1682    /// the process's auxiliary vector and break are the launcher's: Detcore
1683    /// keeps its own model instead (`Self::launched_brk_model`), with no
1684    /// kernel to compare it with, and answers `brk(2)` from it
1685    /// (`Self::handle_brk`). That needs Detcore's tool, which shares the
1686    /// guest's C library there, never to grow the guest's heap through the
1687    /// kernel's break; its `realpath` and `opendir` calls were removed for that
1688    /// (detcore/clippy.toml). Without a model, a brk is refused by name with
1689    /// the reason (`Detcore::handle_brk`).
1690    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    /// Whether the run declares a launcher ([`crate::LaunchImage::launcher`]):
1734    /// the kernel exec'd it in the guest's place, so the process's auxiliary
1735    /// vector and program break are the launcher's.
1736    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    /// Detcore's model of the program break for the image an exec just ran,
1744    /// where the run declares a launcher (`Self::declares_launcher`):
1745    /// load_elf_binary's rule (`Self::recorded_image_layout`) at the bias of
1746    /// the image's own executable mapping ([`crate::exec_record::load_bias`]),
1747    /// since the process's `AT_PHDR` is the launcher's. Or why there is none.
1748    async fn launched_brk_model<G: Guest<Self>>(
1749        &self,
1750        guest: &mut G,
1751    ) -> Result<crate::memory::BrkModel, &'static str> {
1752        // Detcore's brk transaction (`Self::handle_brk`) spans injected
1753        // calls, which Linux makes atomic under the address space's lock;
1754        // only sequentialized threads keep another thread out of it.
1755        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        // Guest code that ran before Detcore's first callback (a pre-init
1770        // function, an IFUNC resolver) used the kernel's break, the
1771        // launcher's, if it moved it: its heap is there, and a model elsewhere
1772        // would split it.
1773        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    /// Whether the kernel randomized the calling thread's program break at
1807    /// its exec: it does where its process runs without `ADDR_NO_RANDOMIZE`
1808    /// (`/proc/<pid>/personality`) and `kernel.randomize_va_space` is above
1809    /// 1. `None` where either cannot be read.
1810    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    /// The kernel's `start_brk` for the calling thread's process, field 47 of
1832    /// its `/proc/<pid>/stat` ([`crate::exec_record::stat_start_brk`]), read
1833    /// where the tool reaches its procfs.
1834    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        // The command name may be any bytes; the fields after it are ASCII.
1838        crate::exec_record::stat_start_brk(&String::from_utf8_lossy(&stat))
1839    }
1840
1841    /// Detcore's pid for the calling thread's process.
1842    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    /// Project an already-completed own-ID query. Like `guest_visible_own_pid`,
1847    /// ordinary processes use the current thread's shared identity, including
1848    /// a fork child that kept the parent Tool. A nested namespace keeps the
1849    /// answer already delegated to the inner Tool, without a second injection.
1850    ///
1851    /// Detcore is a filter-map: a positive inner result establishes success,
1852    /// not the final guest identity. Preserve unusual nonpositive values;
1853    /// errors never reach this projection. This synchronous step adds no
1854    /// future around the existing syscall delegation.
1855    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    /// Answer `readlink` of a `/proc` link whose target Detcore decides from
1872    /// its own model ([`decided_procfs_link`]), at the guest's absolute
1873    /// spelling `path` (for a guest pathname `spelled`), without asking the
1874    /// backend, so every backend answers alike (KVM's own `/proc` refuses
1875    /// `thread-self` and the namespace links). The caller's own descriptor
1876    /// link spelled through its thread is answered as `/proc/self/fd/N` is,
1877    /// with the kernel reading `/proc/thread-self/fd/N`, the calling thread's
1878    /// own table. Not in a nested PID namespace, where the caller's pid is
1879    /// not Detcore's, nor after a guest `chroot`, where `/proc` need not be
1880    /// procfs, nor for a pathname too long for Linux to look up.
1881    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        // Linux takes the size as an int, and refuses one that is not
1905        // positive before it looks up the path.
1906        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                // Linux gives EFAULT for a buffer it cannot write; a backend's
1918                // write can fail otherwise (ptrace's EIO).
1919                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    /// Answer `readlink` of the caller's own descriptor link `fd` spelled
1931    /// through its thread directory, into a buffer of `size` bytes, as
1932    /// `/proc/self/fd/N` is answered (a rendered procfs file's path, a
1933    /// placeholder's, or the kernel's link canonicalized), but by
1934    /// `/proc/thread-self/fd/N`, which every lookup here reads: a thread
1935    /// made without `CLONE_FILES`, or after `unshare(CLONE_FILES)`, has a
1936    /// table of its own, and the leader's may be gone. KVM's own `/proc`
1937    /// refuses `thread-self`, so there they read `/proc/self/fd/N`.
1938    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        // aarch64 has only `readlinkat`.
1972        #[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        // The stack is released before anything else uses it; a placeholder
1980        // is confirmed through the guest's own pathname, as before.
1981        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    /// The absolute spelling of a `readlink` of `path` from `dirfd` that may
1994    /// be a descriptor link of the caller's to a placeholder directory
1995    /// (`syscalls::files::descriptor_directory`), whose link Detcore answers
1996    /// so that the placeholder's own path never reaches the guest: an
1997    /// absolute path as it is; a name relative to a placeholder descriptor
1998    /// below its directory's spelling; a name relative to the working
1999    /// directory, from the guest's working directory (`Detcore::observed_path`,
2000    /// which the caller prepares, `Detcore::prepare_guest_cwd`: where the tool
2001    /// cannot read it from host procfs, KVM, the guest's own `getcwd` names
2002    /// it, <https://github.com/rrnewton/hermit/issues/4541>). `None` for any
2003    /// other.
2004    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    /// Answer a `readlink` of `path` from `dirfd` if it is a descriptor link
2023    /// of the caller's to a placeholder directory
2024    /// ([`Self::placeholder_link_spelling`],
2025    /// `Detcore::answer_descriptor_directory_link`). Boxed by its callers, so
2026    /// that the spelling it holds does not grow every system call's future.
2027    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    // AUTONOMOUS-BOT-IMPLEMENTED
2042    // TODO-HUMAN-REVIEW(#877)
2043    /// Preserve Linux readlink errors and canonicalize procfs namespace identities.
2044    #[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        // Linux refuses a size that is not positive before it reads the path.
2051        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            // As `readlinkat`, the same lookup: a launcher may answer an exact
2081            // `readlink` of the own link itself (SaBRe's plugin before
2082            // Reverie dropped it), and the kernel must decide existence.
2083            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    /// [`Self::handle_readlink`] of a link Detcore does not answer from an
2119    /// exec record.
2120    #[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    // AUTONOMOUS-BOT-IMPLEMENTED
2197    // TODO-HUMAN-REVIEW(#877)
2198    /// Preserve Linux readlinkat errors and canonicalize absolute procfs namespace identities.
2199    pub async fn handle_readlinkat<G: Guest<Self>>(
2200        &self,
2201        guest: &mut G,
2202        call: syscalls::Readlinkat,
2203    ) -> Result<i64, Error> {
2204        // Linux refuses a size that is not positive before it reads the path.
2205        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    /// [`Self::handle_readlinkat`] of a link Detcore does not answer from an
2265    /// exec record.
2266    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        // A relative name below a directory descriptor is spelled as Linux
2273        // looks it up (`Detcore::procfs_spelling_at`): below a `/proc/self`
2274        // or `/proc/<pid>` directory, the task the directory was opened on
2275        // (ESRCH once that task is gone), and below a descriptor Detcore has
2276        // no recorded path for (one received over `SCM_RIGHTS`), the procfs directory
2277        // the kernel names for it; so a namespace or descriptor link there is
2278        // modeled and canonicalized as the same link spelled absolutely. Any
2279        // other such name stays relative, and the kernel decides.
2280        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        // The directory names a task or a kernel path Detcore knows.
2309        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        // A relative pathname is decided only below a descriptor on `/proc`
2317        // itself, a directory, or one whose task or kernel path Detcore knows
2318        // (a selector's directory records its task only where the open
2319        // followed the selector, not for the link itself, an
2320        // `O_PATH | O_NOFOLLOW` open of `/proc/self`). An empty pathname reads
2321        // the descriptor's own link, which the kernel answers (an inherited
2322        // one keeps its opener's binding).
2323        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        // The caller's own `exe`, `cwd` and `root` links, by a name below a
2330        // descriptor on `/proc` or one of its process directories.
2331        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    /// A failed probe (here, the pipe's descriptor limit) is kept as its kind
2378    /// and errno number, not the C library's message, which `to_string`
2379    /// fetches through strerror_r.
2380    #[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    /// The devices read once are the pipe and socket filesystems' own: a
2392    /// fresh pipe and socket show the same ones, and the two differ.
2393    #[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        // Devices that could not be read at start, as with the process's
2405        // descriptors exhausted, fail the run as a tool error and never
2406        // reach the guest as an errno.
2407        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        // Linux takes the size as an int.
2475        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        // A trailing slash or `.` follows the link: to a directory, EINVAL; to
2481        // a file, ENOTDIR.
2482        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        // The caller's own directories are no links: EINVAL, followed or not.
2493        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        // Left to the kernel: `..`, a followed descriptor link, another
2514        // process's or thread's links, unknown namespaces, and links Detcore
2515        // does not decide yet.
2516        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    /// The caller's own descriptor links, by every spelling procfs and
2602    /// `/dev/fd` give them, name its descriptor, in the process's table or
2603    /// the thread's; another process's or thread's, a spelling through `..`,
2604    /// a relative one, or a name below the link do not.
2605    #[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        // Without the caller's procfs names, only the spellings without
2644        // numbers are its own.
2645        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    /// The paths procfs and `/dev` resolve for whoever looks them up, and
2666    /// the ones they do not.
2667    #[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        // The longest link a placeholder can have: the longest memfd name
2768        // (249 bytes) in `/memfd:<name> (deleted)`.
2769        let longest = format!("/memfd:{} (deleted)", "x".repeat(249));
2770        assert!(longest.len() < ANONYMOUS_PROC_FD_TARGET_CAPACITY);
2771        // Each rendered file's placeholder, with the snapshot it carries,
2772        // fits a memfd name and is read back from its link in a session that
2773        // renders it. A file whose snapshots are all the same carries none
2774        // (`ProcfsFile::snapshot_is_rerendered`).
2775        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        // The same number on another device is another file.
2809        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        // What `setup_stdio` caches: stdin's identity on all three.
2820        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}