Skip to main content

detcore/syscalls/
chosen_ids.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//! Process and thread IDs that Detcore chooses (`GuestIds::Chosen`).
10//!
11//! Without a PID namespace of its own (`--no-namespace`), a guest would get
12//! the host's next free ID for every task, a different one in every run. With
13//! the capability to choose IDs (`CAP_SYS_ADMIN` or `CAP_CHECKPOINT_RESTORE`
14//! over the host's PID namespace), Detcore gives the guest's first process
15//! the run's base ID and issues every later clone-family call as `clone3`
16//! with a `set_tid` array whose entry for Hermit's PID namespace is the next
17//! ID (after 1 for a new PID namespace's first task, or after the guest's own
18//! entries), so the kernel itself hands out IDs that depend only on the base
19//! and the guest's own order of clone calls. A call Detcore takes an ID
20//! for keeps it whether or not it creates a task, so a failed one leaves a
21//! gap. The kernel
22//! then writes those IDs wherever it writes a task's ID: clone's return value
23//! and `CLONE_PARENT_SETTID`/`CLONE_CHILD_SETTID` words, `getpid` and
24//! `gettid`, `/proc`, wait results and `si_pid`, PI-futex owner words and
25//! robust-list cleanup. Nothing needs translating.
26//!
27//! `clone3` is given exactly the arguments Linux's own `clone`, `fork` and
28//! `vfork` pass to `kernel_clone` (kernel/fork.c), so the kernel validates
29//! the call as it would the guest's, and the task created is the one the
30//! guest's call would have created, with the chosen ID. A call `clone3`
31//! cannot express, one whose `clone3` could fare differently (under a
32//! seccomp filter of the guest's own), or one by a task inside a PID
33//! namespace the guest made, goes to the kernel as the guest made it, and its
34//! task gets a host ID: a run that needs every ID chosen (`--strict`, an
35//! explicit `--pid-base`) then ends before the task exists, and any other run
36//! says its IDs are not deterministic (`GlobalRequest::GaveHostId`).
37
38use reverie::Error;
39use reverie::Guest;
40use reverie::reserved_input::InputInjectionError;
41use reverie::reserved_input::InputUnavailable;
42use reverie::reserved_input::InputWrite;
43use reverie::reserved_input::ReservedInputImage;
44use reverie::syscalls;
45use reverie::syscalls::Addr;
46use reverie::syscalls::AddrMut;
47use reverie::syscalls::Errno;
48use reverie::syscalls::MemoryAccess;
49use reverie::syscalls::Syscall;
50use reverie::syscalls::SyscallArgs;
51use reverie::syscalls::SyscallInfo;
52use reverie::syscalls::Sysno;
53use serde::Deserialize;
54use serde::Serialize;
55use tracing::debug;
56use tracing::warn;
57
58use crate::Detcore;
59use crate::record_or_replay::RecordOrReplay;
60use crate::types::DetTid;
61
62#[cfg(test)]
63mod injection_tests;
64
65/// `struct clone_args` through `cgroup`: `CLONE_ARGS_SIZE_VER2`
66/// (include/uapi/linux/sched.h), the largest size Detcore rewrites. A
67/// longer struct with nonzero bytes past it is `E2BIG` only on kernels that
68/// know no later version, so it takes the host-ID path.
69pub(crate) const CLONE_ARGS_SIZE_VER2: usize = 88;
70/// Through `set_tid_size`, the first size that carries `set_tid`.
71const CLONE_ARGS_SIZE_VER1: usize = 80;
72/// The first version, through `tls`: the smallest size the kernel accepts.
73const CLONE_ARGS_SIZE_VER0: usize = 64;
74const SET_TID_OFFSET: usize = 64;
75
76/// The end of user memory as `clone3_stack_valid`'s `access_ok` sees it with
77/// 4-level page tables (`TASK_SIZE_MAX`). With 5-level page tables a stack
78/// above it is in user memory too; such a stack is then taken as
79/// inexpressible, the conservative side.
80#[cfg(target_arch = "x86_64")]
81const USER_ADDRESS_LIMIT: u64 = (1 << 47) - 4096;
82/// The end of user memory with 48-bit virtual addresses (`TASK_SIZE_64`);
83/// with 52-bit ones a stack above it is taken as inexpressible.
84#[cfg(not(target_arch = "x86_64"))]
85const USER_ADDRESS_LIMIT: u64 = 1 << 48;
86const SET_TID_SIZE_OFFSET: usize = 72;
87
88/// What becomes of a clone-family call when Detcore chooses the IDs.
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub(crate) enum ChosenIdCall {
91    /// Issue `clone3` with these `struct clone_args` bytes, of which the
92    /// first `size` are passed, once `set_tid` is filled in
93    /// ([`ChosenIdCall::with_set_tid`]).
94    Clone3 {
95        args: [u8; CLONE_ARGS_SIZE_VER2],
96        size: usize,
97    },
98    /// Issue the call as the guest made it: every kernel that can choose IDs
99    /// (5.5 and later, for `set_tid`) refuses it before it creates a task, by
100    /// the ABI (`CLONE_PIDFD` beside `CLONE_DETACHED`, incompatible legacy
101    /// thread/signal-sharing flags, a `clone3` struct shorter than its first
102    /// version), so no ID is needed, and `clone3`
103    /// could refuse it with another error.
104    Unchanged,
105    /// The original legacy call fails validation before conversion. Return
106    /// this errno without allocating an ID or issuing a rewritten syscall.
107    Invalid(Errno),
108    /// A guest `clone3` that passes `set_tid` itself, with its `struct
109    /// clone_args` (the first 88 bytes, zero extended) and the size to pass:
110    /// its ID is the guest's choice if the array reaches Hermit's PID
111    /// namespace, else Detcore adds that entry (`Detcore::plan_clone_id`).
112    GuestChosen {
113        args: [u8; CLONE_ARGS_SIZE_VER2],
114        size: usize,
115    },
116    /// The call creates a task, and `clone3` cannot express it; the reason.
117    Inexpressible(String),
118}
119
120impl ChosenIdCall {
121    /// The arguments with `set_tid` pointing at `set_tid_address`, where the
122    /// array of `set_tid_size` IDs is.
123    pub(crate) fn with_set_tid(
124        mut args: [u8; CLONE_ARGS_SIZE_VER2],
125        set_tid_address: u64,
126        set_tid_size: usize,
127    ) -> [u8; CLONE_ARGS_SIZE_VER2] {
128        args[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&set_tid_address.to_ne_bytes());
129        args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8]
130            .copy_from_slice(&(set_tid_size as u64).to_ne_bytes());
131        args
132    }
133}
134
135/// The `clone3` form of the clone-family call `(sysno, args)`. A `clone3`
136/// call's own `struct clone_args` is `clone3_args`, as many bytes as the call
137/// passes, or `None` if they could not be read.
138///
139/// For `clone`, `fork` and `vfork` the fields are the `kernel_clone_args`
140/// those calls build (kernel/fork.c, `SYSCALL_DEFINE5(clone)`, `fork`,
141/// `vfork`), adjusted where `clone3`'s own checks (`clone3_args_valid`,
142/// `copy_clone_args_from_user`) are stricter than what the kernel does with
143/// the field anyway:
144///
145/// - `CLONE_DETACHED`, which `clone3` refuses, is dropped: `copy_process`
146///   ignores it except beside `CLONE_PIDFD`, where it fails the call, which
147///   is then left unchanged.
148/// - The exit signal is 0 beside `CLONE_THREAD` or `CLONE_PARENT`, which
149///   `clone3` requires: `copy_process` then takes -1 or the parent's exit
150///   signal whatever was passed.
151/// - A new stack pointer `sp` becomes `stack = sp - 1, stack_size = 1`:
152///   `clone3_stack_valid` starts the child at `stack + stack_size`.
153///
154/// An exit signal above 64 is inexpressible on kernels that accept it in
155/// legacy `clone`. Beside `CLONE_PARENT`/`CLONE_THREAD`, return EINVAL before
156/// discarding the original signal, matching Linux v7.1.3 `kernel_clone`.
157/// The CLI admits Chosen only after probing this early signal validation;
158/// hosts without it are refused or use the explicit Host-ID fallback.
159/// A `clone3` call is rewritten only when it passes no `set_tid` of
160/// its own and its size is one of the three versions; Detcore then fills in
161/// `set_tid`, extending a first-version struct to the second.
162pub(crate) fn clone3_form(
163    sysno: Sysno,
164    args: &SyscallArgs,
165    clone3_args: Option<&[u8]>,
166) -> ChosenIdCall {
167    let fields = match sysno {
168        #[cfg(not(target_arch = "aarch64"))]
169        Sysno::fork => LegacyClone {
170            flags: 0,
171            exit_signal: libc::SIGCHLD as u64,
172            ..LegacyClone::default()
173        },
174        #[cfg(not(target_arch = "aarch64"))]
175        Sysno::vfork => LegacyClone {
176            flags: (libc::CLONE_VFORK | libc::CLONE_VM) as u64,
177            exit_signal: libc::SIGCHLD as u64,
178            ..LegacyClone::default()
179        },
180        Sysno::clone => LegacyClone::of_clone(args, CloneArgOrder::NATIVE),
181        Sysno::clone3 => return clone3_with_room_for_set_tid(clone3_args),
182        _ => {
183            return ChosenIdCall::Inexpressible(format!(
184                "{sysno} is not a clone-family call Detcore knows"
185            ));
186        }
187    };
188    fields.as_clone3()
189}
190
191/// The `kernel_clone_args` fields a `clone`, `fork` or `vfork` builds.
192#[derive(Clone, Copy, Debug, Default)]
193struct LegacyClone {
194    flags: u64,
195    pidfd: u64,
196    child_tid: u64,
197    parent_tid: u64,
198    exit_signal: u64,
199    stack: u64,
200    tls: u64,
201}
202
203/// The order of `clone`'s last two arguments (kernel/fork.c,
204/// `SYSCALL_DEFINE5(clone)`).
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206enum CloneArgOrder {
207    /// `clone(flags, newsp, parent_tidptr, child_tidptr, tls)`: x86-64.
208    #[cfg_attr(
209        target_arch = "aarch64",
210        allow(
211            dead_code,
212            reason = "the other architecture's order, built for its test"
213        )
214    )]
215    ChildTidThenTls,
216    /// `clone(flags, newsp, parent_tidptr, tls, child_tidptr)`: arm64
217    /// (`CONFIG_CLONE_BACKWARDS`).
218    #[cfg_attr(
219        not(target_arch = "aarch64"),
220        allow(
221            dead_code,
222            reason = "the other architecture's order, built for its test"
223        )
224    )]
225    TlsThenChildTid,
226}
227
228impl CloneArgOrder {
229    #[cfg(target_arch = "aarch64")]
230    const NATIVE: Self = CloneArgOrder::TlsThenChildTid;
231    #[cfg(not(target_arch = "aarch64"))]
232    const NATIVE: Self = CloneArgOrder::ChildTidThenTls;
233}
234
235impl LegacyClone {
236    /// `SYSCALL_DEFINE5(clone)`: the low 32 bits of the flags, less
237    /// `CSIGNAL`, which is the exit signal; the parent TID address serves as
238    /// the pidfd address too; the last two arguments in `order`.
239    fn of_clone(args: &SyscallArgs, order: CloneArgOrder) -> Self {
240        let low = args.arg0 as u32 as u64;
241        let csignal = libc::CSIGNAL as u64;
242        let (child_tid, tls) = match order {
243            CloneArgOrder::ChildTidThenTls => (args.arg3, args.arg4),
244            CloneArgOrder::TlsThenChildTid => (args.arg4, args.arg3),
245        };
246        LegacyClone {
247            flags: low & !csignal,
248            pidfd: args.arg2 as u64,
249            child_tid: child_tid as u64,
250            parent_tid: args.arg2 as u64,
251            exit_signal: low & csignal,
252            stack: args.arg1 as u64,
253            tls: tls as u64,
254        }
255    }
256
257    fn as_clone3(self) -> ChosenIdCall {
258        let shares_parent = (libc::CLONE_THREAD | libc::CLONE_PARENT) as u64;
259        // Linux v7.1.3 kernel/fork.c:2702, kernel_clone, validates the
260        // original signal before copy_process replaces it for these flags:
261        // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/fork.c?id=199c9959d3a9b53f346c221757fc7ac507fbac50#n2702
262        // v6.16 validated only clone3's signal. Chosen-ID admission probes
263        // the required early validation before a guest starts; Namespace/Host
264        // calls bypass this conversion and retain native results.
265        if self.flags & shares_parent != 0 && self.exit_signal > 64 {
266            return ChosenIdCall::Invalid(Errno::EINVAL);
267        }
268        // These legacy-clone combinations cannot create a child. Let the
269        // original kernel call preserve its error precedence instead of
270        // confusing its EINVAL with the added set_tid entry. Linux v6.17
271        // copy_process rejects both before alloc_pid:
272        // https://github.com/torvalds/linux/blob/v6.17/kernel/fork.c#L1945
273        let thread = self.flags & libc::CLONE_THREAD as u64 != 0;
274        let sighand = self.flags & libc::CLONE_SIGHAND as u64 != 0;
275        let vm = self.flags & libc::CLONE_VM as u64 != 0;
276        if (thread && !sighand) || (sighand && !vm) {
277            return ChosenIdCall::Unchanged;
278        }
279        let detached = libc::CLONE_DETACHED as u64;
280        let pidfd = libc::CLONE_PIDFD as u64;
281        // copy_process: CLONE_PIDFD beside CLONE_DETACHED is EINVAL, before
282        // any task exists. (Beside CLONE_THREAD it is EINVAL only before
283        // Linux 6.9; the rewritten call then fails the same way, and the
284        // guest's own call decides.)
285        if self.flags & pidfd != 0 && self.flags & detached != 0 {
286            return ChosenIdCall::Unchanged;
287        }
288        let exit_signal = if self.flags & shares_parent != 0 {
289            0
290        } else {
291            self.exit_signal
292        };
293        if exit_signal > 64 {
294            return ChosenIdCall::Inexpressible(format!(
295                "its exit signal {exit_signal} is not a signal number, which clone3 refuses"
296            ));
297        }
298        let (stack, stack_size) = match self.stack {
299            0 => (0, 0),
300            // clone3 starts the child at `stack + stack_size`, both nonzero,
301            // and refuses a range outside user memory (clone3_stack_valid).
302            sp if sp == 1 || sp > USER_ADDRESS_LIMIT => {
303                return ChosenIdCall::Inexpressible(format!(
304                    "its new stack pointer {sp:#x} has no clone3 form, which needs a nonzero \
305                     stack in user memory below it"
306                ));
307            }
308            sp => (sp - 1, 1),
309        };
310        let fields: [u64; 11] = [
311            self.flags & !detached,
312            self.pidfd,
313            self.child_tid,
314            self.parent_tid,
315            exit_signal,
316            stack,
317            stack_size,
318            self.tls,
319            0, // set_tid, filled in later
320            0, // set_tid_size
321            0, // cgroup
322        ];
323        let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
324        for (chunk, field) in args.as_chunks_mut::<8>().0.iter_mut().zip(fields) {
325            *chunk = field.to_ne_bytes();
326        }
327        ChosenIdCall::Clone3 {
328            args,
329            size: CLONE_ARGS_SIZE_VER1,
330        }
331    }
332}
333
334/// A guest's own `clone3` arguments, extended to carry `set_tid`.
335///
336/// `copy_clone_args_from_user` reads the first `min(size, 88)` bytes, zero
337/// extends a shorter struct, and refuses a longer one whose extra bytes are
338/// not all zero (`E2BIG`); a struct shorter than the first version is
339/// `EINVAL`. So a struct of any size that the kernel accepts is the
340/// second-version struct of its first 88 bytes, zero extended, and `set_tid`
341/// is unset exactly when bytes 64 to 79 are zero: then Detcore fills it in.
342fn clone3_with_room_for_set_tid(bytes: Option<&[u8]>) -> ChosenIdCall {
343    let Some(bytes) = bytes else {
344        // Mapped, but not readable with the guest's access: the kernel may
345        // read it (`Clone3Read::Unreadable`).
346        return ChosenIdCall::Inexpressible(
347            "Detcore could not read its struct clone_args with the guest's own access, though \
348             it is mapped"
349                .to_owned(),
350        );
351    };
352    let size = bytes.len();
353    if size < CLONE_ARGS_SIZE_VER0 {
354        // copy_clone_args_from_user: EINVAL.
355        return ChosenIdCall::Unchanged;
356    }
357    if bytes
358        .iter()
359        .skip(CLONE_ARGS_SIZE_VER2)
360        .any(|byte| *byte != 0)
361    {
362        // copy_clone_args_from_user refuses it with E2BIG only while the
363        // kernel knows no later struct version; a newer one may read it.
364        return ChosenIdCall::Inexpressible(format!(
365            "its struct clone_args has nonzero bytes past the {CLONE_ARGS_SIZE_VER2} Detcore \
366             knows, which a newer kernel may read"
367        ));
368    }
369    let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
370    let known = size.min(CLONE_ARGS_SIZE_VER2);
371    args[..known].copy_from_slice(&bytes[..known]);
372    if args[SET_TID_OFFSET..CLONE_ARGS_SIZE_VER1]
373        .iter()
374        .any(|byte| *byte != 0)
375    {
376        // The guest names its task's IDs itself, or passes `set_tid` and
377        // `set_tid_size` the kernel refuses (`Detcore::plan_clone_id` tells
378        // which).
379        return ChosenIdCall::GuestChosen {
380            args,
381            size: known.max(CLONE_ARGS_SIZE_VER1),
382        };
383    }
384    ChosenIdCall::Clone3 {
385        args,
386        size: known.max(CLONE_ARGS_SIZE_VER1),
387    }
388}
389
390/// A clone-family call that is to create its task as `clone3` with a
391/// `set_tid` array that reaches Hermit's PID namespace.
392#[derive(Debug)]
393pub(crate) struct ChosenId {
394    /// The array, from the new task's namespace outwards.
395    set_tid: Vec<libc::pid_t>,
396    /// The ID Detcore took for the task in Hermit's namespace, the array's
397    /// last entry; `None` where the guest named every entry itself.
398    id: Option<i32>,
399    /// This attempt took a fresh allocator value while holding its turn.
400    /// A saved restart ID has no such attempt-local registration proof.
401    fresh_allocation: bool,
402    /// The call as a restart keeps its ID under (`RestartedCloneId::call`).
403    restart: Option<Vec<u8>>,
404    /// `struct clone_args`, `set_tid` still unset.
405    args: [u8; CLONE_ARGS_SIZE_VER2],
406    /// How many of those bytes `clone3` is given.
407    size: usize,
408    /// The guest's call passed `CLONE_UNTRACED`, which `args` no longer do.
409    untraced: bool,
410    /// The array begins with entries of the guest's own.
411    guest_entries: bool,
412}
413
414/// Evidence that adding the outer entry did not introduce an EINVAL cause.
415///
416/// This is conditional on pid_max staying stable across this attempt. The
417/// before/after samples detect a visible change, not an ABA shrink and restore.
418/// External host-root sysctl mutation in an init/no-namespace run is outside
419/// this admitted environment; these samples are not an atomic stability proof.
420/// Linux alloc_pid checks the array length, each entry's range, and whether a
421/// namespace without a reaper is being given its first ID. The new last entry
422/// is in Hermit's already-active namespace; inner guest entries are unchanged.
423/// https://github.com/torvalds/linux/blob/v6.17/kernel/pid.c#L178
424///
425/// The fresh allocator value was not registered or clocked at allocation.
426/// The caller retains its serial turn, or installs the pending-vfork barrier
427/// before releasing it; no other guest can register that value before this
428/// failed birth. Thus ThreadIdRefused's zombie exception cannot hide a use of
429/// this fresh value. It is only an additional check, not a universal registry
430/// query. Saved restart IDs lack this attempt-local proof. Registration stays
431/// recorded after exit; clock components themselves can move during exec.
432#[derive(Debug)]
433struct AddedIdEvidence {
434    id: i32,
435    maximum: i32,
436    args: [u8; CLONE_ARGS_SIZE_VER2],
437    size: usize,
438    set_tid: Vec<libc::pid_t>,
439}
440
441impl AddedIdEvidence {
442    fn capture<M: MemoryAccess>(
443        memory: &M,
444        tid: i32,
445        call: &Syscall,
446        chosen: &ChosenId,
447        maximum: Option<i32>,
448    ) -> Option<Self> {
449        let id = chosen.id?;
450        let maximum = maximum?;
451        if !chosen.fresh_allocation || id <= 0 || id >= maximum {
452            return None;
453        }
454        // This proof preserves the original clone3 size and every original
455        // field. Legacy conversions and unknown later struct bytes need
456        // their own equivalence proof, so they retain the refusal policy.
457        let Syscall::Clone3(original) = call else {
458            return None;
459        };
460        if original.size() != chosen.size
461            || !(CLONE_ARGS_SIZE_VER1..=CLONE_ARGS_SIZE_VER2).contains(&chosen.size)
462        {
463            return None;
464        }
465        let Clone3Read::Bytes(bytes) = read_clone_args(memory, FaultWitness::Unproven, original)
466        else {
467            return None;
468        };
469        let mut original_args = [0; CLONE_ARGS_SIZE_VER2];
470        original_args[..bytes.len()].copy_from_slice(&bytes);
471        if original_args != chosen.args {
472            return None;
473        }
474        let flags = u64::from_ne_bytes(original_args[..8].try_into().unwrap());
475        let depth = pid_namespace_depth(tid, flags)?;
476        if chosen.set_tid.len() != depth + 1
477            || chosen.set_tid.len() > MAX_SET_TID
478            || chosen.set_tid.last() != Some(&id)
479        {
480            return None;
481        }
482        if chosen.guest_entries {
483            let original_entries =
484                read_guest_set_tid(memory, FaultWitness::Unproven, &original_args).ok()?;
485            if original_entries != chosen.set_tid[..depth] {
486                return None;
487            }
488        } else if original_args[SET_TID_OFFSET..SET_TID_SIZE_OFFSET + 8] != [0; 16]
489            || !(depth == 0
490                || (depth == 1 && flags & libc::CLONE_NEWPID as u64 != 0 && chosen.set_tid[0] == 1))
491        {
492            return None;
493        }
494        Some(Self {
495            id,
496            maximum,
497            args: original_args,
498            size: chosen.size,
499            set_tid: chosen.set_tid.clone(),
500        })
501    }
502
503    fn staged_matches(
504        &self,
505        image: &ReservedInputImage,
506        rewritten: &syscalls::Clone3,
507        array: u64,
508    ) -> bool {
509        if rewritten.size() != self.size {
510            return false;
511        }
512        let Some(address) = rewritten.args() else {
513            return false;
514        };
515        let mut observed = [0; CLONE_ARGS_SIZE_VER2];
516        let expected = ChosenIdCall::with_set_tid(self.args, array, self.set_tid.len());
517        let mut entries = vec![0; self.set_tid.len() * std::mem::size_of::<libc::pid_t>()];
518        image.read(address.as_raw() as u64, &mut observed)
519            && observed == expected
520            && image.read(array, &mut entries)
521            && entries
522                .as_chunks::<4>()
523                .0
524                .iter()
525                .map(|entry| i32::from_ne_bytes(*entry))
526                .eq(self.set_tid.iter().copied())
527    }
528}
529
530/// Construct a complete future image without touching application memory.
531/// Native inputs/outputs, including effective tagged aliases, may not name
532/// this page. Only fields whose native flags make them active are excluded.
533fn reserved_clone_image(
534    layout: reverie::reserved_input::ReservedInputLayout,
535    original: Syscall,
536    args: [u8; CLONE_ARGS_SIZE_VER2],
537    entries: &[libc::pid_t],
538    size: usize,
539) -> Result<(ReservedInputImage, syscalls::Clone3, u64), InputUnavailable> {
540    use crate::reserved_input::Span;
541    use crate::reserved_input::effective_spans;
542    let page = Span::bytes(layout.base, layout.page_bytes as u64);
543    let excludes = |address: u64, bytes: u64| {
544        effective_spans(address, bytes)
545            .into_iter()
546            .any(|span| span.overlaps(page))
547    };
548    let field = |offset: usize| u64::from_ne_bytes(args[offset..offset + 8].try_into().unwrap());
549    let flags = field(0);
550    if let Syscall::Clone3(call) = original {
551        let address = call.args().map_or(0, |address| address.as_raw() as u64);
552        if excludes(address, call.size() as u64) {
553            return Err(InputUnavailable::new(
554                "clone-original-input-overlaps-reserved-page",
555            ));
556        }
557    }
558    if field(72) != 0
559        && field(72)
560            .checked_mul(4)
561            .is_none_or(|bytes| excludes(field(64), bytes))
562    {
563        return Err(InputUnavailable::new(
564            "clone-set-tid-input-overlaps-reserved-page",
565        ));
566    }
567    for (active, offset) in [
568        (libc::CLONE_PIDFD as u64, 8),
569        (
570            (libc::CLONE_CHILD_SETTID | libc::CLONE_CHILD_CLEARTID) as u64,
571            16,
572        ),
573        (libc::CLONE_PARENT_SETTID as u64, 24),
574    ] {
575        if flags & active != 0 && excludes(field(offset), 4) {
576            return Err(InputUnavailable::new(
577                "clone-native-output-overlaps-reserved-page",
578            ));
579        }
580    }
581    if field(48) != 0 && excludes(field(40), field(48)) {
582        return Err(InputUnavailable::new(
583            "clone-child-stack-overlaps-reserved-page",
584        ));
585    }
586    if flags & libc::CLONE_SETTLS as u64 != 0 && excludes(field(56), 1) {
587        return Err(InputUnavailable::new("clone-tls-overlaps-reserved-page"));
588    }
589    if entries.len() > MAX_SET_TID {
590        return Err(InputUnavailable::new("clone-set-tid-image-too-large"));
591    }
592    let mut array = [0; MAX_SET_TID * std::mem::size_of::<libc::pid_t>()];
593    for (entry, bytes) in entries.iter().zip(array.as_chunks_mut::<4>().0.iter_mut()) {
594        bytes.copy_from_slice(&entry.to_ne_bytes());
595    }
596    let array_address = layout.base;
597    let args_address = layout.base + array.len() as u64;
598    let staged_args = ChosenIdCall::with_set_tid(args, array_address, entries.len());
599    let image = ReservedInputImage::new(
600        layout,
601        &[
602            InputWrite {
603                offset: 0,
604                bytes: &array,
605            },
606            InputWrite {
607                offset: array.len(),
608                bytes: &staged_args,
609            },
610        ],
611    )?;
612    let rewritten = syscalls::Clone3::new()
613        .with_args(AddrMut::from_raw(args_address as usize))
614        .with_size(size);
615    Ok((image, rewritten, array_address))
616}
617
618/// How a clone-family call is issued, as [`Detcore::plan_clone_id`] decides.
619#[derive(Debug)]
620pub(crate) enum ClonePlan {
621    /// As the guest made it, the kernel numbering its task: the run's PID
622    /// namespace does, or the host does (`GuestIds::Namespace`, `Host`).
623    Kernel,
624    /// As `clone3` with `set_tid`.
625    Chosen(ChosenId),
626    /// As the guest made it, though Detcore chooses the IDs: its task gets a
627    /// host ID, for `reason`, which is reported ([`Detcore::gave_host_id`], or
628    /// for a vfork [`Detcore::vfork_gets_host_id`] before the call).
629    AsMade { reason: String },
630    /// As the guest made it, though Detcore chooses the IDs: every kernel
631    /// refuses it before creating a task (`ChosenIdCall::Unchanged`), so it
632    /// is reported only if it creates one after all, and a run that needs
633    /// every ID chosen lets the guest have the kernel's error.
634    KernelRefuses,
635    /// [`ClonePlan::KernelRefuses`] for a legacy `clone` with
636    /// `CLONE_UNTRACED`, issued without the flag: it is one of
637    /// `CLONE_LEGACY_FLAGS`, which no validity check rejects, so the
638    /// kernel's error is the same, and a task created after all is traced.
639    KernelRefusesUntraced,
640    /// As the guest made it: a guest `clone3` that names its task's ID in
641    /// Hermit's PID namespace itself, or passes a `set_tid` array the kernel
642    /// refuses before creating a task.
643    GuestChosen,
644}
645
646impl<T: RecordOrReplay> Detcore<T> {
647    /// Decide how the guest's clone-family call `call` is issued, and, when
648    /// Detcore chooses the IDs (`GuestIds::Chosen`), take the ID of the task
649    /// it is to create: in the caller's turn, before anything the call does
650    /// could let another thread run, so with sequentialized threads the order
651    /// of the IDs is the guest's own order of calls. A call that was
652    /// restarted keeps the ID it took (`ThreadState::restarted_clone_id`), if
653    /// it comes back the same.
654    ///
655    /// The kernel validates the call: Detcore issues it as `clone3` with a
656    /// `set_tid` array that reaches Hermit's PID namespace (the entries the
657    /// guest gave, or 1 for a new namespace's first task, then the chosen
658    /// ID), and the call's own errors come back as they are. Only a call that
659    /// cannot carry a chosen ID takes the host-ID path, which ends a run that
660    /// needs every ID chosen here, before any task exists.
661    pub(crate) async fn plan_clone_id<G: Guest<Self>>(
662        &self,
663        guest: &mut G,
664        call: &Syscall,
665    ) -> Result<ClonePlan, Error> {
666        let detcore_model::pid::GuestIds::Chosen {
667            refuse_host_ids, ..
668        } = self.cfg.guest_ids
669        else {
670            return Ok(ClonePlan::Kernel);
671        };
672        let (sysno, args) = call.into_parts();
673        // The calling thread's own maps: the leader's are empty once it has
674        // exited, while the others still run on the same memory.
675        let tid = guest.tid().as_raw();
676        let witness = FaultWitness::for_backend(&guest.config().backend, tid);
677        let clone3_read = match call {
678            Syscall::Clone3(clone) => Some(read_clone_args(&guest.memory(), witness, clone)),
679            _ => None,
680        };
681        if clone3_read == Some(Clone3Read::Refused) {
682            return Ok(ClonePlan::KernelRefuses);
683        }
684        // A task the tracer does not follow would run outside Detcore's
685        // scheduler, so a call with CLONE_UNTRACED is issued only rewritten,
686        // without the flag; where it cannot be, the run ends. A struct whose
687        // flags Detcore could not read at all counts as untraced.
688        let flags = match (&clone3_read, sysno) {
689            (_, Sysno::clone) => args.arg0 as u64,
690            (Some(Clone3Read::Bytes(bytes)), _) => bytes
691                .get(..8)
692                .map_or(0, |flags| u64::from_ne_bytes(flags.try_into().unwrap())),
693            (Some(Clone3Read::Unreadable { flags }), _) => {
694                flags.unwrap_or(libc::CLONE_UNTRACED as u64)
695            }
696            _ => 0,
697        };
698        let untraced = flags & libc::CLONE_UNTRACED as u64 != 0;
699        let clone3_args = match clone3_read {
700            Some(Clone3Read::Bytes(bytes)) => Some(bytes),
701            _ => None,
702        };
703        let form = clone3_form(sysno, &args, clone3_args.as_deref());
704        // A restarted call's ID is for that call only: taken here whatever
705        // the plan, so that no other call inherits it.
706        let restarted = guest.thread_state_mut().restarted_clone_id.take();
707        let tid = guest.tid().as_raw();
708        let (args, size, set_tid, guest_entries) = match form {
709            ChosenIdCall::Invalid(errno) => return Err(errno.into()),
710            ChosenIdCall::Clone3 { args, size } => {
711                let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
712                // The new task's ID in each namespace inside Hermit's: none,
713                // or 1, a new namespace's first task, with CLONE_NEWPID from
714                // Hermit's own. A namespace the guest made for its children
715                // already has tasks numbered by the kernel.
716                let inner = match pid_namespace_depth(tid, flags) {
717                    Some(0) => Vec::new(),
718                    Some(1) if flags & libc::CLONE_NEWPID as u64 != 0 => vec![1],
719                    Some(depth) => {
720                        let why = format!(
721                            "its task is created {depth} PID namespace(s) below Hermit's, in one \
722                             the guest made, whose IDs the kernel picks"
723                        );
724                        return self
725                            .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
726                            .await;
727                    }
728                    None => {
729                        let why =
730                            "the PID namespace its task is created in could not be read".to_owned();
731                        return self
732                            .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
733                            .await;
734                    }
735                };
736                if let Some(why) = self.clone3_could_fare_otherwise(guest) {
737                    return self
738                        .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
739                        .await;
740                }
741                (args, size, inner, false)
742            }
743            ChosenIdCall::GuestChosen { args, size } => {
744                // An array every kernel refuses goes to the kernel, whose
745                // EINVAL or EFAULT is the guest's. One Detcore cannot read
746                // though it is mapped could still be read by the kernel, so
747                // it may create a task whose ID in Hermit's namespace the
748                // kernel picks.
749                let tids = match read_guest_set_tid(&guest.memory(), witness, &args) {
750                    Ok(tids) => tids,
751                    Err(SetTidUnread::Refused) => return Ok(ClonePlan::GuestChosen),
752                    Err(SetTidUnread::Unreadable) => {
753                        let why = "Detcore could not read its set_tid array with the guest's own \
754                                   access"
755                            .to_owned();
756                        return self
757                            .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
758                            .await;
759                    }
760                };
761                let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
762                // The array runs from the new task's PID namespace outwards.
763                match pid_namespace_depth(tid, flags) {
764                    // It names the task's ID in Hermit's namespace itself.
765                    Some(depth) if tids.len() > depth => {
766                        if !untraced {
767                            return Ok(ClonePlan::GuestChosen);
768                        }
769                        // Issued rewritten only to drop CLONE_UNTRACED.
770                        let args = without_untraced(args);
771                        return Ok(ClonePlan::Chosen(ChosenId {
772                            set_tid: tids,
773                            id: None,
774                            fresh_allocation: false,
775                            restart: None,
776                            args,
777                            size,
778                            untraced,
779                            guest_entries: true,
780                        }));
781                    }
782                    // One short: Detcore adds the ID in Hermit's namespace.
783                    Some(depth) if tids.len() == depth && depth < MAX_SET_TID => {
784                        (args, size, tids, true)
785                    }
786                    Some(depth) => {
787                        let why = format!(
788                            "its set_tid names {} ID(s), but its task is created {depth} PID \
789                             namespace(s) below Hermit's, so the IDs in between are the kernel's",
790                            tids.len()
791                        );
792                        return self
793                            .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
794                            .await;
795                    }
796                    None => {
797                        let why =
798                            "the PID namespace its task is created in could not be read".to_owned();
799                        return self
800                            .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
801                            .await;
802                    }
803                }
804            }
805            // CLONE_UNTRACED is dropped from such a call (a legacy clone's:
806            // a clone3 struct is never Unchanged), keeping the kernel's error,
807            // so that a task it creates after all is traced.
808            ChosenIdCall::Unchanged if untraced => return Ok(ClonePlan::KernelRefusesUntraced),
809            ChosenIdCall::Unchanged => return Ok(ClonePlan::KernelRefuses),
810            ChosenIdCall::Inexpressible(why) => {
811                return self
812                    .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
813                    .await;
814            }
815        };
816        // A task Detcore numbers must be traced: its own clones reach Detcore
817        // only then. CLONE_UNTRACED only keeps a tracer from following the
818        // task, and the guest's tasks are Hermit's to trace, so the rewrite
819        // drops it (as https://github.com/rrnewton/hermit/pull/4660 does for
820        // every clone). Nothing else in the call depends on it.
821        let args = without_untraced(args);
822        let call = RestartedCloneId::call(sysno, &args, size);
823        let (id, fresh_allocation) = match restarted {
824            Some(restarted) if restarted.call == call => (restarted.id, false),
825            _ => match crate::tool_global::next_guest_id(guest).await {
826                Some(id) => (id, true),
827                // Exhaustion does not wrap to a nonpositive or reused ID.
828                // Keep the existing host-ID fallback or strict refusal.
829                None => {
830                    return self
831                        .host_id_planned(
832                            guest,
833                            sysno,
834                            "Detcore has no unused positive process ID left".to_owned(),
835                            refuse_host_ids,
836                            untraced,
837                        )
838                        .await;
839                }
840            },
841        };
842        let mut set_tid = set_tid;
843        set_tid.push(id);
844        Ok(ClonePlan::Chosen(ChosenId {
845            set_tid,
846            id: Some(id),
847            fresh_allocation,
848            restart: Some(call),
849            args,
850            size,
851            untraced,
852            guest_entries,
853        }))
854    }
855
856    /// Why the caller's `clone3` could fare otherwise than its own call, if
857    /// it could: a seccomp filter the guest installed (only Reverie's and
858    /// those Hermit inherited are known) could trap, kill or refuse `clone3`
859    /// where it allows the guest's call.
860    fn clone3_could_fare_otherwise<G: Guest<Self>>(&self, guest: &G) -> Option<String> {
861        let tid = guest.tid().as_raw();
862        let expected = match self.cfg.inherited_seccomp {
863            // Reverie's own filter, and those Hermit runs under.
864            detcore_model::config::InheritedSeccomp::Disabled => 1,
865            detcore_model::config::InheritedSeccomp::Filter(Some(filters)) => filters + 1,
866            _ => {
867                return Some(
868                    "Hermit could not count the seccomp filters it runs under, so a filter of \
869                     the guest's own could not be told from them"
870                        .to_owned(),
871                );
872            }
873        };
874        let status = std::fs::read_to_string(format!("/proc/{tid}/status")).unwrap_or_default();
875        let filters = status
876            .lines()
877            .find_map(|line| line.strip_prefix("Seccomp_filters:"))
878            .and_then(|count| count.trim().parse::<u32>().ok());
879        match filters {
880            Some(filters) if filters <= expected => None,
881            Some(_) => Some(
882                "the guest installed a seccomp filter, which could trap, kill or refuse clone3 \
883                 where it allows this call"
884                    .to_owned(),
885            ),
886            None => Some(
887                "this kernel does not report the task's seccomp filters (Linux 5.9 and later \
888                 do), so one of the guest's own could not be ruled out"
889                    .to_owned(),
890            ),
891        }
892    }
893
894    /// A call whose task will get a host ID, for `why`: the end of a run that
895    /// needs every ID chosen, or of any run if the call is `untraced` (its
896    /// task would run outside Detcore's scheduler), else a plan to issue it
897    /// as made. A run that needs every ID chosen ends before the kernel sees
898    /// the call, so an error the kernel would also have given it is not the
899    /// guest's to see: the run fails closed rather than refusing only after a
900    /// task with a host ID exists.
901    async fn host_id_planned<G: Guest<Self>>(
902        &self,
903        guest: &mut G,
904        sysno: Sysno,
905        why: String,
906        refuse_host_ids: bool,
907        untraced: bool,
908    ) -> Result<ClonePlan, Error> {
909        let reason = format!("this {sysno} cannot be given a chosen process ID: {why}");
910        if untraced {
911            refuse_untraced(guest, reason).await
912        }
913        if refuse_host_ids {
914            refuse_host_id(guest, reason).await
915        }
916        Ok(ClonePlan::AsMade { reason })
917    }
918
919    /// What a refusal for a held chosen ID asks for: a replay takes its
920    /// record's base, so it names the record; any other run can pass another
921    /// base.
922    fn free_base_hint(&self) -> String {
923        let base = self
924            .cfg
925            .guest_ids
926            .base()
927            .map_or_else(|| "?".to_owned(), |base| base.to_string());
928        match self
929            .cfg
930            .replay_preemptions_from
931            .as_ref()
932            .or(self.cfg.replay_schedule_from.as_ref())
933        {
934            Some(record) => format!(
935                "The replayed record {} takes base {base}; replay it where no host task holds \
936                 the IDs from that base",
937                record.display()
938            ),
939            None => "Pass a --pid-base whose IDs no host task holds".to_owned(),
940        }
941    }
942
943    /// Report that the guest's clone-family call created task `child` with a
944    /// host ID, for `reason`: the end of a run that needs every ID chosen,
945    /// else a warning and a count, which the summary shows and which marks
946    /// the preemption record `GuestIds::Host`, so that a replay refuses it.
947    pub(crate) async fn gave_host_id<G: Guest<Self>>(
948        &self,
949        guest: &mut G,
950        child: i64,
951        reason: &str,
952    ) {
953        let reason = format!("task {child} got a host ID, not a chosen one: {reason}");
954        if matches!(
955            self.cfg.guest_ids,
956            detcore_model::pid::GuestIds::Chosen {
957                refuse_host_ids: true,
958                ..
959            }
960        ) {
961            refuse_host_id(guest, reason).await
962        }
963        warn!(
964            "[detcore, dtid {}] {reason}; that ID differs from run to run",
965            guest.thread_state().dettid
966        );
967        crate::tool_global::note_host_id_task(guest).await;
968    }
969
970    /// Report, before a `CLONE_VFORK` call issued as made, that its task, if
971    /// it creates one, has a host ID for `reason`. Its parent returns only
972    /// once the task has exec'd or exited, and the run can end while it runs,
973    /// so a report after the call would come too late for the preemption
974    /// record. A call that then fails was reported all the same, which errs
975    /// on the side of a record a replay refuses.
976    async fn vfork_gets_host_id<G: Guest<Self>>(&self, guest: &mut G, reason: &str) {
977        let reason = format!(
978            "this vfork's task gets a host ID, not a chosen one, reported before the call: \
979             {reason}"
980        );
981        if matches!(
982            self.cfg.guest_ids,
983            detcore_model::pid::GuestIds::Chosen {
984                refuse_host_ids: true,
985                ..
986            }
987        ) {
988            refuse_host_id(guest, reason).await
989        }
990        warn!(
991            "[detcore, dtid {}] {reason}; that ID differs from run to run",
992            guest.thread_state().dettid
993        );
994        crate::tool_global::note_host_id_task(guest).await;
995    }
996
997    /// Issue the guest's `CLONE_VFORK` call `call` as made, its task's host
998    /// ID reported first, for `reason` ([`Self::vfork_gets_host_id`]). A
999    /// call that then fails, other than for a restart, created no task,
1000    /// which a warning says, since the count already has it.
1001    async fn issue_reported_vfork<G: Guest<Self>>(
1002        &self,
1003        guest: &mut G,
1004        call: Syscall,
1005        reason: &str,
1006    ) -> Result<i64, Errno> {
1007        self.vfork_gets_host_id(guest, reason).await;
1008        let result = crate::reserved_input::inject_original_clone(guest, call).await;
1009        if let Err(errno) = result
1010            && !is_restart(errno)
1011        {
1012            warn!(
1013                "[detcore, dtid {}] the vfork reported above failed with {errno} and created no \
1014                 task; the run's count of accepted host-ID reports includes it",
1015                guest.thread_state().dettid
1016            );
1017        }
1018        result
1019    }
1020
1021    /// Issue `call`, which every kernel is expected to refuse before creating
1022    /// a task, as made; a task it creates after all has a host ID, reported
1023    /// after the call.
1024    async fn issue_kernel_refused<G: Guest<Self>>(
1025        &self,
1026        guest: &mut G,
1027        call: Syscall,
1028    ) -> Result<i64, Errno> {
1029        let result = crate::reserved_input::inject_original_clone(guest, call).await;
1030        if let Ok(child) = result {
1031            self.gave_host_id(
1032                guest,
1033                child,
1034                "unexpectedly, as Detcore expected every kernel to refuse it",
1035            )
1036            .await;
1037        }
1038        result
1039    }
1040
1041    /// Issue the guest's clone-family call `call` as `plan` says; `vfork`
1042    /// says whether it passes `CLONE_VFORK`.
1043    pub(crate) async fn inject_clone_planned<G: Guest<Self>>(
1044        &self,
1045        guest: &mut G,
1046        call: Syscall,
1047        plan: ClonePlan,
1048        vfork: bool,
1049    ) -> Result<i64, Errno> {
1050        match plan {
1051            ClonePlan::Kernel | ClonePlan::GuestChosen => {
1052                crate::reserved_input::inject_original_clone(guest, call).await
1053            }
1054            ClonePlan::AsMade { reason } => {
1055                if vfork {
1056                    return self.issue_reported_vfork(guest, call, &reason).await;
1057                }
1058                let result = crate::reserved_input::inject_original_clone(guest, call).await;
1059                if let Ok(child) = result {
1060                    self.gave_host_id(guest, child, &reason).await;
1061                }
1062                result
1063            }
1064            ClonePlan::KernelRefuses => self.issue_kernel_refused(guest, call).await,
1065            ClonePlan::KernelRefusesUntraced => {
1066                let (sysno, args) = call.into_parts();
1067                self.issue_kernel_refused(
1068                    guest,
1069                    Syscall::from_raw(sysno, traced_legacy_clone(args)),
1070                )
1071                .await
1072            }
1073            ClonePlan::Chosen(chosen) => {
1074                self.inject_with_chosen_id(guest, call, chosen, vfork).await
1075            }
1076        }
1077    }
1078
1079    /// Prepare and issue one native attempt with an owned immutable image.
1080    /// A successful mechanism result certifies full-page write/readback and
1081    /// settled ownership even when the native syscall returned an errno.
1082    async fn stage_chosen_clone<G: Guest<Self>>(
1083        &self,
1084        guest: &mut G,
1085        call: Syscall,
1086        args: [u8; CLONE_ARGS_SIZE_VER2],
1087        set_tid: &[libc::pid_t],
1088        size: usize,
1089        attribution: Option<&AddedIdEvidence>,
1090    ) -> Result<(Result<i64, Errno>, bool), InputInjectionError> {
1091        let (control, task) = crate::reserved_input::capability(guest)
1092            .ok_or_else(|| InputUnavailable::new("reserved-input-facility-unavailable"))?;
1093        let layout = control
1094            .layout(task)
1095            .ok_or_else(|| InputUnavailable::new("reserved-input-layout-unqualified"))?;
1096        let (image, rewritten, array) = reserved_clone_image(layout, call, args, set_tid, size)?;
1097        // Compare F2's complete expected struct/array against the future view.
1098        // The backend uses that same immutable image for preparation and
1099        // native readback; there is no Tool callback after page publication.
1100        let input_matches =
1101            attribution.is_some_and(|proof| proof.staged_matches(&image, &rewritten, array));
1102        let scope = control.begin_operation(task, guest.thread_state().stats.syscall_count)?;
1103        let args_address = rewritten.args().expect("fixed reserved address").as_raw();
1104        let args_offset = args_address - layout.base as usize;
1105        let description = crate::reserved_input::clone_description(
1106            rewritten.into(),
1107            Some((
1108                args_address as u64,
1109                &image.bytes()[args_offset..args_offset + CLONE_ARGS_SIZE_VER2],
1110            )),
1111            guest.reserved_input_process_exit_signal().ok(),
1112        );
1113        let receipt = match control.prepare_injection(&scope, description) {
1114            Ok(receipt) => receipt,
1115            Err(error) => {
1116                control.finish_operation(scope)?;
1117                return Err(error.into());
1118            }
1119        };
1120        let result = guest
1121            .inject_staged_input(rewritten, &scope, image, receipt)
1122            .await;
1123        // No synthetic grant: finish only releases this operation's consumed
1124        // capability after the native ledger proves its physical obligations.
1125        control
1126            .finish_operation(scope)
1127            .map_err(|error| InputInjectionError::Fatal(error.to_string()))?;
1128        result.map(|native| (native, input_matches))
1129    }
1130
1131    /// Issue the guest's clone-family call `call` as `clone3` creating its
1132    /// task with the `set_tid` array `chosen` names, through backend-owned
1133    /// sealed input, never through a borrowed application stack.
1134    ///
1135    /// The added ID can introduce `EEXIST` when held, `EPERM` without the
1136    /// capability to choose it, or `EINVAL` when out of range. EINVAL may be
1137    /// attributed to the original call only with [`AddedIdEvidence`], under
1138    /// its explicit stable-pid_max assumption. Missing evidence keeps the
1139    /// error ambiguous. For ambiguous errors, and when scratch memory cannot be
1140    /// written, a run that needs every ID chosen ends, before any task gets a
1141    /// host ID; any other run issues the guest's own call, whose task, if it
1142    /// creates one, has a host ID ([`Self::gave_host_id`]). A restart keeps
1143    /// the ID for the restarted call.
1144    async fn inject_with_chosen_id<G: Guest<Self>>(
1145        &self,
1146        guest: &mut G,
1147        call: Syscall,
1148        chosen: ChosenId,
1149        vfork: bool,
1150    ) -> Result<i64, Errno> {
1151        let refuse_host_ids = matches!(
1152            self.cfg.guest_ids,
1153            detcore_model::pid::GuestIds::Chosen {
1154                refuse_host_ids: true,
1155                ..
1156            }
1157        );
1158        let maximum_before = pid_max();
1159        // A filter can distinguish the staged struct pointer or injection
1160        // instruction pointer even when every original field is preserved.
1161        // Only a launch with no inherited filter, and no additional guest
1162        // filter beyond Reverie's known Allow/Trace filter, can certify this
1163        // errno. Counting arbitrary inherited filters is not equivalence.
1164        let mut attribution = (self.cfg.sequentialize_threads
1165            && self.cfg.inherited_seccomp == detcore_model::config::InheritedSeccomp::Disabled
1166            && self.clone3_could_fare_otherwise(guest).is_none())
1167        .then(|| {
1168            AddedIdEvidence::capture(
1169                &guest.memory(),
1170                guest.tid().as_raw(),
1171                &call,
1172                &chosen,
1173                maximum_before,
1174            )
1175        })
1176        .flatten();
1177        if let Some(evidence) = &attribution
1178            && crate::tool_global::thread_id_refused(guest, DetTid::from_raw(evidence.id)).await
1179        {
1180            attribution = None;
1181        }
1182        let ChosenId {
1183            set_tid,
1184            id,
1185            fresh_allocation: _,
1186            restart,
1187            args,
1188            size,
1189            untraced,
1190            guest_entries,
1191        } = chosen;
1192        // Reject a known bad added ID before rewriting. The sample alone
1193        // does not establish attribution of a later EINVAL.
1194        if let Some(cause) = id.and_then(|id| chosen_id_range_refusal(id, maximum_before)) {
1195            if refuse_host_ids {
1196                refuse_host_id(
1197                    guest,
1198                    format!(
1199                        "this {} cannot create its task with its chosen ID: {cause}. The guest \
1200                         has created more tasks than the IDs from its base to pid_max hold; the \
1201                         run needs a lower base",
1202                        call.number()
1203                    ),
1204                )
1205                .await
1206            }
1207            return self
1208                .issue_as_made(guest, call, &cause, untraced, vfork)
1209                .await;
1210        }
1211        let cause = match self
1212            .stage_chosen_clone(guest, call, args, &set_tid, size, attribution.as_ref())
1213            .await
1214        {
1215            Ok((result, staged_matches)) => {
1216                if !staged_matches {
1217                    attribution = None;
1218                }
1219                // No child exists after EINVAL. Recheck the same allocated
1220                // value, not a PID obtained from the failed syscall result.
1221                // Matching samples do not exclude pid_max ABA; the admitted
1222                // environment must keep that sysctl stable during the attempt.
1223                if result == Err(Errno::EINVAL)
1224                    && let Some(evidence) = &attribution
1225                    && pid_max() == Some(evidence.maximum)
1226                    && !crate::tool_global::thread_id_refused(guest, DetTid::from_raw(evidence.id))
1227                        .await
1228                {
1229                    return Err(Errno::EINVAL);
1230                }
1231                match result {
1232                    Ok(child) => {
1233                        debug!(
1234                            "[detcore, dtid {}] {} created task {child} with set_tid {set_tid:?}",
1235                            guest.thread_state().dettid,
1236                            call.number()
1237                        );
1238                        return Ok(child);
1239                    }
1240                    // Linux restarts the guest's call whole; it keeps its ID.
1241                    Err(errno) if is_restart(errno) => {
1242                        if let (Some(id), Some(call)) = (id, restart) {
1243                            guest.thread_state_mut().restarted_clone_id =
1244                                Some(RestartedCloneId { id, call });
1245                        }
1246                        return Err(errno);
1247                    }
1248                    Err(errno) => match id.and_then(|_| set_tid_failure(errno)) {
1249                        None => return Err(errno),
1250                        Some(SetTidFailure::Taken) => {
1251                            let id = id.unwrap();
1252                            let cause = if guest_entries {
1253                                format!(
1254                                    "ID {id}, or one of the guest's own set_tid entries, is \
1255                                     taken (EEXIST); ID {id}: {}",
1256                                    holder_of(id)
1257                                )
1258                            } else {
1259                                format!("ID {id} is taken: {}", holder_of(id))
1260                            };
1261                            if refuse_host_ids {
1262                                refuse_host_id(
1263                                    guest,
1264                                    format!(
1265                                        "this {} cannot create its task with its chosen ID: \
1266                                         {cause}. {}",
1267                                        call.number(),
1268                                        self.free_base_hint()
1269                                    ),
1270                                )
1271                                .await
1272                            }
1273                            cause
1274                        }
1275                        Some(SetTidFailure::NotPermitted) => format!(
1276                            "the rewrite with ID {} in Hermit's PID namespace failed with EPERM, \
1277                             taken as the guest task's lacking CAP_SYS_ADMIN and \
1278                             CAP_CHECKPOINT_RESTORE there (for example in a user namespace of its \
1279                             own)",
1280                            id.unwrap()
1281                        ),
1282                        Some(SetTidFailure::PossiblyOutOfRange) => format!(
1283                            "the rewrite with ID {} in Hermit's PID namespace failed with EINVAL; \
1284                             the added ID lacks complete unchanged-input, stable-pid-range and \
1285                             registration evidence for attributing that error to the guest",
1286                            id.unwrap()
1287                        ),
1288                    },
1289                }
1290            }
1291            Err(InputInjectionError::Unavailable(error)) => {
1292                format!("its reserved input is unavailable: {error}")
1293            }
1294            Err(InputInjectionError::Fatal(error)) => {
1295                // The backend has already failed the run before permitting a
1296                // continuation. This must not become a guest errno or retry.
1297                panic!("reserved-input native ownership failure: {error}")
1298            }
1299        };
1300        // A run that needs every ID chosen ends here, before the guest's own
1301        // call could create a task with a host ID (and, for vfork, run it).
1302        if refuse_host_ids {
1303            refuse_host_id(
1304                guest,
1305                format!(
1306                    "this {} cannot create its task with a chosen ID: {cause}",
1307                    call.number()
1308                ),
1309            )
1310            .await
1311        }
1312        self.issue_as_made(guest, call, &cause, untraced, vfork)
1313            .await
1314    }
1315
1316    /// Issue the guest's own call `call`, whose task, if it creates one, has
1317    /// a host ID for `cause` ([`Self::gave_host_id`], or for a `vfork` call
1318    /// [`Self::vfork_gets_host_id`] before it). An untraced call is not
1319    /// issued: its task would run outside Detcore's scheduler, so even a run
1320    /// that takes host IDs ends.
1321    async fn issue_as_made<G: Guest<Self>>(
1322        &self,
1323        guest: &mut G,
1324        call: Syscall,
1325        cause: &str,
1326        untraced: bool,
1327        vfork: bool,
1328    ) -> Result<i64, Errno> {
1329        if untraced {
1330            refuse_untraced(
1331                guest,
1332                format!(
1333                    "this {} cannot create its task with its chosen ID ({cause}), and the guest's \
1334                     own call would create a task the tracer does not follow (CLONE_UNTRACED)",
1335                    call.number()
1336                ),
1337            )
1338            .await
1339        }
1340        if vfork {
1341            return self.issue_reported_vfork(guest, call, cause).await;
1342        }
1343        let original = crate::reserved_input::inject_original_clone(guest, call).await;
1344        if let Ok(child) = original {
1345            self.gave_host_id(guest, child, cause).await;
1346        }
1347        original
1348    }
1349}
1350
1351/// A legacy `clone`'s arguments `args` without `CLONE_UNTRACED`, in its
1352/// flags (the first argument), and nothing else changed.
1353fn traced_legacy_clone(mut args: SyscallArgs) -> SyscallArgs {
1354    args.arg0 &= !(libc::CLONE_UNTRACED as usize);
1355    args
1356}
1357
1358/// The ID a restarted clone-family call took (`ThreadState::restarted_clone_id`),
1359/// and `call`, the call as Detcore normalized it (`RestartedCloneId::call`).
1360/// Linux restarts the same call with the same arguments, so the restart
1361/// matches it and keeps the ID. The binding is by arguments, not by call
1362/// instance: a call in between with other arguments (a signal handler's
1363/// `clone` with its own stack) takes an ID of its own and retires this one;
1364/// one with the same arguments (a handler's `fork`) takes this ID, and the
1365/// restart then takes the next. Either way no ID is given twice, and where
1366/// the handler runs is decided by the scheduler, so the pairing is the same
1367/// in every run.
1368#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1369pub(crate) struct RestartedCloneId {
1370    pub(crate) id: i32,
1371    pub(crate) call: Vec<u8>,
1372}
1373
1374impl RestartedCloneId {
1375    /// The call's number, the size passed to `clone3`, and the `struct
1376    /// clone_args` bytes before `set_tid` is filled in, as one byte string.
1377    fn call(sysno: Sysno, args: &[u8], size: usize) -> Vec<u8> {
1378        let mut call = Vec::with_capacity(16 + args.len());
1379        call.extend_from_slice(&(sysno as u64).to_ne_bytes());
1380        call.extend_from_slice(&(size as u64).to_ne_bytes());
1381        call.extend_from_slice(args);
1382        call
1383    }
1384}
1385
1386/// `args` with `CLONE_UNTRACED` cleared.
1387fn without_untraced(mut args: [u8; CLONE_ARGS_SIZE_VER2]) -> [u8; CLONE_ARGS_SIZE_VER2] {
1388    let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
1389    args[..8].copy_from_slice(&(flags & !(libc::CLONE_UNTRACED as u64)).to_ne_bytes());
1390    args
1391}
1392
1393/// The host's pid_max, read at each call, since it can change: an ID at or
1394/// above it is not one `set_tid` can give.
1395fn pid_max() -> Option<i32> {
1396    std::fs::read_to_string("/proc/sys/kernel/pid_max")
1397        .ok()?
1398        .trim()
1399        .parse()
1400        .ok()
1401}
1402
1403/// A preflight rejection, not proof that a later kernel allocation will
1404/// accept this ID. A missing or changing pid_max leaves EINVAL ambiguous.
1405fn chosen_id_range_refusal(id: i32, maximum: Option<i32>) -> Option<String> {
1406    if id <= 0 {
1407        Some(format!("chosen ID {id} is not positive"))
1408    } else {
1409        maximum
1410            .filter(|maximum| id >= *maximum)
1411            .map(|maximum| format!("ID {id} is not below pid_max, {maximum}"))
1412    }
1413}
1414
1415/// How the rewritten `clone3` may have failed because of the ID Detcore
1416/// added to its `set_tid` array. These are taken as that ID's, the
1417/// conservative side: a run that needs every ID chosen then ends, and any
1418/// other run issues the guest's own call, which gets its own answer.
1419#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1420enum SetTidFailure {
1421    /// `EEXIST`: the ID is still allocated, as a live task's or only as an
1422    /// exited task's process group or session, which no procfs scan can
1423    /// rule out (alloc_pid refuses any ID still in the namespace's IDR).
1424    /// Linux's own clone picks a free ID, so without entries of the guest's
1425    /// it is always the added ID's; with them, one of the guest's own may be
1426    /// the one taken.
1427    Taken,
1428    /// `EPERM`: choosing an ID in Hermit's namespace takes `CAP_SYS_ADMIN`
1429    /// or `CAP_CHECKPOINT_RESTORE` over it, which the guest's own call may
1430    /// not need (a task in a user namespace of its own has both there, not
1431    /// in Hermit's). Another cause of the call's own (a guest entry, an LSM)
1432    /// cannot be told from it.
1433    NotPermitted,
1434    /// `EINVAL`: alloc_pid rejects an out-of-range set_tid entry before
1435    /// checking the capability. A sampled pid_max cannot rule this out.
1436    PossiblyOutOfRange,
1437}
1438
1439/// Failures the added set_tid entry may cause (kernel/pid.c, alloc_pid).
1440/// EINVAL remains ambiguous without the complete conditional AddedIdEvidence.
1441/// When that evidence is absent and fallback is permitted, only the original
1442/// call can establish its own result.
1443fn set_tid_failure(errno: Errno) -> Option<SetTidFailure> {
1444    match errno {
1445        Errno::EEXIST => Some(SetTidFailure::Taken),
1446        Errno::EPERM => Some(SetTidFailure::NotPermitted),
1447        Errno::EINVAL => Some(SetTidFailure::PossiblyOutOfRange),
1448        _ => None,
1449    }
1450}
1451
1452/// The kernel's limit on PID namespace nesting (`MAX_PID_NS_LEVEL`), and so
1453/// on `set_tid_size`.
1454const MAX_SET_TID: usize = 32;
1455
1456/// A guest `clone3`'s own `set_tid` array, from its `struct clone_args`
1457/// `args`, or `None` where the kernel refuses the call on that array before
1458/// creating a task (`copy_clone_args_from_user`): an address without a size
1459/// or the reverse, or more than `MAX_PID_NS_LEVEL` entries (`EINVAL`), or an
1460/// array it cannot read (`EFAULT`). The entries themselves are left to the
1461/// kernel.
1462fn read_guest_set_tid<M: MemoryAccess>(
1463    memory: &M,
1464    witness: FaultWitness,
1465    args: &[u8; CLONE_ARGS_SIZE_VER2],
1466) -> Result<Vec<libc::pid_t>, SetTidUnread> {
1467    let (address, size) = guest_set_tid_shape(args).ok_or(SetTidUnread::Refused)?;
1468    let mut bytes = vec![0u8; size * std::mem::size_of::<libc::pid_t>()];
1469    if !within_user_memory(address as usize, bytes.len()) {
1470        return Err(if range_faults(witness, address as usize, bytes.len()) {
1471            SetTidUnread::Refused
1472        } else {
1473            SetTidUnread::Unreadable
1474        });
1475    }
1476    // Exactly the array, with the guest's own access, as copy_from_user
1477    // reads it: a debugger read could reach past it into an unmapped page.
1478    let read = memory.read_exact_with_user_access(
1479        Addr::<u8>::from_raw(address as usize).ok_or(SetTidUnread::Refused)?,
1480        &mut bytes,
1481    );
1482    if read.is_err() {
1483        return Err(if range_faults(witness, address as usize, bytes.len()) {
1484            SetTidUnread::Refused
1485        } else {
1486            SetTidUnread::Unreadable
1487        });
1488    }
1489    Ok(bytes
1490        .as_chunks::<4>()
1491        .0
1492        .iter()
1493        .map(|chunk| libc::pid_t::from_ne_bytes(*chunk))
1494        .collect())
1495}
1496
1497/// Read the guest's `pid_t` array at `address` into `tids` exactly, as
1498/// `copy_from_user` reads it, with the guest's own access: never a debugger
1499/// read, which could reach past the array into the next page, or read a page
1500/// the guest cannot. `None` if that read fails; the caller then decides by
1501/// [`range_faults`] whether the kernel must fault too.
1502pub(crate) fn read_pid_array<M: MemoryAccess>(
1503    memory: &M,
1504    address: usize,
1505    tids: &mut [libc::pid_t],
1506) -> Option<()> {
1507    const ENTRY: usize = std::mem::size_of::<libc::pid_t>();
1508    let mut bytes = vec![0u8; tids.len() * ENTRY];
1509    memory
1510        .read_exact_with_user_access(Addr::<u8>::from_raw(address)?, &mut bytes)
1511        .ok()?;
1512    for (tid, chunk) in tids.iter_mut().zip(bytes.as_chunks::<ENTRY>().0) {
1513        *tid = libc::pid_t::from_ne_bytes(*chunk);
1514    }
1515    Some(())
1516}
1517
1518/// Why a guest `clone3`'s own `set_tid` array was not read.
1519#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1520enum SetTidUnread {
1521    /// Every kernel refuses the call before creating a task: the array's
1522    /// address and size disagree, or it is longer than `MAX_PID_NS_LEVEL`
1523    /// (`EINVAL`), or reading it is proved to fault (`EFAULT`,
1524    /// [`range_faults`]).
1525    Refused,
1526    /// Detcore could not read it with the guest's own access, and no fault
1527    /// is proved: the kernel may read it (a page mapped writable, not
1528    /// readable).
1529    Unreadable,
1530}
1531
1532/// The address and length of a guest `clone3`'s own `set_tid` array, unless
1533/// `copy_clone_args_from_user` refuses them (`EINVAL`).
1534fn guest_set_tid_shape(args: &[u8; CLONE_ARGS_SIZE_VER2]) -> Option<(u64, usize)> {
1535    let field = |offset: usize| u64::from_ne_bytes(args[offset..offset + 8].try_into().unwrap());
1536    let (address, size) = (field(SET_TID_OFFSET), field(SET_TID_SIZE_OFFSET));
1537    (address != 0 && size != 0 && size <= MAX_SET_TID as u64).then_some((address, size as usize))
1538}
1539
1540/// How many PID namespaces out of the one a clone-family call with `flags`
1541/// by thread `tid` creates its task in Hermit's own is: 0 for Hermit's, one
1542/// more with `CLONE_NEWPID`, and the depth of a namespace the guest made for
1543/// its children (`pid_for_children`), walked up with `NS_GET_PARENT`. A
1544/// kernel without PID namespaces (`/proc/self/ns` without `pid`) has only
1545/// the one. `None` if a namespace cannot be read (no `/proc/self/ns` at
1546/// all), or the walk does not reach Hermit's within the kernel's nesting
1547/// limit; a depth beyond that limit, where Linux would refuse a
1548/// `CLONE_NEWPID` with `ENOSPC`, also takes the host-ID path.
1549fn pid_namespace_depth(tid: i32, flags: u64) -> Option<usize> {
1550    use std::os::fd::AsRawFd;
1551    use std::os::fd::FromRawFd;
1552    use std::os::unix::fs::MetadataExt;
1553    // ioctl_ns(2): _IO(0xb7, 0x2).
1554    const NS_GET_PARENT: u64 = 0xb702;
1555    let new = usize::from(flags & libc::CLONE_NEWPID as u64 != 0);
1556    let own = match std::fs::metadata("/proc/self/ns/pid") {
1557        Ok(own) => own,
1558        // No `pid` entry beside the others: a kernel without PID namespaces.
1559        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
1560            return std::path::Path::new("/proc/self/ns")
1561                .is_dir()
1562                .then_some(new);
1563        }
1564        Err(_) => return None,
1565    };
1566    let mut namespace = std::fs::File::open(format!("/proc/{tid}/ns/pid_for_children")).ok()?;
1567    for depth in 0..=MAX_SET_TID {
1568        let here = namespace.metadata().ok()?;
1569        if (here.dev(), here.ino()) == (own.dev(), own.ino()) {
1570            return Some(depth + new);
1571        }
1572        // SAFETY: NS_GET_PARENT takes no argument and returns a new file
1573        // descriptor, owned from here on, or -1.
1574        let parent = unsafe { libc::ioctl(namespace.as_raw_fd(), NS_GET_PARENT as _) };
1575        if parent < 0 {
1576            return None;
1577        }
1578        // SAFETY: `parent` is a file descriptor nothing else owns.
1579        namespace = unsafe { std::fs::File::from_raw_fd(parent) };
1580    }
1581    None
1582}
1583
1584/// Whether `errno` is one of the kernel's restart codes, which a call returns
1585/// only to be restarted.
1586fn is_restart(errno: Errno) -> bool {
1587    [
1588        Errno::ERESTARTSYS,
1589        Errno::ERESTARTNOINTR,
1590        Errno::ERESTARTNOHAND,
1591        Errno::ERESTART_RESTARTBLOCK,
1592    ]
1593    .contains(&errno)
1594}
1595
1596/// A `clone3` call's `struct clone_args`, all the bytes it passes, if they
1597/// can be read and number at most a page, the kernel's limit (E2BIG).
1598pub(crate) fn read_clone_args<M: MemoryAccess>(
1599    memory: &M,
1600    witness: FaultWitness,
1601    clone: &syscalls::Clone3,
1602) -> Clone3Read {
1603    let Ok(page) = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }) else {
1604        return Clone3Read::Unreadable { flags: None };
1605    };
1606    let size = clone.size();
1607    let raw = clone.args().map_or(0, |args| args.as_raw());
1608    // Only a range within user memory is read: past it there is nothing to
1609    // read, and the address arithmetic of a read near the top of the address
1610    // space would overflow.
1611    let address = Addr::<u8>::from_raw(raw).filter(|_| within_user_memory(raw, size));
1612    clone3_read_decision(
1613        size,
1614        page,
1615        || {
1616            // Exactly the struct, with the guest's own access, as
1617            // copy_from_user reads it. Detcore cannot read NULL at all.
1618            let mut bytes = vec![0u8; size];
1619            memory
1620                .read_exact_with_user_access(address?, &mut bytes)
1621                .ok()
1622                .map(|()| bytes)
1623        },
1624        || range_faults(witness, raw, size),
1625        || {
1626            // Its flags, read past the protection, still say whether the
1627            // call is untraced.
1628            let mut flags = [0u8; 8];
1629            memory
1630                .read_exact(address?, &mut flags)
1631                .ok()
1632                .map(|()| u64::from_ne_bytes(flags))
1633        },
1634    )
1635}
1636
1637/// How a guest `clone3`'s `struct clone_args` of `size` bytes is taken, on
1638/// pages of `page` bytes: refused before any read (`EINVAL` under the first
1639/// version, `E2BIG` over a page, as `copy_clone_args_from_user` checks), its
1640/// bytes if `read` gets them, refused if reading it is proved to fault
1641/// (`faults`, NULL included: page 0 can be mapped), else unreadable, with
1642/// the flags `flags` gets, if any.
1643fn clone3_read_decision(
1644    size: usize,
1645    page: usize,
1646    read: impl FnOnce() -> Option<Vec<u8>>,
1647    faults: impl FnOnce() -> bool,
1648    flags: impl FnOnce() -> Option<u64>,
1649) -> Clone3Read {
1650    if size < CLONE_ARGS_SIZE_VER0 || size > page {
1651        return Clone3Read::Refused;
1652    }
1653    if let Some(bytes) = read() {
1654        return Clone3Read::Bytes(bytes);
1655    }
1656    if faults() {
1657        return Clone3Read::Refused;
1658    }
1659    Clone3Read::Unreadable { flags: flags() }
1660}
1661
1662/// A guest `clone3`'s `struct clone_args`, as Detcore read it to plan the
1663/// call.
1664#[derive(Clone, Debug, PartialEq, Eq)]
1665pub(crate) enum Clone3Read {
1666    /// Its bytes, read as `copy_from_user` reads them.
1667    Bytes(Vec<u8>),
1668    /// Every kernel refuses the call before creating a task: the struct is
1669    /// shorter than its first version (`EINVAL`) or larger than a page
1670    /// (`E2BIG`), or reading it is proved to fault (`EFAULT`,
1671    /// [`range_faults`]).
1672    Refused,
1673    /// Detcore could not read it with the guest's access, and no fault is
1674    /// proved: the kernel may read it (a page mapped writable, not
1675    /// readable). Its flags, if Detcore could read them otherwise.
1676    Unreadable { flags: Option<u64> },
1677}
1678
1679/// The end of user memory, exclusive (`TASK_SIZE_MAX`, page aligned), from
1680/// `user_ptr_max`, the highest start `access_ok` accepts for a four-byte
1681/// word. Kernels up to 6.1 check the word's end, so that start is four bytes
1682/// short of the end; later x86 kernels check only a small access's start,
1683/// against `TASK_SIZE_MAX` itself, relying on the unmapped page there. Both
1684/// round down to the end.
1685fn user_memory_end() -> Option<usize> {
1686    let page = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).ok()?;
1687    let word_end = super::threads::user_ptr_max().checked_add(std::mem::size_of::<u32>())?;
1688    Some(word_end & !(page - 1))
1689}
1690
1691/// What tells Detcore whether a guest range that it could not read with the
1692/// guest's own access must fault in the kernel too.
1693#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1694pub(crate) enum FaultWitness {
1695    /// The calling thread's host mappings (`/proc/<tid>/smaps`; the leader's
1696    /// are empty once it has exited), on a backend through whose host tasks
1697    /// a tool may read guest threads (`host_task_reads_reach_guest_threads`,
1698    /// today ptrace): see [`range_faults`].
1699    HostMaps(i32),
1700    /// Nothing: on any other backend no host `/proc` entry is known to
1701    /// describe the guest thread (KVM's and DBT's tids are their own, and no
1702    /// tool read through LiteInst's or SaBRe's host tasks is validated), and
1703    /// the backend's own failed read may not be the kernel's (an in-guest
1704    /// runtime reads its own memory with `process_vm_readv`, which a page
1705    /// mapped writable but not readable refuses while the guest's `clone3`
1706    /// reads it). A failed read proves no fault, so the call is refused by
1707    /// name rather than handed to the kernel unchecked.
1708    Unproven,
1709}
1710
1711impl FaultWitness {
1712    /// The witness for the calling thread `tid` under `backend`.
1713    pub(crate) fn for_backend(backend: &reverie::BackendCapabilities, tid: i32) -> Self {
1714        if backend.host_task_reads_reach_guest_threads {
1715            FaultWitness::HostMaps(tid)
1716        } else {
1717            FaultWitness::Unproven
1718        }
1719    }
1720}
1721
1722/// Whether some byte of the guest's `address..address + len`, which Detcore
1723/// could not read with the guest's own access, is one the kernel's
1724/// `copy_from_user` faults on too (`EFAULT`), by `witness`: from the calling
1725/// thread's mappings (`/proc/<tid>/smaps`, see [`range_faults_in`]) where
1726/// those are known to be the guest's; false when nothing is proved, as when
1727/// the mappings cannot be read or are not known to be the guest's. Reading smaps walks the page
1728/// tables of every mapping, which is why it is read only after a read with
1729/// the guest's own access has failed, a rare event.
1730pub(crate) fn range_faults(witness: FaultWitness, address: usize, len: usize) -> bool {
1731    // An address in the kernel half faults on every kernel, whatever the
1732    // backend.
1733    if in_kernel_half(address) {
1734        return true;
1735    }
1736    let tid = match witness {
1737        FaultWitness::HostMaps(tid) => tid,
1738        FaultWitness::Unproven => return false,
1739    };
1740    let Some(limit) = user_memory_end() else {
1741        return false;
1742    };
1743    let Ok(smaps) = std::fs::read_to_string(format!("/proc/{tid}/smaps")) else {
1744        return false;
1745    };
1746    range_faults_in(&smaps, limit, address, len)
1747}
1748
1749/// Whether `address..address + len` lies within user memory, where Detcore
1750/// can try to read it. Reverie's remote read splits a range at the next page
1751/// with arithmetic that overflows in the top page
1752/// (https://github.com/rrnewton/reverie/issues/1185), so nothing past user
1753/// memory is ever passed to it.
1754pub(crate) fn within_user_memory(address: usize, len: usize) -> bool {
1755    user_memory_end()
1756        .zip(address.checked_add(len))
1757        .is_some_and(|(end, last)| last <= end)
1758}
1759
1760/// Whether an address at or beyond the end of user memory faults whatever
1761/// the task: one in the kernel half does, but one that may be a tagged user
1762/// pointer proves nothing. arm64's `access_ok` untags a pointer for a task
1763/// that enabled the tagged-address ABI (`PR_SET_TAGGED_ADDR_CTRL`) by sign
1764/// extending bit 55, so bit 55 set is the kernel half and a nonzero top byte
1765/// with it clear may be a tag. x86 linear address masking would tag bits 48
1766/// (or 57) to 62, never bit 63. Detcore's prctl allowlist does not let a
1767/// guest enable tagging today, so the tagged case is a guard, not a path a
1768/// guest takes.
1769fn past_end_faults(address: usize) -> bool {
1770    in_kernel_half(address) || (cfg!(target_arch = "aarch64") && address >> 56 == 0)
1771}
1772
1773/// Whether `address` lies in the kernel half of the address space, which no
1774/// user access reaches on any kernel, tagged or not: bit 63 on x86-64, bit 55
1775/// on arm64 (where untagging sign-extends it).
1776fn in_kernel_half(address: usize) -> bool {
1777    if cfg!(target_arch = "aarch64") {
1778        address & (1 << 55) != 0
1779    } else {
1780        address & (1 << 63) != 0
1781    }
1782}
1783
1784/// Whether some byte of `address..address + len` is, by `smaps` (a
1785/// `/proc/<tid>/smaps` text), one the kernel's `copy_from_user` faults on in
1786/// every case:
1787/// - from below `limit`, the end of user memory (exclusive), to at or beyond
1788///   it;
1789/// - in a mapping without any permission (`---p`, such as a thread stack's
1790///   guard);
1791/// - in no mapping, below a mapping that does not grow down. A mapping that
1792///   grows down (`VmFlags` `gd`, a stack) is extended into the hole below it
1793///   on a fault, within the faulting task's own `RLIMIT_STACK`, which is the
1794///   guest's, not the tracer's, so such a hole proves nothing.
1795///
1796/// False when nothing is proved, as when `smaps` lists no mapping or a
1797/// record is malformed. An address at or beyond `limit` is judged by its
1798/// shape alone ([`past_end_faults`]).
1799fn range_faults_in(smaps: &str, limit: usize, address: usize, len: usize) -> bool {
1800    if address >= limit {
1801        return past_end_faults(address);
1802    }
1803    let Some(end) = address.checked_add(len.max(1)) else {
1804        return true;
1805    };
1806    if end > limit {
1807        return true;
1808    }
1809    // Each mapping's start, end, whether it permits any access (unknown for
1810    // a malformed permission field) and whether it grows down (unknown
1811    // until exactly one well-formed VmFlags record follows it).
1812    struct Mapping {
1813        start: usize,
1814        stop: usize,
1815        permitted: Option<bool>,
1816        grows_down: Option<bool>,
1817        flag_records: u32,
1818    }
1819    let mut mappings: Vec<Mapping> = Vec::new();
1820    for line in smaps.lines() {
1821        if let Some(flags) = line.strip_prefix("VmFlags:") {
1822            if let Some(last) = mappings.last_mut() {
1823                last.flag_records += 1;
1824                let flags: Vec<&str> = flags.split_whitespace().collect();
1825                let well_formed = !flags.is_empty()
1826                    && flags.iter().all(|flag| {
1827                        flag.len() == 2 && flag.bytes().all(|b| b.is_ascii_lowercase())
1828                    });
1829                last.grows_down =
1830                    (well_formed && last.flag_records == 1).then(|| flags.contains(&"gd"));
1831            }
1832            continue;
1833        }
1834        let mut fields = line.split_whitespace();
1835        let Some((start, stop)) = fields.next().and_then(|range| range.split_once('-')) else {
1836            continue;
1837        };
1838        let (Ok(start), Ok(stop), Some(perms)) = (
1839            usize::from_str_radix(start, 16),
1840            usize::from_str_radix(stop, 16),
1841            fields.next(),
1842        ) else {
1843            continue;
1844        };
1845        let perms = perms.as_bytes();
1846        let permitted = (perms.len() == 4
1847            && matches!(perms[0], b'r' | b'-')
1848            && matches!(perms[1], b'w' | b'-')
1849            && matches!(perms[2], b'x' | b'-')
1850            && matches!(perms[3], b'p' | b's'))
1851        .then(|| perms[..3] != *b"---");
1852        mappings.push(Mapping {
1853            start,
1854            stop,
1855            permitted,
1856            grows_down: None,
1857            flag_records: 0,
1858        });
1859    }
1860    if mappings.is_empty() {
1861        return false;
1862    }
1863    let mut at = address;
1864    while at < end {
1865        match mappings
1866            .iter()
1867            .find(|mapping| (mapping.start..mapping.stop).contains(&at))
1868        {
1869            Some(Mapping {
1870                permitted: Some(false),
1871                ..
1872            }) => return true,
1873            Some(Mapping {
1874                permitted: Some(true),
1875                stop,
1876                ..
1877            }) => at = *stop,
1878            Some(Mapping {
1879                permitted: None, ..
1880            }) => return false,
1881            None => {
1882                let above = mappings
1883                    .iter()
1884                    .filter(|mapping| mapping.start > at)
1885                    .min_by_key(|mapping| mapping.start);
1886                // Proved only below a mapping known not to grow down (or
1887                // below none at all).
1888                return matches!(
1889                    above,
1890                    None | Some(Mapping {
1891                        grows_down: Some(false),
1892                        ..
1893                    })
1894                );
1895            }
1896        }
1897    }
1898    false
1899}
1900
1901/// End a run that needs every ID chosen (`GuestIds::Chosen::refuse_host_ids`:
1902/// `--strict` or an explicit `--pid-base`) because a task would have, or has,
1903/// a host ID: `reason` says why. The run ends with Hermit's policy-refusal
1904/// status.
1905async fn refuse_host_id<T, G>(guest: &mut G, reason: String) -> !
1906where
1907    T: RecordOrReplay,
1908    G: Guest<Detcore<T>>,
1909{
1910    let reason = format!(
1911        "this run needs every guest process ID chosen (--strict or --pid-base), and a task would \
1912         have, or has, a host ID, which differs from run to run: {reason}"
1913    );
1914    tracing::error!("[detcore, dtid {}] {reason}", guest.thread_state().dettid);
1915    crate::tool_global::report_policy_refusal(guest, reason).await;
1916    crate::tool_global::unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT)
1917        .await
1918}
1919
1920/// End the run, whatever its guest IDs, because the guest's clone-family
1921/// call, as made, would create a task the tracer does not follow
1922/// (`CLONE_UNTRACED`), which would run outside Detcore's scheduler, and
1923/// Detcore cannot rewrite it without the flag: `reason` says why. The run
1924/// ends with Hermit's policy-refusal status.
1925async fn refuse_untraced<T, G>(guest: &mut G, reason: String) -> !
1926where
1927    T: RecordOrReplay,
1928    G: Guest<Detcore<T>>,
1929{
1930    let reason = format!(
1931        "a task the tracer does not follow (CLONE_UNTRACED) would run outside Detcore's \
1932         scheduler: {reason}"
1933    );
1934    tracing::error!("[detcore, dtid {}] {reason}", guest.thread_state().dettid);
1935    crate::tool_global::report_policy_refusal(guest, reason).await;
1936    crate::tool_global::unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT)
1937        .await
1938}
1939
1940/// What holds `id` on the host, for a refusal: the task's name and its
1941/// process, or that only a process group or session holds it.
1942pub fn holder_of(id: i32) -> String {
1943    let status = std::fs::read_to_string(format!("/proc/{id}/status")).ok();
1944    let field = |name: &str| {
1945        status.as_deref().and_then(|status| {
1946            status
1947                .lines()
1948                .find_map(|line| line.strip_prefix(name))
1949                .map(|value| value.trim().to_owned())
1950        })
1951    };
1952    match (field("Name:"), field("Tgid:")) {
1953        (Some(name), Some(tgid)) if tgid == id.to_string() => {
1954            format!("process {id} ({name})")
1955        }
1956        (Some(name), Some(tgid)) => format!("thread {id} ({name}) of process {tgid}"),
1957        _ => format!(
1958            "no live task: a process group or session of an exited process keeps ID {id}, or \
1959             its holder has exited since"
1960        ),
1961    }
1962}
1963
1964#[cfg(test)]
1965mod tests {
1966    use super::*;
1967
1968    fn fields(call: &ChosenIdCall) -> ([u64; 11], usize) {
1969        match call {
1970            ChosenIdCall::Clone3 { args, size } => {
1971                let mut fields = [0u64; 11];
1972                for (field, chunk) in fields.iter_mut().zip(args.as_chunks::<8>().0) {
1973                    *field = u64::from_ne_bytes(*chunk);
1974                }
1975                (fields, *size)
1976            }
1977            other => panic!("expected a clone3 form, got {other:?}"),
1978        }
1979    }
1980
1981    fn clone_args(flags: u64, sp: u64, ptid: u64, ctid: u64, tls: u64) -> SyscallArgs {
1982        #[cfg(target_arch = "aarch64")]
1983        let (arg3, arg4) = (tls, ctid);
1984        #[cfg(not(target_arch = "aarch64"))]
1985        let (arg3, arg4) = (ctid, tls);
1986        SyscallArgs {
1987            arg0: flags as usize,
1988            arg1: sp as usize,
1989            arg2: ptid as usize,
1990            arg3: arg3 as usize,
1991            arg4: arg4 as usize,
1992            arg5: 0,
1993        }
1994    }
1995
1996    const THREAD_FLAGS: u64 = (libc::CLONE_VM
1997        | libc::CLONE_FS
1998        | libc::CLONE_FILES
1999        | libc::CLONE_SIGHAND
2000        | libc::CLONE_THREAD
2001        | libc::CLONE_SYSVSEM
2002        | libc::CLONE_SETTLS
2003        | libc::CLONE_PARENT_SETTID
2004        | libc::CLONE_CHILD_CLEARTID) as u64;
2005
2006    /// A C library's thread creation keeps every field, in clone3's order,
2007    /// with the new stack pointer as the top of a one-byte stack.
2008    #[test]
2009    fn a_thread_creation_keeps_every_field() {
2010        let call = clone3_form(
2011            Sysno::clone,
2012            &clone_args(THREAD_FLAGS, 0x7000_1000, 0x5000, 0x5000, 0x6000),
2013            None,
2014        );
2015        assert_eq!(
2016            fields(&call),
2017            (
2018                [
2019                    THREAD_FLAGS,
2020                    0x5000,
2021                    0x5000,
2022                    0x5000,
2023                    0,
2024                    0x7000_0fff,
2025                    1,
2026                    0x6000,
2027                    0,
2028                    0,
2029                    0
2030                ],
2031                CLONE_ARGS_SIZE_VER1
2032            )
2033        );
2034    }
2035
2036    /// The exit signal leaves the flags; beside CLONE_THREAD or CLONE_PARENT
2037    /// it becomes 0, which copy_process ignores anyway.
2038    #[test]
2039    fn the_exit_signal_moves_out_of_the_flags() {
2040        let sigchld = libc::SIGCHLD as u64;
2041        let (process, _) = fields(&clone3_form(
2042            Sysno::clone,
2043            &clone_args(sigchld, 0, 0, 0, 0),
2044            None,
2045        ));
2046        assert_eq!(
2047            (process[0], process[4], process[5], process[6]),
2048            (0, sigchld, 0, 0)
2049        );
2050        for shared in [
2051            libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2052            libc::CLONE_PARENT,
2053        ] {
2054            let (fields, _) = fields(&clone3_form(
2055                Sysno::clone,
2056                &clone_args(shared as u64 | sigchld, 0, 0, 0, 0),
2057                None,
2058            ));
2059            assert_eq!((fields[0], fields[4]), (shared as u64, 0));
2060        }
2061    }
2062
2063    #[test]
2064    fn invalid_original_signals_are_not_normalized_into_valid_clones() {
2065        for shared in [
2066            libc::CLONE_PARENT,
2067            libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2068        ] {
2069            for signal in [65, 128, 255] {
2070                let call = clone3_form(
2071                    Sysno::clone,
2072                    &clone_args(shared as u64 | signal, 0, 0, 0, 0),
2073                    None,
2074                );
2075                assert_eq!(call, ChosenIdCall::Invalid(Errno::EINVAL));
2076            }
2077            for signal in [0, libc::SIGCHLD as u64, 64] {
2078                let (valid, _) = fields(&clone3_form(
2079                    Sysno::clone,
2080                    &clone_args(shared as u64 | signal, 0, 0, 0, 0),
2081                    None,
2082                ));
2083                assert_eq!((valid[0], valid[4]), (shared as u64, 0));
2084            }
2085        }
2086    }
2087
2088    /// Bits above the low 32 never reach the kernel from clone; neither does
2089    /// CLONE_DETACHED, which clone3 refuses.
2090    #[test]
2091    fn high_bits_and_clone_detached_are_dropped() {
2092        let flags = (1u64 << 40) | libc::CLONE_DETACHED as u64 | libc::CLONE_FILES as u64;
2093        let (fields, _) = fields(&clone3_form(
2094            Sysno::clone,
2095            &clone_args(flags, 0, 0, 0, 0),
2096            None,
2097        ));
2098        assert_eq!(fields[0], libc::CLONE_FILES as u64);
2099    }
2100
2101    /// A call every kernel refuses before creating a task goes unchanged;
2102    /// CLONE_PIDFD beside CLONE_THREAD, which only kernels before 6.9 refuse,
2103    /// is rewritten, and its clone3 fails as the call would. An exit signal
2104    /// clone3 cannot carry is inexpressible, except beside CLONE_THREAD or
2105    /// CLONE_PARENT, where original-signal validation must precede conversion.
2106    #[test]
2107    fn refused_and_inexpressible_calls() {
2108        let detached = (libc::CLONE_PIDFD | libc::CLONE_DETACHED) as u64;
2109        assert_eq!(
2110            clone3_form(Sysno::clone, &clone_args(detached, 0, 0, 0, 0), None),
2111            ChosenIdCall::Unchanged
2112        );
2113        let pidfd_thread =
2114            (libc::CLONE_PIDFD | libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM) as u64;
2115        let (pidfd_fields, _) = fields(&clone3_form(
2116            Sysno::clone,
2117            &clone_args(pidfd_thread, 0, 0, 0, 0),
2118            None,
2119        ));
2120        assert_eq!(pidfd_fields[0], pidfd_thread);
2121        assert!(matches!(
2122            clone3_form(Sysno::clone, &clone_args(0x80, 0, 0, 0, 0), None),
2123            ChosenIdCall::Inexpressible(_)
2124        ));
2125        let thread = (libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM) as u64;
2126        assert_eq!(
2127            clone3_form(Sysno::clone, &clone_args(thread | 0x80, 0, 0, 0, 0), None),
2128            ChosenIdCall::Invalid(Errno::EINVAL)
2129        );
2130
2131        // Known no-child combinations go to the original kernel even if
2132        // their stack cannot be converted. UNTRACED still takes the
2133        // existing traced-original path, retaining all other registers.
2134        for untraced in [0, libc::CLONE_UNTRACED] {
2135            for invalid in [
2136                libc::CLONE_THREAD,
2137                libc::CLONE_THREAD | libc::CLONE_VM,
2138                libc::CLONE_SIGHAND,
2139                libc::CLONE_THREAD | libc::CLONE_SIGHAND,
2140            ] {
2141                let args = clone_args(
2142                    (invalid | untraced) as u64 | libc::SIGCHLD as u64,
2143                    1,
2144                    0x1234,
2145                    0x2345,
2146                    0x3456,
2147                );
2148                assert_eq!(
2149                    clone3_form(Sysno::clone, &args, None),
2150                    ChosenIdCall::Unchanged
2151                );
2152                let mut expected = args;
2153                expected.arg0 &= !(libc::CLONE_UNTRACED as usize);
2154                assert_eq!(traced_legacy_clone(args), expected);
2155            }
2156            for valid in [
2157                0,
2158                libc::CLONE_VM,
2159                libc::CLONE_SIGHAND | libc::CLONE_VM,
2160                libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2161            ] {
2162                let flags = (valid | untraced) as u64;
2163                let (converted, _) = fields(&clone3_form(
2164                    Sysno::clone,
2165                    &clone_args(flags, 0, 0, 0, 0),
2166                    None,
2167                ));
2168                assert_eq!(converted[0], flags);
2169            }
2170        }
2171        // Original-signal validation still precedes this no-child route.
2172        assert_eq!(
2173            clone3_form(
2174                Sysno::clone,
2175                &clone_args(libc::CLONE_THREAD as u64 | 65, 1, 0, 0, 0),
2176                None
2177            ),
2178            ChosenIdCall::Invalid(Errno::EINVAL)
2179        );
2180        // The legacy-only proof does not alter clone3 size or set_tid handling.
2181        let mut guest_args = [0; CLONE_ARGS_SIZE_VER2];
2182        guest_args[..8].copy_from_slice(&(libc::CLONE_SIGHAND as u64).to_ne_bytes());
2183        let (converted, _) = fields(&clone3_form(
2184            Sysno::clone3,
2185            &clone_args(0, 0, 0, 0, 0),
2186            Some(&guest_args),
2187        ));
2188        assert_eq!(converted[0], libc::CLONE_SIGHAND as u64);
2189    }
2190
2191    /// clone's last two arguments are the child TID address and the TLS on
2192    /// x86-64, and the other way round on arm64 (`CONFIG_CLONE_BACKWARDS`):
2193    /// each order puts each in its clone3 field, whichever architecture runs
2194    /// the test.
2195    #[test]
2196    fn both_clone_argument_orders() {
2197        // clone(flags, newsp, parent_tidptr, arg3, arg4).
2198        let args = SyscallArgs::new(THREAD_FLAGS as usize, 0, 0x5000, 0x3333, 0x4444, 0);
2199        for (order, child_tid, tls) in [
2200            (CloneArgOrder::ChildTidThenTls, 0x3333, 0x4444),
2201            (CloneArgOrder::TlsThenChildTid, 0x4444, 0x3333),
2202        ] {
2203            let (fields, _) = fields(&LegacyClone::of_clone(&args, order).as_clone3());
2204            // clone_args: flags, pidfd, child_tid, parent_tid, exit_signal,
2205            // stack, stack_size, tls.
2206            assert_eq!((fields[2], fields[3], fields[7]), (child_tid, 0x5000, tls));
2207        }
2208        #[cfg(target_arch = "aarch64")]
2209        assert_eq!(CloneArgOrder::NATIVE, CloneArgOrder::TlsThenChildTid);
2210        #[cfg(target_arch = "x86_64")]
2211        assert_eq!(CloneArgOrder::NATIVE, CloneArgOrder::ChildTidThenTls);
2212    }
2213
2214    /// fork and vfork are the kernel's fixed argument sets.
2215    #[cfg(not(target_arch = "aarch64"))]
2216    #[test]
2217    fn fork_and_vfork() {
2218        let none = clone_args(0, 0, 0, 0, 0);
2219        let (fork, _) = fields(&clone3_form(Sysno::fork, &none, None));
2220        assert_eq!((fork[0], fork[4]), (0, libc::SIGCHLD as u64));
2221        let (vfork, _) = fields(&clone3_form(Sysno::vfork, &none, None));
2222        assert_eq!(
2223            (vfork[0], vfork[4]),
2224            (
2225                (libc::CLONE_VFORK | libc::CLONE_VM) as u64,
2226                libc::SIGCHLD as u64
2227            )
2228        );
2229    }
2230
2231    /// A guest's clone3 keeps its bytes and grows to the size that carries
2232    /// set_tid. One that sets set_tid itself (also within a short struct,
2233    /// whose bytes from 64 are set_tid's) is the guest's choice. One the
2234    /// kernel refuses for its size or bytes (under 64; over 88 with a nonzero
2235    /// byte past them; unreadable) goes unchanged, and a longer one whose
2236    /// extra bytes are zero is its first 88.
2237    #[test]
2238    fn a_guest_clone3() {
2239        let none = SyscallArgs::new(0, 0, 0, 0, 0, 0);
2240        let mut ver0 = [0u8; CLONE_ARGS_SIZE_VER0];
2241        ver0[..8].copy_from_slice(&(libc::CLONE_FILES as u64).to_ne_bytes());
2242        let (ver0_fields, size) = fields(&clone3_form(Sysno::clone3, &none, Some(&ver0)));
2243        assert_eq!(
2244            (ver0_fields[0], size),
2245            (libc::CLONE_FILES as u64, CLONE_ARGS_SIZE_VER1)
2246        );
2247
2248        let mut own = [0u8; CLONE_ARGS_SIZE_VER2];
2249        own[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&0x1000u64.to_ne_bytes());
2250        own[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8].copy_from_slice(&1u64.to_ne_bytes());
2251        assert_eq!(
2252            clone3_form(Sysno::clone3, &none, Some(&own)),
2253            ChosenIdCall::GuestChosen {
2254                args: own,
2255                size: CLONE_ARGS_SIZE_VER2
2256            }
2257        );
2258        let mut short = [0u8; 70];
2259        short[SET_TID_OFFSET] = 1;
2260        assert!(matches!(
2261            clone3_form(Sysno::clone3, &none, Some(&short)),
2262            ChosenIdCall::GuestChosen { .. }
2263        ));
2264        // Shorter than the first version: EINVAL from every kernel.
2265        assert_eq!(
2266            clone3_form(Sysno::clone3, &none, Some(&[0u8; 32])),
2267            ChosenIdCall::Unchanged
2268        );
2269        // A nonzero tail a newer kernel may read, or a struct Detcore could
2270        // not read, is not proved refused: it takes the host-ID path.
2271        for unproven in [Some(&[[0u8; 96], [1u8; 96]].concat()[..]), None] {
2272            assert!(matches!(
2273                clone3_form(Sysno::clone3, &none, unproven),
2274                ChosenIdCall::Inexpressible(_)
2275            ));
2276        }
2277        assert!(matches!(
2278            clone3_form(Sysno::getpid, &none, None),
2279            ChosenIdCall::Inexpressible(_)
2280        ));
2281        let mut long = [0u8; 96];
2282        long[..8].copy_from_slice(&(libc::CLONE_FILES as u64).to_ne_bytes());
2283        let (long_fields, size) = fields(&clone3_form(Sysno::clone3, &none, Some(&long)));
2284        assert_eq!(
2285            (long_fields[0], size),
2286            (libc::CLONE_FILES as u64, CLONE_ARGS_SIZE_VER2)
2287        );
2288    }
2289
2290    #[test]
2291    fn set_tid_is_filled_in() {
2292        let args = ChosenIdCall::with_set_tid([0u8; CLONE_ARGS_SIZE_VER2], 0x1234, 2);
2293        assert_eq!(
2294            u64::from_ne_bytes(args[SET_TID_OFFSET..SET_TID_OFFSET + 8].try_into().unwrap()),
2295            0x1234
2296        );
2297        assert_eq!(
2298            u64::from_ne_bytes(
2299                args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8]
2300                    .try_into()
2301                    .unwrap()
2302            ),
2303            2
2304        );
2305    }
2306
2307    /// The depth of the PID namespace a call creates its task in, out of
2308    /// Hermit's: this test's own namespace is Hermit's here, so 0, and one
2309    /// more with CLONE_NEWPID.
2310    #[test]
2311    fn a_pid_namespace_depth_counts_from_hermits() {
2312        let own = unsafe { libc::gettid() };
2313        assert_eq!(super::pid_namespace_depth(own, 0), Some(0));
2314        assert_eq!(
2315            super::pid_namespace_depth(own, libc::CLONE_NEWPID as u64),
2316            Some(1)
2317        );
2318    }
2319
2320    /// A restarted call keeps its ID only for the same call: the same number,
2321    /// size and normalized arguments, compared byte for byte.
2322    #[test]
2323    fn a_restarted_clone_keeps_its_id_only_for_the_same_call() {
2324        use super::RestartedCloneId;
2325        let args = [7u8; super::CLONE_ARGS_SIZE_VER2];
2326        let mut other = args;
2327        other[0] ^= 1;
2328        let call = RestartedCloneId::call(Sysno::clone, &args, 64);
2329        assert_eq!(call, RestartedCloneId::call(Sysno::clone, &args, 64));
2330        assert_ne!(call, RestartedCloneId::call(Sysno::clone, &other, 64));
2331        assert_ne!(call, RestartedCloneId::call(Sysno::clone3, &args, 64));
2332        assert_ne!(call, RestartedCloneId::call(Sysno::clone, &args, 88));
2333    }
2334
2335    /// A range sample cannot establish that EINVAL is the original call's
2336    /// error; the handler must obtain that call's own result on fallback.
2337    #[test]
2338    fn an_added_id_can_also_make_the_rewrite_invalid() {
2339        use super::SetTidFailure;
2340        assert_eq!(
2341            super::set_tid_failure(Errno::EEXIST),
2342            Some(SetTidFailure::Taken)
2343        );
2344        assert_eq!(
2345            super::set_tid_failure(Errno::EPERM),
2346            Some(SetTidFailure::NotPermitted)
2347        );
2348        assert_eq!(
2349            super::set_tid_failure(Errno::EINVAL),
2350            Some(SetTidFailure::PossiblyOutOfRange)
2351        );
2352        for errno in [
2353            Errno::EFAULT,
2354            Errno::EBADF,
2355            Errno::EACCES,
2356            Errno::EAGAIN,
2357            Errno::EINTR,
2358        ] {
2359            assert_eq!(super::set_tid_failure(errno), None, "{errno}");
2360        }
2361        for maximum in [None, Some(100)] {
2362            for id in [i32::MIN, -1, 0] {
2363                assert!(super::chosen_id_range_refusal(id, maximum).is_some());
2364            }
2365            assert_eq!(super::chosen_id_range_refusal(99, maximum), None);
2366        }
2367        assert!(super::chosen_id_range_refusal(100, Some(100)).is_some());
2368        assert_eq!(super::chosen_id_range_refusal(100, None), None);
2369    }
2370
2371    /// A guest's own set_tid array is read only where the kernel would: an
2372    /// address and a size both set, at most MAX_PID_NS_LEVEL entries.
2373    #[test]
2374    fn a_guest_set_tid_array_has_the_shape_the_kernel_reads() {
2375        let shape = |address: u64, size: u64| {
2376            let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
2377            args[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&address.to_ne_bytes());
2378            args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8].copy_from_slice(&size.to_ne_bytes());
2379            super::guest_set_tid_shape(&args)
2380        };
2381        assert_eq!(shape(0x1000, 1), Some((0x1000, 1)));
2382        assert_eq!(shape(0x1000, 32), Some((0x1000, 32)));
2383        assert_eq!(shape(0x1000, 0), None);
2384        assert_eq!(shape(0, 1), None);
2385        assert_eq!(shape(0x1000, 33), None);
2386    }
2387
2388    /// The rewrite drops CLONE_UNTRACED and nothing else.
2389    #[test]
2390    fn the_rewrite_drops_only_clone_untraced() {
2391        let flags = (libc::CLONE_VM | libc::CLONE_UNTRACED | libc::CLONE_SETTLS) as u64;
2392        let mut args = [9u8; CLONE_ARGS_SIZE_VER2];
2393        args[..8].copy_from_slice(&flags.to_ne_bytes());
2394        let cleared = super::without_untraced(args);
2395        assert_eq!(
2396            u64::from_ne_bytes(cleared[..8].try_into().unwrap()),
2397            (libc::CLONE_VM | libc::CLONE_SETTLS) as u64
2398        );
2399        assert_eq!(cleared[8..], args[8..]);
2400    }
2401
2402    /// A legacy clone whose new stack pointer clone3 cannot express (1, or
2403    /// beyond user memory) is inexpressible rather than rewritten into an
2404    /// EINVAL of clone3's own.
2405    #[test]
2406    fn a_stack_pointer_clone3_cannot_express_is_inexpressible() {
2407        for sp in [1usize, (super::USER_ADDRESS_LIMIT + 1) as usize] {
2408            let args = SyscallArgs::new(libc::SIGCHLD as usize, sp, 0, 0, 0, 0);
2409            assert!(
2410                matches!(
2411                    clone3_form(Sysno::clone, &args, None),
2412                    ChosenIdCall::Inexpressible(why) if why.contains("stack pointer")
2413                ),
2414                "{sp:#x}"
2415            );
2416        }
2417        let args = SyscallArgs::new(libc::SIGCHLD as usize, 0x7fff_0000_1000, 0, 0, 0, 0);
2418        assert!(matches!(
2419            clone3_form(Sysno::clone, &args, None),
2420            ChosenIdCall::Clone3 { .. }
2421        ));
2422    }
2423
2424    /// A failed read proves EFAULT only for a range that starts in user
2425    /// memory and crosses its end, or a byte in a mapping without
2426    /// permissions, or one in a hole below a mapping known not to grow
2427    /// down.
2428    #[test]
2429    fn only_a_definite_fault_is_taken_as_the_kernels_refusal() {
2430        let smaps = "\
24311000-2000 rw-p 00000000 00:00 0
2432VmFlags: rd wr mr mw me ac
24332000-3000 ---p 00000000 00:00 0
2434VmFlags: mr mw me ac
24355000-6000 -w-p 00000000 00:00 0
2436VmFlags: wr mr mw me ac
24379000-a000 rw-p 00000000 00:00 0 [stack]
2438VmFlags: rd wr mr mw me gd ac
2439";
2440        let limit = 0x10000;
2441        let faults =
2442            |address: usize, len: usize| super::range_faults_in(smaps, limit, address, len);
2443        // Mapped and permitted, even writable only: nothing is proved.
2444        assert!(!faults(0x1ff0, 0x10));
2445        assert!(!faults(0x5000, 88));
2446        // A mapping without permissions, entered from a readable one.
2447        assert!(faults(0x1ff8, 0x10));
2448        // A hole below a mapping that does not grow down.
2449        assert!(faults(0x4000, 8));
2450        assert!(faults(0x1000 - 4, 8));
2451        // A hole below a stack, which the guest's fault may grow into.
2452        assert!(!faults(0x8ff8, 8));
2453        // The end of user memory, exclusive, for 1, 4, 8 and 88 bytes.
2454        for len in [1, 4, 8, 88] {
2455            assert!(!super::range_faults_in(
2456                "f000-10000 -w-p 00000000 00:00 0\nVmFlags: wr\n",
2457                limit,
2458                limit - len,
2459                len
2460            ));
2461            // One byte more crosses the end (a single byte cannot, as its
2462            // address would be the end itself).
2463            if len > 1 {
2464                assert!(faults(limit - len + 1, len));
2465            }
2466        }
2467        // At or beyond the end, an address in the kernel half faults, but
2468        // one that may be a tagged user pointer proves nothing.
2469        assert!(faults(usize::MAX - 4095, 88));
2470        assert!(faults(usize::MAX - 2, 8));
2471        assert!(!faults(0x0f00_0000_0000_4000, 8));
2472        // Untagged, just past the end: arm64 faults; x86 cannot tell it from
2473        // a linear-address-masked pointer.
2474        assert_eq!(faults(limit, 8), cfg!(target_arch = "aarch64"));
2475        // No mappings listed: nothing is proved.
2476        assert!(!super::range_faults_in("", limit, 0x4000, 8));
2477        // Only ptrace's guest threads are read through their host tasks; on
2478        // every other backend a failed read proves nothing.
2479        use reverie::BackendCapabilities as B;
2480        for backend in [B::PTRACE, B::E9PATCH] {
2481            assert_eq!(
2482                super::FaultWitness::for_backend(&backend, 7),
2483                super::FaultWitness::HostMaps(7)
2484            );
2485        }
2486        for backend in [B::LITEINST_IN_GUEST, B::SABRE, B::DBT, B::KVM] {
2487            assert_eq!(
2488                super::FaultWitness::for_backend(&backend, 7),
2489                super::FaultWitness::Unproven
2490            );
2491        }
2492        // Except in the kernel half, which faults on every backend.
2493        assert!(!super::range_faults(
2494            super::FaultWitness::Unproven,
2495            0x4000,
2496            8
2497        ));
2498        assert!(super::range_faults(
2499            super::FaultWitness::Unproven,
2500            usize::MAX - 4095,
2501            88
2502        ));
2503        // A mapping whose VmFlags are missing might grow down: a hole below
2504        // it proves nothing. Below no mapping at all, it does.
2505        let unflagged = "5000-6000 rw-p 00000000 00:00 0\n";
2506        assert!(!super::range_faults_in(unflagged, limit, 0x4000, 8));
2507        assert!(super::range_faults_in(unflagged, limit, 0x7000, 8));
2508        // Page zero, when mapped with some permission, is read like any other
2509        // page.
2510        assert!(!super::range_faults_in(
2511            "0-1000 rw-p 00000000 00:00 0\nVmFlags: rd wr\n",
2512            limit,
2513            0,
2514            88
2515        ));
2516        // An empty, malformed or repeated VmFlags record, or a malformed
2517        // permission field, is no evidence.
2518        for record in [
2519            "VmFlags:\n",
2520            "VmFlags: rd wr GD\n",
2521            "VmFlags: rd wr\nVmFlags: rd wr\n",
2522        ] {
2523            let smaps = format!("5000-6000 rw-p 00000000 00:00 0\n{record}");
2524            assert!(
2525                !super::range_faults_in(&smaps, limit, 0x4000, 8),
2526                "{record:?}"
2527            );
2528        }
2529        assert!(!super::range_faults_in(
2530            "5000-6000 --- 00000000 00:00 0\nVmFlags: mr\n",
2531            limit,
2532            0x5000,
2533            8
2534        ));
2535        // The end of user memory is a page boundary, at most four bytes past
2536        // the last four-byte word access_ok accepts.
2537        let end = super::user_memory_end().unwrap();
2538        let word = super::super::threads::user_ptr_max();
2539        assert_eq!(end % 4096, 0, "{end:#x}");
2540        assert!(word < end + 4096 && end <= word + 4, "{word:#x} {end:#x}");
2541    }
2542
2543    /// A clone3 struct is refused before any read for its size, read when it
2544    /// can be, refused when reading it is proved to fault (NULL included),
2545    /// and otherwise unreadable, with whatever flags could be read.
2546    #[test]
2547    fn a_clone3_struct_is_refused_only_when_every_kernel_refuses_it() {
2548        use super::Clone3Read;
2549        let decide = |size: usize, read: Option<Vec<u8>>, faults: bool, flags: Option<u64>| {
2550            super::clone3_read_decision(size, 4096, || read, || faults, || flags)
2551        };
2552        assert_eq!(
2553            decide(32, Some(vec![0; 32]), false, None),
2554            Clone3Read::Refused
2555        );
2556        assert_eq!(decide(8192, None, false, None), Clone3Read::Refused);
2557        assert_eq!(
2558            decide(88, Some(vec![1; 88]), false, None),
2559            Clone3Read::Bytes(vec![1; 88])
2560        );
2561        // NULL, or any other address: unread, and a fault proved or not.
2562        assert_eq!(decide(88, None, true, None), Clone3Read::Refused);
2563        assert_eq!(
2564            decide(88, None, false, None),
2565            Clone3Read::Unreadable { flags: None }
2566        );
2567        assert_eq!(
2568            decide(88, None, false, Some(7)),
2569            Clone3Read::Unreadable { flags: Some(7) }
2570        );
2571    }
2572
2573    /// A legacy clone issued without CLONE_UNTRACED keeps every other bit
2574    /// and argument.
2575    #[test]
2576    fn a_traced_legacy_clone_drops_only_clone_untraced() {
2577        let flags = (libc::CLONE_UNTRACED | libc::CLONE_PIDFD | libc::CLONE_DETACHED) as usize
2578            | libc::SIGCHLD as usize;
2579        let args = SyscallArgs::new(flags, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000);
2580        let traced = super::traced_legacy_clone(args);
2581        assert_eq!(traced.arg0, flags & !(libc::CLONE_UNTRACED as usize));
2582        assert_eq!(
2583            (
2584                traced.arg1,
2585                traced.arg2,
2586                traced.arg3,
2587                traced.arg4,
2588                traced.arg5
2589            ),
2590            (0x1000, 0x2000, 0x3000, 0x4000, 0x5000)
2591        );
2592    }
2593}