Skip to main content

detcore/syscalls/
signal.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//! System calls dealing with signals.
10
11use std::time::Duration;
12
13use detcore_model::schedule::SigWrapper;
14use nix::sys::signal::Signal;
15use reverie::Errno;
16use reverie::Error;
17use reverie::Guest;
18use reverie::Stack;
19use reverie::syscalls;
20use reverie::syscalls::Addr;
21use reverie::syscalls::AddrMut;
22use reverie::syscalls::MemoryAccess;
23use reverie::syscalls::Sysno;
24use reverie::syscalls::Timespec;
25use tracing::info;
26
27use crate::Detcore;
28use crate::fd::FdType;
29use crate::record_or_replay::RecordOrReplay;
30use crate::resources::InGuestSleepSigalrm;
31use crate::resources::Permission;
32use crate::resources::ResourceID;
33use crate::resources::Resources;
34use crate::resources::SigalrmSleepDisposition;
35use crate::scheduler::HostTimedSignalScope;
36use crate::scheduler::OwnProcessSignalInputs;
37use crate::scheduler::SiblingSignalState;
38use crate::scheduler::discarded_at_send::DiscardToken;
39use crate::scheduler::discarded_at_send::DiscardedSignalAnswer;
40use crate::scheduler::discarded_at_send::DiscardedSignalControl;
41use crate::scheduler::discarded_at_send::QueueId;
42use crate::scheduler::pid_namespaces::GuestPidAnswer;
43use crate::scheduler::process_signal_receiver::ProcessSignalReceiver;
44use crate::scheduler::signal_lists::KernelSiginfo;
45use crate::syscalls::guest_siginfo::GuestSiginfo;
46use crate::syscalls::helpers::REAL_TIME_SIGNALS;
47use crate::syscalls::helpers::act_on_pending_real_time;
48use crate::syscalls::helpers::observe_under_mask;
49use crate::syscalls::helpers::pending_real_time_among;
50use crate::syscalls::helpers::retry_nonblocking_syscall_with_timeout;
51use crate::syscalls::helpers::unblocked_by;
52use crate::syscalls::threads::KERNEL_SIGSET_SIZE;
53use crate::syscalls::threads::KernelSigaction;
54use crate::syscalls::threads::KernelSigset;
55use crate::syscalls::threads::kernel_sigset_bit;
56use crate::syscalls::threads::read_thread_wait_signal_state;
57use crate::tool_global::ResumeStatus;
58use crate::tool_global::SigalrmControl;
59use crate::tool_global::alarm_remaining;
60use crate::tool_global::commit_sigkill;
61use crate::tool_global::host_timed_signals;
62use crate::tool_global::names_a_retired_exited_leader;
63use crate::tool_global::notify_signal_pending;
64use crate::tool_global::own_process_signal_inputs;
65use crate::tool_global::record_host_timed_signals;
66use crate::tool_global::refuse_sigalrm;
67use crate::tool_global::register_alarm;
68use crate::tool_global::registered_process_of;
69use crate::tool_global::reserve_sigkill;
70use crate::tool_global::reserve_sigkill_group;
71use crate::tool_global::resolve_kill_targets_and_exit;
72use crate::tool_global::resource_request;
73use crate::tool_global::set_sigchld_disposition;
74use crate::tool_global::set_sigtimedwait_set;
75use crate::tool_global::sigalrm_refuses;
76use crate::tool_global::signal_target_process;
77use crate::tool_global::thread_observe_time;
78use crate::types::DetPid;
79use crate::types::DetTid;
80use crate::types::LogicalTime;
81
82/// Signals hermit takes from the guest's namespace, and what a guest loses.
83///
84/// ⚠️ ENUMERATED BY MEASUREMENT, NOT BY READING (2026-08-25). Every signal 1..64
85/// was run under two delivery paths -- self-directed `raise` and a sibling
86/// thread's `pthread_kill` -- with a native run as the control for each. Exactly
87/// TWO differ from native, and they fail in different ways at different points:
88///
89///   SIGSTKFLT (16)  `rt_sigaction` is NO-OPED below, so the handler is never
90///                   installed and the default disposition terminates the guest:
91///                   observed exit 144 (= 128 + 16).
92///   SIGTRAP    (5)  `rt_sigaction` passes through and the handler IS installed,
93///                   but ptrace consumes every SIGTRAP (syscall stops, seccomp
94///                   stops, breakpoints), so the handler never runs. ⚠️ THE GUEST
95///                   THEN EXITS 0 WITH NO DIAGNOSTIC AT ALL -- a clean pass that
96///                   behaved differently from native, which no cell can catch.
97///
98/// Everything else in 1..31 matches native exactly; 9/19 and 32/33 refuse
99/// `sigaction` natively too and are not hermit's. Realtime 34..64 are
100/// delivered and handled like any other signal under ptrace
101/// (<https://github.com/rrnewton/reverie/issues/207>); they are not
102/// appropriation.
103const APPROPRIATED_SIGNALS: [(i32, &str); 2] = [
104    (
105        libc::SIGTRAP,
106        "ptrace consumes every SIGTRAP (syscall/seccomp stops, breakpoints)",
107    ),
108    (
109        libc::SIGSTKFLT,
110        "reverie uses it as PERF_EVENT_SIGNAL, the PMU preemption timer",
111    ),
112];
113
114/// Say, once per installation, that a guest handler will never run.
115///
116/// ⚠️ WHY A DIAGNOSTIC AND NOT A REFUSAL. Returning `EINVAL` from `sigaction`
117/// was considered and deliberately rejected for SIGSTKFLT -- see the comment at
118/// the no-op below: the Go runtime registers that handler, and refusing would
119/// break every Go guest at startup. That reasoning generalises: installing a
120/// handler defensively is common, actually raising these signals is rare, so
121/// refusal breaks MORE programs than the current behaviour. The contract
122/// question of what a guest is owed here is genuinely open.
123///
124/// What is NOT open is that hermit currently says NOTHING. This line commits to
125/// no policy, breaks no conforming program, and turns a silent wrong answer into
126/// a visible one -- the same reasoning as the `HERMIT_INTERNAL_FAILURE` marker.
127/// The decision, separated from the reporting so a test can exercise THIS and
128/// not a copy of it. A unit test that re-implements a predicate keeps passing
129/// when the real one is gutted -- measured in this project's own pipe work,
130/// where deleting the production wiring left every unit test green.
131fn appropriated_reason(signum: i32, handler: u64) -> Option<&'static str> {
132    // SIG_DFL (0) and SIG_IGN (1) lose nothing: the guest is not asking to be
133    // called back, so there is no expectation to disappoint.
134    if handler <= 1 {
135        return None;
136    }
137    APPROPRIATED_SIGNALS
138        .iter()
139        .find(|(s, _)| *s == signum)
140        .map(|(_, why)| *why)
141}
142
143fn warn_appropriated_signal(signum: i32, handler: u64) {
144    if let Some(why) = appropriated_reason(signum, handler) {
145        tracing::warn!(
146            "HERMIT_APPROPRIATED_SIGNAL signum={signum} effect=handler-installed-but-never-invoked reason={why}"
147        );
148    }
149}
150
151// NB: the kernel uses an eight-byte signal mask in its raw signal syscalls on
152// x86_64. `libc::sigset_t` is the 128-byte userspace wrapper type and must not
153// be used to access these buffers. See:
154// https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/signal.h#L75
155fn validate_kernel_sigset_size(sigsetsize: usize) -> Result<(), Errno> {
156    if sigsetsize == KERNEL_SIGSET_SIZE {
157        Ok(())
158    } else {
159        Err(Errno::EINVAL)
160    }
161}
162
163/// The mask Linux installs when a call names `mask` as the signal mask to sleep
164/// under (`rt_sigsuspend`, `ppoll`, `pselect6`): `set_current_blocked` drops
165/// `SIGKILL` and `SIGSTOP`, which can never be blocked.
166pub(crate) fn kernel_installed_signal_mask(mask: KernelSigset) -> KernelSigset {
167    let unblockable = (1_u64 << (libc::SIGKILL - 1)) | (1_u64 << (libc::SIGSTOP - 1));
168    mask & !unblockable
169}
170
171fn without_perf_event_signal(mask: KernelSigset) -> KernelSigset {
172    let bit = (reverie::PERF_EVENT_SIGNAL as u32) - 1;
173    mask & !(1_u64 << bit)
174}
175
176/// The result of a kernel-access probe that is a call's first injection,
177/// made before anything of the call happens (`read_kernel_sigset`,
178/// `read_kernel_sigaction`, pselect6's wrapper validation), with the
179/// backend's not-run report, `-ERESTARTSYS` (which no probe returns
180/// itself), turned into `-ERESTARTNOINTR`. The report means a signal reached
181/// the thread at the call's entry and stopped the probe before its `syscall`
182/// instruction; the backend holds it. The call then restarts once the signal
183/// is delivered, as if the signal had come before the call: its handler runs
184/// under the thread's own mask, and the call runs. Ending the call with the
185/// not-run report instead returned EINTR, from a wait whose temporary mask
186/// the handler never saw and from calls that never fail with EINTR
187/// (`rt_sigprocmask`, `rt_sigaction`), which no Linux run produces
188/// (https://github.com/rrnewton/hermit/issues/4526).
189///
190/// Only for a call's first injection. A probe after an earlier injection
191/// keeps the not-run report, since a restart would repeat that injection
192/// (pselect6's mask read after its wrapper validation and its observation of
193/// a pending real-time timer expiration, for example), and so does one after
194/// a scheduler request, which changes scheduler state (a record/replay
195/// zero-timeout ppoll). Where an earlier phase injects only sometimes, the
196/// caller says whether it did or can: epoll_pwait and epoll_pwait2's
197/// observation (`observe_under_temporary_mask`, which reports whether it
198/// injected), and a run-mode zero-timeout ppoll's publication of virtual
199/// timerfd expirations, which can inject only for a process holding an armed
200/// virtual timerfd. Record/replay pselect6's entry read is always the call's
201/// first injection (https://github.com/rrnewton/hermit/issues/4526).
202pub(crate) fn restart_if_first_probe_not_run<V>(result: Result<V, Error>) -> Result<V, Error> {
203    match result {
204        Err(Error::Errno(Errno::ERESTARTSYS)) => Err(Errno::ERESTARTNOINTR.into()),
205        other => other,
206    }
207}
208
209/// Read one raw kernel signal mask while preserving the kernel's user-access check.
210///
211/// `safeptrace::Stopped::read` deliberately uses `PTRACE_PEEKDATA` for reads of
212/// eight bytes or less. That operation can read a `PROT_NONE` page, unlike the
213/// kernel's `copy_from_user`, so using `MemoryAccess::read_value` alone would
214/// turn an `EFAULT` from a raw signal syscall into success. An invalid `how`
215/// value makes `rt_sigprocmask` copy exactly the kernel-sized input and then
216/// return `EINVAL` without changing the mask. Use that as a permission probe,
217/// then read the already-validated word while the guest is stopped.
218/// The red zone below the stack pointer, which a leaf function may use and an
219/// injected call therefore must not.
220pub(super) const STACK_RED_ZONE: usize = crate::arch::STACK_RED_ZONE;
221
222/// The two eight-byte scratch cells `handle_rt_sigsuspend` places below the
223/// red zone: the rt_sigpending result and the copy of the call's mask.
224const SCRATCH_CELLS_SIZE: usize = 2 * std::mem::size_of::<u64>();
225
226pub(super) async fn read_kernel_sigset<G, T>(
227    guest: &mut G,
228    address: Addr<'_, libc::sigset_t>,
229) -> Result<KernelSigset, Error>
230where
231    G: Guest<Detcore<T>>,
232    T: RecordOrReplay,
233{
234    let validation = syscalls::RtSigprocmask::new()
235        .with_how(-1)
236        .with_set(Some(address))
237        .with_oldset(None)
238        .with_sigsetsize(KERNEL_SIGSET_SIZE);
239    match guest.inject(validation).await {
240        Err(Errno::EINVAL) => {}
241        Err(errno) => return Err(errno.into()),
242        Ok(_) => {
243            // Both Linux and the KVM syscall implementation reject an unknown
244            // operation. Success means the backend did not validate the probe.
245            return Err(Errno::EIO.into());
246        }
247    }
248    Ok(guest.memory().read_value(address.cast())?)
249}
250
251/// Whether a thread-directed SIGKILL (`tgkill` or `tkill`) names `tid`, a
252/// thread-group leader that has exited while a sibling runs on. Linux keeps
253/// such a leader as a zombie, and a SIGKILL that names it does not end its
254/// group: `complete_signal` finds that the exiting leader does not want the
255/// signal, and a thread-directed signal goes to no other thread. So the send
256/// is not reserved as a group kill; it takes the ordinary path, and the
257/// kernel answers it as Linux does, with no effect. Only a leader the
258/// scheduler retired at its own `exit` counts: it commits no other turn until
259/// the kernel shows such a leader a zombie, so by this turn it is one, in
260/// every run (`Scheduler::names_a_retired_exited_leader`). A leader that left
261/// the scheduler at a host-timed deregistration, as on DBT, does not
262/// (https://github.com/rrnewton/hermit/issues/4445).
263pub(crate) async fn sigkill_names_an_exited_leader<G, T>(guest: &mut G, tid: DetTid) -> bool
264where
265    G: Guest<Detcore<T>>,
266    T: RecordOrReplay,
267{
268    names_a_retired_exited_leader(guest, tid).await
269}
270
271/// Signal phase 1: the guest's SIGALRM disposition (a guest handler or not)
272/// and SIGALRM blocked bit, as its runtime keeps them virtual, read back with
273/// a query `rt_sigaction` and `rt_sigprocmask` that the runtime answers from
274/// its virtual state.
275async fn virtual_sigalrm_state<G, T>(guest: &mut G) -> Result<(bool, bool), Error>
276where
277    G: Guest<Detcore<T>>,
278    T: RecordOrReplay,
279{
280    let state = read_sigalrm_sleep_state(guest).await?;
281    Ok((
282        state.disposition == SigalrmSleepDisposition::Handled,
283        state.blocked,
284    ))
285}
286
287/// Query through the runtime, which reports the guest's action and mask even
288/// when it owns a physical SIGALRM handler. These queries run in an owned turn;
289/// a scheduler-side read of the callback's physical mask is not a substitute.
290async fn read_sigalrm_sleep_state<G, T>(guest: &mut G) -> Result<InGuestSleepSigalrm, Error>
291where
292    G: Guest<Detcore<T>>,
293    T: RecordOrReplay,
294{
295    let mut stack = guest.stack().await;
296    let action = stack.reserve::<KernelSigaction>();
297    let mask = stack.reserve::<KernelSigset>();
298    let _stack_guard = stack.commit()?;
299    guest
300        .inject(
301            syscalls::RtSigaction::new()
302                .with_signum(libc::SIGALRM)
303                .with_action(None)
304                .with_old_action(Some(action.cast()))
305                .with_sigsetsize(KERNEL_SIGSET_SIZE),
306        )
307        .await?;
308    guest
309        .inject(
310            syscalls::RtSigprocmask::new()
311                .with_how(libc::SIG_BLOCK)
312                .with_set(None)
313                .with_oldset(Some(mask.cast()))
314                .with_sigsetsize(KERNEL_SIGSET_SIZE),
315        )
316        .await?;
317    let action: KernelSigaction = guest.memory().read_value(action)?;
318    let mask: KernelSigset = guest.memory().read_value(mask)?;
319    let disposition = if action.handler == libc::SIG_DFL as u64 {
320        SigalrmSleepDisposition::Default
321    } else if action.handler == libc::SIG_IGN as u64 {
322        SigalrmSleepDisposition::Ignored
323    } else {
324        SigalrmSleepDisposition::Handled
325    };
326    Ok(InGuestSleepSigalrm {
327        disposition,
328        blocked: mask & kernel_sigset_bit(libc::SIGALRM) != 0,
329    })
330}
331
332/// A5 step 3c.1d, design D37 rule (iii): before an `rt_sigaction` that moves
333/// `signum` away from ignored (its action now `SIG_IGN`, or `SIG_DFL` for a
334/// default-ignored signal with no handler; the new `handler` neither) runs,
335/// remove the pending ignored signals the guest's mask at this request
336/// (`M_req`, which governs next too) lets in, `I ∖ M_req`
337/// (`ignored_signals::removable`). Linux discarded each of them when it was
338/// sent, since an unblocked ignored signal is never queued (`sig_ignored`,
339/// kernel/signal.c:104-120 at v6.12), but the in-guest runtime's mask held it
340/// queued; once the new action is installed (`do_sigaction`, :4170-4233) it
341/// would be delivered at the return. A blocked one stays: Linux queued it
342/// too, and delivers it under the new action once unblocked. Under the gate
343/// only; one `/proc` read, and injections only for instances to remove.
344async fn remove_ignored_before_unignore<G, T>(
345    guest: &mut G,
346    signum: i32,
347    handler: u64,
348) -> Result<(), Error>
349where
350    G: Guest<Detcore<T>>,
351    T: RecordOrReplay,
352{
353    if !(1..=64).contains(&signum) || signum == libc::SIGKILL || signum == libc::SIGSTOP {
354        return Ok(());
355    }
356    let bit = kernel_sigset_bit(signum);
357    let stays_ignored = handler == libc::SIG_IGN as u64
358        || (handler == libc::SIG_DFL as u64
359            && crate::syscalls::threads::default_ignored_signals() & bit != 0);
360    if stays_ignored {
361        return Ok(());
362    }
363    let state = read_thread_wait_signal_state(guest.pid(), guest.tid())?;
364    if crate::syscalls::ignored_at_generation(state.ignored, state.caught) & bit == 0 {
365        return Ok(());
366    }
367    let own = crate::syscalls::helpers::gated_own_mask(guest, state.blocked);
368    let removed = crate::ignored_signals::removable(
369        &state,
370        own,
371        own,
372        guest.config().in_guest_reserved_signals,
373    );
374    crate::syscalls::helpers::remove_ignored_signals(guest, removed).await
375}
376
377/// Take one pending copy of `signo` from the calling thread's queues, its own
378/// first and then its process's shared one, as Linux dequeues it, with a
379/// zero-timeout `rt_sigtimedwait` injected while the thread holds its turn.
380/// Returns the signal taken, or the call's error (EAGAIN: none was pending).
381pub(crate) async fn take_pending_signal<G, T>(guest: &mut G, signo: i32) -> Result<i64, Error>
382where
383    G: Guest<Detcore<T>>,
384    T: RecordOrReplay,
385{
386    let mut stack = guest.stack().await;
387    let set = stack.push(kernel_sigset_bit(signo));
388    let timeout = stack.push(Timespec {
389        tv_sec: 0,
390        tv_nsec: 0,
391    });
392    let stack_guard = stack.commit()?;
393    // SAFETY: the kernel only reads the set; Reverie types the argument as
394    // mutable.
395    let set = unsafe { set.cast::<libc::sigset_t>().into_mut() };
396    let taken = guest
397        .inject(
398            syscalls::RtSigtimedwait::new()
399                .with_set(Some(set))
400                .with_info(None)
401                .with_timeout(Some(timeout))
402                .with_sigsetsize(KERNEL_SIGSET_SIZE),
403        )
404        .await;
405    drop(stack_guard);
406    Ok(taken?)
407}
408
409/// Claim a pending `SIGCHLD` for the calling thread, in its own turn, so that
410/// it is delivered to this thread as it next resumes and no other thread can
411/// take it. Returns whether there was one.
412///
413/// A harmless `getpid` is injected. Reverie runs an injected call by stepping
414/// the thread through user mode, so while a signal is pending and unblocked the
415/// kernel dequeues it at that step, Reverie holds it for delivery as the thread
416/// resumes, and the injection returns a restart errno instead of running. The
417/// caller calls this only while SIGCHLD is the signal the kernel dequeues next,
418/// so that held signal is the copy: claimed. If an outside thread took the copy
419/// first, nothing is pending, the call runs, and nothing is claimed. The call
420/// consumes no signal itself, so a stop that ends it can lose nothing (the Codex
421/// review of a version that took the copy with `rt_sigtimedwait` and queued it
422/// again).
423pub(crate) async fn claim_pending_sigchld<G, T>(guest: &mut G) -> Result<bool, Error>
424where
425    G: Guest<Detcore<T>>,
426    T: RecordOrReplay,
427{
428    match guest.inject(syscalls::Getpid::new()).await {
429        Ok(_) => Ok(false),
430        Err(errno) if is_restart_errno(errno) => Ok(true),
431        Err(errno) => Err(errno.into()),
432    }
433}
434
435/// Test-only fault hook, like `HERMIT_TEST_TIMERFD_FAULT`, acting once per run
436/// at the first claim of a `SIGCHLD` (`claim_pending_sigchld`), for the orders
437/// of https://github.com/rrnewton/hermit/issues/4346 that host timing alone
438/// gives too rarely to test:
439/// - `usr1-before-claim`: just before the claim, Detcore sends the claiming
440///   thread SIGUSR1 from its own process (an injected send would itself
441///   dequeue the pending SIGCHLD), so the thread's own queue holds a lower
442///   signal and the claim takes SIGUSR1 instead;
443/// - `usr1-after-claim`: just after the claim, Detcore sends the claiming
444///   thread SIGUSR1, which a guest that blocks it leaves pending, so the
445///   backend passes the claimed SIGCHLD on unreported;
446/// - `drop-claimed`: the claimed copy is dropped, as the ledger drops a
447///   duplicate (`Detcore::child_exit_sigchld_delivers`), at its delivery stop
448///   or where the backend passes it on unreported
449///   (`Tool::filter_unreported_signal`), so no handler runs and the sleep
450///   restarts: a relative `nanosleep` through the kernel's `restart_syscall`
451///   (`helpers::RestartCall::Nanosleep`).
452///
453/// Unset, nothing changes.
454const CLAIM_FAULT_ENV: &str = "HERMIT_TEST_SIGCHLD_CLAIM_FAULT";
455
456/// Whether the claim fault hook has fired in this process. It fires once.
457static CLAIM_FAULT_FIRED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
458
459/// A fault `HERMIT_TEST_SIGCHLD_CLAIM_FAULT` asks for.
460#[derive(Clone, Copy, Debug, PartialEq, Eq)]
461pub(crate) enum ClaimFault {
462    Usr1BeforeClaim,
463    Usr1AfterClaim,
464    DropClaimed,
465}
466
467/// Whether the hook asks for `fault` now: at most once per process.
468pub(crate) fn take_claim_fault(fault: ClaimFault) -> bool {
469    let asked = match std::env::var(CLAIM_FAULT_ENV).as_deref() {
470        Ok("usr1-before-claim") => ClaimFault::Usr1BeforeClaim,
471        Ok("usr1-after-claim") => ClaimFault::Usr1AfterClaim,
472        Ok("drop-claimed") => ClaimFault::DropClaimed,
473        _ => return false,
474    };
475    asked == fault && !CLAIM_FAULT_FIRED.swap(true, std::sync::atomic::Ordering::SeqCst)
476}
477
478/// For `usr1-before-claim` or `usr1-after-claim` (`fault`,
479/// `take_claim_fault`): send the claiming thread SIGUSR1 from Detcore's
480/// process, which shares the guest's pid namespace.
481fn send_claim_fault_signal<G, T>(guest: &G, fault: ClaimFault)
482where
483    G: Guest<Detcore<T>>,
484    T: RecordOrReplay,
485{
486    if take_claim_fault(fault) {
487        // SAFETY: tgkill only sends a signal.
488        let sent = unsafe {
489            libc::syscall(
490                libc::SYS_tgkill,
491                guest.pid().as_raw(),
492                guest.tid().as_raw(),
493                libc::SIGUSR1,
494            )
495        };
496        if sent == 0 {
497            tracing::warn!("claim fault hook: sent SIGUSR1 {:?}", fault);
498        } else {
499            tracing::error!(
500                "claim fault hook: could not send SIGUSR1 {:?}: {}",
501                fault,
502                std::io::Error::last_os_error()
503            );
504        }
505    }
506}
507
508/// Whether `errno` is a restart errno, which an injection returns when a signal
509/// stop ended it before it ran (`claim_pending_sigchld`).
510fn is_restart_errno(errno: Errno) -> bool {
511    matches!(
512        errno,
513        Errno::ERESTARTSYS
514            | Errno::ERESTARTNOINTR
515            | Errno::ERESTARTNOHAND
516            | Errno::ERESTART_RESTARTBLOCK
517    )
518}
519
520/// The guest's virtual SIGALRM blocked bit, read from the runtime (which
521/// answers the query from its virtual state).
522pub(crate) async fn virtual_sigalrm_blocked<G, T>(guest: &mut G) -> Result<bool, Error>
523where
524    G: Guest<Detcore<T>>,
525    T: RecordOrReplay,
526{
527    let mut stack = guest.stack().await;
528    let mask = stack.reserve::<KernelSigset>();
529    let _stack_guard = stack.commit()?;
530    guest
531        .inject(
532            syscalls::RtSigprocmask::new()
533                .with_how(libc::SIG_BLOCK)
534                .with_set(None)
535                .with_oldset(Some(mask.cast()))
536                .with_sigsetsize(KERNEL_SIGSET_SIZE),
537        )
538        .await?;
539    let mask: KernelSigset = guest.memory().read_value(mask)?;
540    Ok(mask & kernel_sigset_bit(libc::SIGALRM) != 0)
541}
542
543/// Validate the entire action through the kernel before copying it privately.
544/// A partial `read_value` can fall back to ptrace for its final eight bytes,
545/// which would read through a protected page. SIGKILL accepts no new action,
546/// but both Linux and KVM copy the complete input before rejecting it.
547async fn read_kernel_sigaction<G, T>(
548    guest: &mut G,
549    address: Addr<'_, libc::sigaction>,
550) -> Result<KernelSigaction, Error>
551where
552    G: Guest<Detcore<T>>,
553    T: RecordOrReplay,
554{
555    let validation = syscalls::RtSigaction::new()
556        .with_signum(libc::SIGKILL)
557        .with_action(Some(address))
558        .with_old_action(None)
559        .with_sigsetsize(KERNEL_SIGSET_SIZE);
560    match guest.inject(validation).await {
561        Err(Errno::EINVAL) => {}
562        Err(errno) => return Err(errno.into()),
563        Ok(_) => return Err(Errno::EIO.into()),
564    }
565    Ok(guest.memory().read_value(address.cast())?)
566}
567
568// AUTONOMOUS-BOT-IMPLEMENTED
569// TODO-HUMAN-REVIEW(#663)
570fn timeval_to_logical_time(value: libc::timeval) -> Result<LogicalTime, Errno> {
571    let seconds = u64::try_from(value.tv_sec).map_err(|_| Errno::EINVAL)?;
572    let micros = u64::try_from(value.tv_usec).map_err(|_| Errno::EINVAL)?;
573    if micros >= 1_000_000 {
574        return Err(Errno::EINVAL);
575    }
576    let nanos = seconds
577        .checked_mul(1_000_000_000)
578        .and_then(|nanos| nanos.checked_add(micros * 1_000))
579        .ok_or(Errno::EINVAL)?;
580    Ok(LogicalTime::from_nanos(nanos))
581}
582
583// AUTONOMOUS-BOT-IMPLEMENTED
584// TODO-HUMAN-REVIEW(#663)
585fn logical_time_to_timeval(value: LogicalTime) -> libc::timeval {
586    libc::timeval {
587        tv_sec: value.as_secs() as libc::time_t,
588        tv_usec: value.subsec_micros() as libc::suseconds_t,
589    }
590}
591
592// AUTONOMOUS-BOT-IMPLEMENTED
593// TODO-HUMAN-REVIEW(#663)
594#[cfg(target_arch = "x86_64")]
595fn logical_time_to_alarm_seconds(value: LogicalTime) -> i64 {
596    value.as_nanos().div_ceil(1_000_000_000) as i64
597}
598
599/// `alarm()`'s return as Linux computes it (`alarm_setitimer`,
600/// `kernel/time/itimer.c`): the whole seconds left, plus one when the
601/// remainder is at least half a second, or when no whole second is left but
602/// the timer is still active. So an active timer, even one with less than a
603/// microsecond left, returns at least 1
604/// (https://github.com/rrnewton/hermit/issues/4139).
605#[cfg(target_arch = "x86_64")]
606fn linux_alarm_seconds(value: LogicalTime) -> i64 {
607    let ns = value.as_nanos();
608    let (secs, rem) = (ns / 1_000_000_000, ns % 1_000_000_000);
609    let round_up = (secs == 0 && rem != 0) || rem >= 500_000_000;
610    (secs + u64::from(round_up)) as i64
611}
612
613// AUTONOMOUS-BOT-IMPLEMENTED
614// TODO-HUMAN-REVIEW(#663)
615/// A pid no task can have: above the largest `pid_max` Linux allows (2^22).
616/// `rt_sigqueueinfo` to it makes the kernel check the siginfo exactly as for
617/// any pid and then answer ESRCH, which is Linux's answer for a gone process
618/// (<https://github.com/rrnewton/hermit/issues/4124>).
619pub(crate) const NO_SUCH_PID: libc::pid_t = libc::pid_t::MAX;
620
621/// The 128 siginfo bytes at `info`. A page the kernel may write a siginfo to
622/// can lack read permission (`PROT_WRITE` alone), which `process_vm_readv`
623/// refuses; the tracer then reads it through `/proc/<tid>/mem`, as ptrace
624/// access does, which needs no read permission on the mapping.
625/// The siginfo a guest's send `call` of `signal` queues, as the receiver
626/// sees it (4262 design N8-5): the first 48 bytes of the caller's
627/// `siginfo_t` for `rt_sigqueueinfo` and `rt_tgsigqueueinfo`, or Linux's
628/// `SI_USER` (`kill`) or `SI_TKILL` (`tgkill`, `tkill`) record with the
629/// caller's process id and real uid; for `pidfd_send_signal`, the caller's
630/// siginfo or `SI_USER`. `None` when it cannot be read, or for a call
631/// Detcore does not decode, which the scheduler records as `SI_USER`.
632/// The process id is the container's, which is the receiver's unless either
633/// is in a nested PID namespace.
634fn sent_siginfo<T, G: Guest<Detcore<T>>>(
635    guest: &mut G,
636    call: &syscalls::Syscall,
637    signal: i32,
638) -> Option<KernelSiginfo>
639where
640    T: RecordOrReplay,
641{
642    if !crate::scheduler::discarded_at_send::records(guest.config()) {
643        return None;
644    }
645    let code = match call {
646        syscalls::Syscall::RtSigqueueinfo(call) => {
647            return read_siginfo(guest, call.siginfo()?)
648                .map(|bytes| KernelSiginfo::from_user(&bytes));
649        }
650        syscalls::Syscall::RtTgsigqueueinfo(call) => {
651            return read_siginfo(guest, call.siginfo()?)
652                .map(|bytes| KernelSiginfo::from_user(&bytes));
653        }
654        // `pidfd_send_signal` queues the caller's siginfo when it passes one,
655        // and Linux's `SI_USER` record otherwise.
656        syscalls::Syscall::Other(sysno, args) if *sysno == Sysno::pidfd_send_signal => {
657            match AddrMut::<libc::siginfo_t>::from_raw(args.arg2) {
658                Some(info) => {
659                    return read_siginfo(guest, info).map(|bytes| KernelSiginfo::from_user(&bytes));
660                }
661                None => libc::SI_USER,
662            }
663        }
664        syscalls::Syscall::Kill(_) => libc::SI_USER,
665        syscalls::Syscall::Tgkill(_) | syscalls::Syscall::Tkill(_) => libc::SI_TKILL,
666        _ => return None,
667    };
668    let pid = guest.thread_state().detpid?.as_raw();
669    let uid = crate::syscalls::real_uid(guest.tid().as_raw())?;
670    Some(crate::scheduler::signal_lists::ordinary_siginfo(
671        signal, code, pid, uid,
672    ))
673}
674
675/// The 128 bytes of the guest's `siginfo_t` at `info`, `None` when they
676/// cannot be read. A range that would pass the end of the address space is
677/// unreadable, as it is to Linux's copy (EFAULT), and is never handed to the
678/// guest-memory reader, whose page walk would overflow on it.
679fn read_siginfo<T, G: Guest<Detcore<T>>>(
680    guest: &mut G,
681    info: AddrMut<libc::siginfo_t>,
682) -> Option<[u8; 128]>
683where
684    T: RecordOrReplay,
685{
686    info.as_raw().checked_add(128)?;
687    if let Ok(bytes) = guest.memory().read_value(info.cast::<[u8; 128]>()) {
688        return Some(bytes);
689    }
690    use std::os::unix::fs::FileExt;
691    let mem = std::fs::File::open(format!("/proc/{}/mem", guest.tid().as_raw())).ok()?;
692    let mut bytes = [0u8; 128];
693    mem.read_exact_at(&mut bytes, info.as_raw() as u64).ok()?;
694    Some(bytes)
695}
696
697/// The first bytes of the guest range at `address`, up to `len`, that are
698/// mapped: read a page at a time, through `/proc/<tid>/mem` where the page
699/// lacks read permission (`PROT_WRITE` alone, or `PROT_NONE`), and stopping at
700/// the first page that is not mapped. The kernel's copy to user mode stops at
701/// the first byte it cannot write, so every byte it can change in the range
702/// is in this prefix (the Codex re-check of
703/// https://github.com/rrnewton/hermit/pull/4351, P2-1).
704fn read_mapped_prefix<T, G: Guest<Detcore<T>>>(guest: &mut G, address: usize, len: usize) -> Vec<u8>
705where
706    T: RecordOrReplay,
707{
708    const PAGE: usize = 4096;
709    let mut bytes = Vec::with_capacity(len);
710    let mut mem = None;
711    while bytes.len() < len {
712        let at = address + bytes.len();
713        let take = (PAGE - at % PAGE).min(len - bytes.len());
714        let mut piece = vec![0u8; take];
715        let Some(remote) = AddrMut::<u8>::from_raw(at) else {
716            break;
717        };
718        if guest.memory().read_exact(remote, &mut piece).is_err() {
719            use std::os::unix::fs::FileExt;
720            if mem.is_none() {
721                mem = std::fs::File::open(format!("/proc/{}/mem", guest.tid().as_raw())).ok();
722            }
723            let Some(file) = mem.as_ref() else {
724                break;
725            };
726            if file.read_exact_at(&mut piece, at as u64).is_err() {
727                break;
728            }
729        }
730        bytes.extend_from_slice(&piece);
731    }
732    bytes
733}
734
735/// The most a signalfd read's records can need restored: one 128-byte record
736/// for each standard signal in each of the two queues it reads (the thread's
737/// and the process's). Real-time signals, which can queue more copies, are
738/// out of scope (https://github.com/rrnewton/hermit/issues/4319).
739const SIGNALFD_CAPTURED: usize = 2 * 64 * SIGNALFD_RECORD;
740
741/// What the guest's buffers `extents` (address, length) hold, in order, up to
742/// `limit` bytes and at most `SIGNALFD_CAPTURED`, read before a signalfd read
743/// writes them, so a record that read drops can leave them as they were
744/// (`apply_signalfd_answers`). The capture stops at the first byte that is not
745/// mapped, which the read cannot write either.
746pub(crate) fn capture_signalfd_buffer<T, G: Guest<Detcore<T>>>(
747    guest: &mut G,
748    extents: &[(usize, usize)],
749    limit: usize,
750) -> Vec<u8>
751where
752    T: RecordOrReplay,
753{
754    let total: usize = extents.iter().map(|(_, len)| *len).sum();
755    let len = total.min(limit).min(SIGNALFD_CAPTURED);
756    let mut bytes = Vec::with_capacity(len);
757    for (address, length) in signalfd_extent_pieces(extents, len) {
758        let piece = read_mapped_prefix(guest, address, length);
759        let short = piece.len() < length;
760        bytes.extend_from_slice(&piece);
761        if short {
762            break;
763        }
764    }
765    bytes
766}
767
768/// Put back, in the guest's siginfo buffer at `info`, the bytes that differ
769/// from `before`, the mapped prefix of its 128 bytes before a take whose copy
770/// is dropped (`read_mapped_prefix`). Only the bytes the kernel wrote differ,
771/// and it wrote only what user mode could, so no write reaches a page user
772/// mode could not write; a buffer that ends at an unmapped page keeps its
773/// prefix as it was (the Codex re-check of
774/// https://github.com/rrnewton/hermit/pull/4351, P2-1).
775fn restore_changed_bytes<T, G: Guest<Detcore<T>>>(
776    guest: &mut G,
777    info: AddrMut<libc::siginfo_t>,
778    before: &[u8],
779) where
780    T: RecordOrReplay,
781{
782    let now = read_mapped_prefix(guest, info.as_raw(), before.len());
783    let before = &before[..now.len()];
784    let mut at = 0;
785    while at < before.len() {
786        if now[at] == before[at] {
787            at += 1;
788            continue;
789        }
790        let start = at;
791        while at < before.len() && now[at] != before[at] {
792            at += 1;
793        }
794        if let Some(address) = AddrMut::<u8>::from_raw(info.as_raw() + start) {
795            let _ = guest.memory().write_exact(address, &before[start..at]);
796        }
797    }
798}
799
800/// The thread a process-directed signal goes to. `zombie` is the process's
801/// exit state (`Scheduler::exited_process_is_zombie`). `Ok(None)` is a zombie:
802/// no live threads, but its parent can still wait for it, so Linux still finds
803/// the process and the send succeeds (https://github.com/rrnewton/hermit/issues/4091).
804fn deterministic_kill_target(
805    targets: &[DetTid],
806    zombie: Option<bool>,
807    sig: libc::c_int,
808) -> Result<Option<DetTid>, Errno> {
809    match targets {
810        [] if zombie == Some(true) => Ok(None),
811        [] => Err(Errno::ESRCH),
812        [target] => Ok(Some(*target)),
813        [target, ..] if sig == 0 => Ok(Some(*target)),
814        _ => Err(Errno::ENOSYS),
815    }
816}
817
818// AUTONOMOUS-BOT-IMPLEMENTED
819// TODO-HUMAN-REVIEW(PR-1119): Review unmaskable process-group SIGKILL forwarding.
820fn can_forward_process_group_signal(
821    pid: libc::pid_t,
822    sig: libc::c_int,
823    backend_requires_pid_translation: bool,
824) -> bool {
825    pid < -1 && sig == libc::SIGKILL && !backend_requires_pid_translation
826}
827
828/// Whether one of Linux's three ordinary signal syscalls names the calling
829/// task exactly and asks for the one signal that cannot return successfully.
830///
831/// Keep process-group and broadcast spellings out of this predicate. Even when
832/// the caller belongs to the named group, KVM can refuse a group containing a
833/// second process; reserving an exit before that refusal would strand the
834/// scheduler's terminal barrier.
835fn self_sigkill_targets_current_task(
836    signal: libc::c_int,
837    target_process: Option<DetPid>,
838    target_thread: Option<DetTid>,
839    current_process: DetPid,
840    current_thread: DetTid,
841) -> bool {
842    signal == libc::SIGKILL
843        && (target_process.is_some() || target_thread.is_some())
844        && target_process.is_none_or(|target| target == current_process)
845        && target_thread.is_none_or(|target| target == current_thread)
846}
847
848impl<T: RecordOrReplay> Detcore<T> {
849    /// Bind a supported in-guest emulated sleep to its guest disposition/mask.
850    /// Ordinary admitted creation does not share signal dispositions between
851    /// processes, and each supported in-guest process has one guest thread.
852    /// The snapshot therefore stays current until this request resumes.
853    pub(crate) async fn in_guest_sleep_sigalrm_snapshot<G: Guest<Self>>(
854        &self,
855        guest: &mut G,
856    ) -> Option<InGuestSleepSigalrm> {
857        if !self.cfg.backend.virtualizes_guest_sigalrm {
858            return None;
859        }
860        match read_sigalrm_sleep_state(guest).await {
861            Ok(state) => Some(state),
862            Err(error) => {
863                self.stop_after_in_guest_sleep_signal_loss(
864                    guest,
865                    format!("could not query its SIGALRM action/mask before sleeping: {error}"),
866                )
867                .await
868            }
869        }
870    }
871
872    /// A typed wake means the scheduler generated a one-shot SIGALRM for this
873    /// exact sleep without sending it physically. Stage it through the frozen
874    /// runtime self-signal path, which runs signal and exit callbacks before
875    /// Linux kills the process. The caller must immediately return its handler:
876    /// it must neither resume guest code nor copy nanosleep's remainder out.
877    pub(crate) async fn stage_in_guest_fatal_alarm<G: Guest<Self>>(&self, guest: &mut G) {
878        if !self.cfg.backend.virtualizes_guest_sigalrm {
879            self.stop_after_in_guest_sleep_signal_loss(
880                guest,
881                "received an in-guest fatal alarm on another backend".into(),
882            )
883            .await;
884        }
885        match read_sigalrm_sleep_state(guest).await {
886            Ok(InGuestSleepSigalrm {
887                disposition: SigalrmSleepDisposition::Default,
888                blocked: false,
889            }) => {}
890            other => {
891                self.stop_after_in_guest_sleep_signal_loss(
892                    guest,
893                    format!("its default/unblocked SIGALRM sleep snapshot changed: {other:?}"),
894                )
895                .await;
896            }
897        }
898        let call = syscalls::Tgkill::new()
899            .with_tgid(guest.pid().as_raw())
900            .with_tid(guest.tid().as_raw())
901            .with_sig(libc::SIGALRM);
902        if guest.inject(call).await != Ok(0) {
903            self.stop_after_in_guest_sleep_signal_loss(
904                guest,
905                "could not stage its default-fatal SIGALRM after a sleep wake".into(),
906            )
907            .await;
908        }
909    }
910
911    async fn stop_after_in_guest_sleep_signal_loss<G: Guest<Self>>(
912        &self,
913        guest: &mut G,
914        reason: String,
915    ) -> ! {
916        let reason = format!(
917            "thread {} cannot complete its in-guest SIGALRM sleep delivery: {reason}",
918            guest.thread_state().dettid,
919        );
920        crate::tool_global::report_determinism_loss(guest, reason).await;
921        crate::guest_log::flush_unsent();
922        match guest
923            .tail_inject(
924                syscalls::ExitGroup::new().with_status(detcore_model::HERMIT_POLICY_REFUSAL_EXIT),
925            )
926            .await {}
927    }
928
929    // AUTONOMOUS-BOT-IMPLEMENTED
930    // TODO-HUMAN-REVIEW(#663)
931    /// We send the alarms to the global scheduler to handle.
932    #[cfg(target_arch = "x86_64")]
933    pub async fn handle_alarm<G: Guest<Self>>(
934        &self,
935        guest: &mut G,
936        call: syscalls::Alarm,
937    ) -> Result<i64, Error> {
938        if guest.config().sequentialize_threads {
939            let remaining = register_alarm(
940                guest,
941                LogicalTime::from_secs(call.seconds() as u64),
942                LogicalTime::ZERO,
943                Signal::SIGALRM,
944            )
945            .await;
946            // Record and replay keep the rounding they recorded with.
947            Ok(if self.cfg.recordreplay_modes {
948                logical_time_to_alarm_seconds(remaining.0)
949            } else {
950                linux_alarm_seconds(remaining.0)
951            })
952        } else {
953            info!(
954                "[dtid {}] Running without scheduler, so letting alarm call through...",
955                guest.thread_state().dettid
956            );
957            Ok(guest.inject(call).await?)
958        }
959    }
960
961    // AUTONOMOUS-BOT-IMPLEMENTED
962    // TODO-HUMAN-REVIEW(#663)
963    // TODO-HUMAN-REVIEW(#869)
964    /// Schedule a one-shot or periodic real-time interval timer on Detcore logical time.
965    pub async fn handle_setitimer<G: Guest<Self>>(
966        &self,
967        guest: &mut G,
968        call: syscalls::Setitimer,
969    ) -> Result<i64, Error> {
970        if !guest.config().sequentialize_threads {
971            info!(
972                "[dtid {}] Running without scheduler, so letting setitimer call through...",
973                guest.thread_state().dettid
974            );
975            return Ok(guest.inject(call).await?);
976        }
977        if call.which() != libc::ITIMER_REAL {
978            return Err(Error::Errno(Errno::ENOSYS));
979        }
980
981        let value = call.value().ok_or(Errno::EFAULT)?;
982        let timer: libc::itimerval = guest.memory().read_value(value)?;
983        let interval = timeval_to_logical_time(timer.it_interval)?;
984        let duration = timeval_to_logical_time(timer.it_value)?;
985        // A zero value disarms the timer, whatever its interval.
986        if duration.as_nanos() != 0 && interval.as_nanos() != 0 {
987            refuse_sigalrm(guest, SigalrmControl::ArmRecurringTimer).await?;
988        }
989        let (remaining, old_interval) =
990            register_alarm(guest, duration, interval, Signal::SIGALRM).await;
991        if let Some(old_value) = call.ovalue() {
992            let old_timer = libc::itimerval {
993                it_interval: logical_time_to_timeval(old_interval),
994                it_value: logical_time_to_timeval(remaining),
995            };
996            guest.memory().write_value(old_value, &old_timer)?;
997        }
998        Ok(0)
999    }
1000
1001    // AUTONOMOUS-BOT-IMPLEMENTED
1002    // TODO-HUMAN-REVIEW(PR-892)
1003    /// Return interval-timer state from Detcore's logical scheduler.
1004    pub async fn handle_getitimer<G: Guest<Self>>(
1005        &self,
1006        guest: &mut G,
1007        call: syscalls::Getitimer,
1008    ) -> Result<i64, Error> {
1009        if !guest.config().sequentialize_threads {
1010            info!(
1011                "[dtid {}] Running without scheduler, so letting getitimer call through...",
1012                guest.thread_state().dettid
1013            );
1014            return Ok(guest.inject(call).await?);
1015        }
1016
1017        let snapshot = match call.which() {
1018            libc::ITIMER_REAL => alarm_remaining(guest).await,
1019            libc::ITIMER_VIRTUAL | libc::ITIMER_PROF => {
1020                crate::scheduler::real_timer::ItimerSnapshot::default()
1021            }
1022            _ => return Err(Errno::EINVAL.into()),
1023        };
1024        let value = call.value().ok_or(Errno::EFAULT)?;
1025        let timer = libc::itimerval {
1026            it_interval: logical_time_to_timeval(snapshot.interval),
1027            it_value: logical_time_to_timeval(snapshot.remaining),
1028        };
1029        guest.memory().write_value(value, &timer)?;
1030        Ok(0)
1031    }
1032
1033    /// A pause is really just an unbounded sleep.
1034    #[cfg(target_arch = "x86_64")]
1035    pub async fn handle_pause<G: Guest<Self>>(
1036        &self,
1037        guest: &mut G,
1038        call: syscalls::Pause,
1039    ) -> Result<i64, Error> {
1040        if guest.config().sequentialize_threads {
1041            // `pause` has no deadline: it returns only when a signal is delivered.
1042            // `LogicalTime::INDEFINITE` records that, and the scheduler refuses to
1043            // fast-forward virtual time onto it (see `step2d_handle_empty_queue`),
1044            // so the `Normal` arm below stays unreachable.
1045            loop {
1046                // A signal that already ended the call before it parked: one sent
1047                // while this thread was stopped short of filing the request, as
1048                // when its timeslice ended in the syscall's prehook.
1049                if sleep_signal(guest, SleepCheck::BeforePark, true).await? == SleepSignal::Pending
1050                {
1051                    return Err(Errno::ERESTARTNOHAND.into());
1052                }
1053                let mut req = Self::sleep_request_abs(guest, LogicalTime::INDEFINITE).await;
1054                req.fyi(crate::resources::PAUSE_FYI);
1055                req.in_guest_sleep_sigalrm = self.in_guest_sleep_sigalrm_snapshot(guest).await;
1056                match crate::tool_global::parked_wait_request(
1057                    guest,
1058                    req,
1059                    crate::scheduler::parked::ParkedWaitPolicy::PauseNoHandlerRestart,
1060                )
1061                .await
1062                {
1063                    ResumeStatus::InGuestFatalAlarm => {
1064                        self.stage_in_guest_fatal_alarm(guest).await;
1065                        return Err(Errno::EINTR.into());
1066                    }
1067                    // A pause never asks for `NothingElseCanRun`.
1068                    ResumeStatus::Normal | ResumeStatus::NothingElseCanRun => {
1069                        panic!(
1070                            "Internal violation: pause should never return from the scheduler except by interruption!"
1071                        )
1072                    }
1073                    ResumeStatus::Signaled(signals) => {
1074                        match sleep_signal(guest, SleepCheck::AfterWake(signals), true).await? {
1075                            SleepSignal::Pending | SleepSignal::Claimed => {
1076                                return Err(Errno::ERESTARTNOHAND.into());
1077                            }
1078                            SleepSignal::Held => return Err(Errno::EINTR.into()),
1079                            SleepSignal::None => {
1080                                info!(
1081                                    "[dtid {}] pause woken for a signal no longer pending for it; waiting again",
1082                                    guest.thread_state().dettid
1083                                );
1084                            }
1085                        }
1086                    }
1087                }
1088            }
1089        } else {
1090            info!(
1091                "[dtid {}] Running without scheduler, so letting pause call through...",
1092                guest.thread_state().dettid
1093            );
1094            Ok(guest.inject(call).await?)
1095        }
1096    }
1097
1098    /// Run rt_sigsuspend, without holding the deterministic scheduler turn where
1099    /// the call sleeps.
1100    ///
1101    /// The kernel must perform the temporary mask swap atomically and restore the
1102    /// original mask after signal delivery, so execute the real blocking syscall
1103    /// while marking this thread as blocked outside the runnable set.
1104    ///
1105    /// On a backend whose `rt_sigsuspend` never sleeps
1106    /// (`rt_sigsuspend_never_sleeps`: KVM), the call instead runs in the
1107    /// caller's own turn, and a refused wait yields the turn
1108    /// (<https://github.com/rrnewton/hermit/issues/4360>).
1109    pub async fn handle_rt_sigsuspend<G: Guest<Self>>(
1110        &self,
1111        guest: &mut G,
1112        call: syscalls::RtSigsuspend,
1113    ) -> Result<i64, Error> {
1114        // D41 (A5 step 3c.1c): rt_sigsuspend is kernel-run on the in-guest
1115        // backends, so a default-fatal signal deliverable at its entry means
1116        // the call is not run, checked before anything else, the argument
1117        // checks and the real-time timer observation below included.
1118        if let Some(refused) = super::helpers::kernel_run_entry_refusal(guest)? {
1119            return Err(refused);
1120        }
1121        // Invalid arguments return immediately from the kernel and therefore are
1122        // not signal-only waits.
1123        validate_kernel_sigset_size(call.sigsetsize())?;
1124        let Some(mask_addr) = call.mask() else {
1125            return Err(Errno::EFAULT.into());
1126        };
1127
1128        let temporary_mask =
1129            restart_if_first_probe_not_run(read_kernel_sigset(guest, mask_addr).await)?;
1130        super::signal_shadow::note_wait_mask(guest, temporary_mask);
1131        // Under the in-guest delivery gate the call is parked in the
1132        // scheduler, never run by the kernel (A5 step 3c.1d, D39).
1133        if self.cfg.in_guest_signal_delivery {
1134            return Box::pin(self.parked_rt_sigsuspend(guest, temporary_mask)).await;
1135        }
1136        // The temporary mask lets the kernel take a pending real-time timer
1137        // expiration that every thread blocked when it expired
1138        // (https://github.com/rrnewton/hermit/issues/4089).
1139        observe_under_mask(guest, temporary_mask).await?;
1140        // The real call sleeps under a private copy of the mask read here, not
1141        // under the guest's buffer. The scheduler decides from this copy which
1142        // signals can end the call (`Resources::blocked_signal_mask`), and
1143        // another process sharing the buffer (MAP_SHARED) may rewrite it before
1144        // the call runs; Linux copies the mask when the call starts, and from
1145        // the guest's view the call has already started.
1146        //
1147        // The copy and the rt_sigpending result live in two cells just below
1148        // the stack's red zone, the scratch area an injected call may use, but
1149        // never on the caller's buffer: a guest may keep its mask there while
1150        // it blocks every signal, and a copy placed on that very buffer would
1151        // be the original again, while the pending cell would overwrite the
1152        // guest's mask. If the buffer overlaps the two cells, they move to just
1153        // below it. They are written directly rather than through a scratch
1154        // stack, whose commit writes one contiguous region down from the red
1155        // zone and so would cover a buffer inside it.
1156        let caller_mask = mask_addr.as_raw()..mask_addr.as_raw() + KERNEL_SIGSET_SIZE;
1157        // A stack pointer, or a buffer, too close to address zero leaves no
1158        // room for the red zone and the cells below it. The call then fails
1159        // with EFAULT, as it does when a cell is not writable.
1160        let below_red_zone = (crate::arch::stack_pointer(&guest.regs().await) as usize)
1161            .checked_sub(STACK_RED_ZONE)
1162            .ok_or(Errno::EFAULT)?;
1163        let cells = below_red_zone
1164            .checked_sub(SCRATCH_CELLS_SIZE)
1165            .ok_or(Errno::EFAULT)?..below_red_zone;
1166        let cells_start = if cells.start < caller_mask.end && caller_mask.start < cells.end {
1167            // Checked for symmetry only: the mask was just read, and nothing
1168            // below address 16 is readable.
1169            caller_mask
1170                .start
1171                .checked_sub(SCRATCH_CELLS_SIZE)
1172                .ok_or(Errno::EFAULT)?
1173                & !(std::mem::align_of::<u64>() - 1)
1174        } else {
1175            cells.start
1176        };
1177        let pending_addr = Addr::<u64>::from_raw(cells_start).ok_or(Errno::EFAULT)?;
1178        let mask_copy = AddrMut::<u64>::from_raw(cells_start + std::mem::size_of::<u64>())
1179            .ok_or(Errno::EFAULT)?;
1180        guest.memory().write_value(mask_copy, &temporary_mask)?;
1181        let pending_out = AddrMut::<libc::sigset_t>::from_raw(pending_addr.as_raw())
1182            .expect("stack address must be non-null");
1183        let pending_call = syscalls::RtSigpending::new()
1184            .with_set(Some(pending_out))
1185            .with_sigsetsize(KERNEL_SIGSET_SIZE);
1186        guest.inject_with_retry(pending_call).await?;
1187        let pending: u64 = guest.memory().read_value(pending_addr)?;
1188        let call = call.with_mask(Some(Addr::from_raw(mask_copy.as_raw()).expect("non-null")));
1189
1190        // On a backend whose `rt_sigsuspend` never sleeps
1191        // (`rt_sigsuspend_never_sleeps`), the call returns at once, so it runs
1192        // in this thread's own turn, as any call that cannot block does.
1193        // Backgrounded as a sleeper, it left nothing holding the scheduler
1194        // between the background grant and its continuation, so the scheduler
1195        // reported a deadlock or skipped virtual time to a timed waiter, as
1196        // host timing decided (https://github.com/rrnewton/hermit/issues/4360).
1197        //
1198        // The backend refuses a wait for a signal not yet pending (ENOSYS), and
1199        // a guest retries it, so that refusal hands the turn on first with a
1200        // strong yield (`ResourceID::SchedYield`): the caller is not selected
1201        // again until another thread has had a turn, and under chaos not until
1202        // a thread that did not yield has (a yield epoch), so the threads it
1203        // waits on run before it retries, in the order the scheduler gives. In
1204        // its own turn alone, the caller spun and never let its child's exit
1205        // be granted; a weak yield, which `sched_yield` makes under chaos,
1206        // would let a caller in a higher band win every turn (the Codex
1207        // review).
1208        if rt_sigsuspend_never_sleeps(&self.cfg.backend) {
1209            let result = self.record_or_replay(guest, call).await;
1210            if result == Err(Errno::ENOSYS) && self.cfg.sequentialize_threads {
1211                if self.cfg.max_timeslice.is_some() {
1212                    guest
1213                        .thread_state_mut()
1214                        .reset_timeslice_for_explicit_yield();
1215                }
1216                let request = Self::sched_yield_request(guest);
1217                resource_request(guest, request).await;
1218            }
1219            return Ok(result?);
1220        }
1221        // The scheduler records the mask the call sleeps under, read here in
1222        // this thread's turn, to choose a SIGCHLD target while the thread is
1223        // outside the runnable set (`Resources::blocked_signal_mask`).
1224        let installed_mask = kernel_installed_signal_mask(temporary_mask);
1225        if pending & !temporary_mask != 0 {
1226            // The kernel will consume an already-pending signal as soon as it
1227            // atomically installs the temporary mask. Keep this immediate case
1228            // out of the terminal-wait classification; the real syscall still
1229            // performs delivery and restores the old mask.
1230            self.record_or_replay_blocking_with_mask(guest, call.into(), Some(installed_mask))
1231                .await
1232        } else {
1233            self.record_or_replay_rt_sigsuspend(guest, call, installed_mask)
1234                .await
1235        }
1236    }
1237
1238    /// A5 step 3c.1d, design D39: `rt_sigsuspend` under the in-guest delivery
1239    /// gate, parked in the scheduler as a `pause` is, under its temporary mask
1240    /// (`temporary_mask`, read at entry in Linux's order, `sigsuspend`,
1241    /// kernel/signal.c:4697-4728 at v6.12), which the kernel never installs:
1242    /// the in-guest runtime's mask holds every signal sent while the thread
1243    /// waits, and Detcore decides.
1244    ///
1245    /// - The default-fatal check under the guest's own mask at entry (D41)
1246    ///   has already run, as the handler's first statement; signal phase 1's
1247    ///   refusal of temporary-mask calls in a process that handles SIGALRM
1248    ///   runs before any handler (`sigalrm_phase1`, F8).
1249    /// - Each check, before the park and after each wake
1250    ///   (`helpers::temporary_mask_check`), ends the wait for a default-fatal
1251    ///   signal the temporary mask lets in: `-ERESTARTNOHAND`, as `sigsuspend`
1252    ///   returns, with the D40 flag, which the restart checkpoint turns into
1253    ///   `-EINTR` for the death path of step 3c.1e. Otherwise it removes the
1254    ///   ignored signals Linux would not hold (D37 rule (ii)): those pending at
1255    ///   entry, queued under the own mask, unless both masks block them (the
1256    ///   transparent restart discards them: `signal_pending` counts them,
1257    ///   :157-166, and `get_signal` drops them, :2815, :2830), and those sent
1258    ///   since, unless the temporary mask blocks them (discarded at send).
1259    ///   The first check runs in this turn, before any request, so everything
1260    ///   pending then was pending at entry.
1261    /// - The temporary mask is the one Linux installs, without SIGKILL and
1262    ///   SIGSTOP (`set_current_blocked`, :3089-3092).
1263    /// - The park is a `SleepUntil(INDEFINITE)` request tagged
1264    ///   `RT_SIGSUSPEND_FYI` that publishes the temporary mask
1265    ///   (`Resources::blocked_signal_mask`, so `publish_request_guest_mask`
1266    ///   publishes it): the scheduler wakes it, as a `pause`, at a send's
1267    ///   commit for a signal deliverable under that mask
1268    ///   (`Scheduler::sleep_interrupting_signals`). It files no in-guest
1269    ///   SIGALRM sleep snapshot, so a one-shot alarm reaches it through the
1270    ///   ordinary send and the same wake, never the phase-1 fatal-alarm
1271    ///   divert, whose check reads the guest's own mask.
1272    /// - A pending real-time timer expiration is observed under the temporary
1273    ///   mask only after the first check found the call waiting, so a D41-like
1274    ///   end never drops one first (IR-22); under the gate the scheduler never
1275    ///   keeps one pending in any case (`real_time_expiration_fate` keeps one
1276    ///   only where signal targets are modelled, which the gate excludes).
1277    async fn parked_rt_sigsuspend<G: Guest<Self>>(
1278        &self,
1279        guest: &mut G,
1280        temporary_mask: KernelSigset,
1281    ) -> Result<i64, Error> {
1282        use crate::syscalls::helpers::TemporaryMaskCheck;
1283        // The mask Linux installs (`set_current_blocked`, kernel/signal.c:
1284        // 3089-3092 at v6.12, from `sigsuspend`, :4700): never SIGKILL or
1285        // SIGSTOP. It is what the wait decides by and what it publishes, so a
1286        // `/proc` status shows the `SigBlk` Linux shows.
1287        let temporary_mask = kernel_installed_signal_mask(temporary_mask);
1288        let mut entry = Some(!0);
1289        loop {
1290            let first = entry.is_some();
1291            match crate::syscalls::helpers::temporary_mask_check(
1292                guest,
1293                temporary_mask,
1294                entry.take(),
1295                false,
1296            )
1297            .await?
1298            {
1299                TemporaryMaskCheck::Waits => {}
1300                TemporaryMaskCheck::Fatal => return Err(Errno::ERESTARTNOHAND.into()),
1301                TemporaryMaskCheck::Refused | TemporaryMaskCheck::EpollInterrupted => {
1302                    return Err(Errno::EINTR.into());
1303                }
1304            }
1305            if first {
1306                observe_under_mask(guest, temporary_mask).await?;
1307            }
1308            let mut request = Self::sleep_request_abs(guest, LogicalTime::INDEFINITE).await;
1309            request.fyi(crate::resources::RT_SIGSUSPEND_FYI);
1310            request.blocked_signal_mask = Some(temporary_mask);
1311            match resource_request(guest, request).await {
1312                ResumeStatus::Signaled(_) => {}
1313                status @ (ResumeStatus::Normal
1314                | ResumeStatus::NothingElseCanRun
1315                | ResumeStatus::InGuestFatalAlarm) => panic!(
1316                    "Internal violation: a parked rt_sigsuspend returned from the scheduler \
1317                     with {status:?}, not by interruption"
1318                ),
1319            }
1320        }
1321    }
1322
1323    /// rt_sigaction
1324    pub async fn handle_rt_sigaction<G: Guest<Self>>(
1325        &self,
1326        guest: &mut G,
1327        call: syscalls::RtSigaction,
1328    ) -> Result<i64, Error> {
1329        // Linux rejects an invalid size before inspecting either user pointer.
1330        // Preserve that EINVAL-before-EFAULT ordering even for the signal that
1331        // Hermit reserves for deterministic preemption.
1332        validate_kernel_sigset_size(call.sigsetsize())?;
1333
1334        // Copy the complete kernel object before inspecting the signal number or
1335        // writing old_action. This preserves Linux's action-before-old_action
1336        // pointer ordering, including when both arguments alias.
1337        let kernel_action = match call.action() {
1338            Some(action) => Some(restart_if_first_probe_not_run(
1339                read_kernel_sigaction(guest, action).await,
1340            )?),
1341            None => None,
1342        };
1343
1344        // SIGTRAP is reported here, at the one point where the guest states its
1345        // expectation: it falls through to the ordinary path below (its handler
1346        // really is installed; ptrace just eats the signal).
1347        // SIGSTKFLT's disposition is not installed at all; the branch below
1348        // announces that once per run instead.
1349        if let Some(action) = kernel_action
1350            && call.signum() != reverie::PERF_EVENT_SIGNAL as i32
1351        {
1352            warn_appropriated_signal(call.signum(), action.handler);
1353        }
1354
1355        // PERF_EVENT_SIGNAL is reserved.
1356        if call.signum() == reverie::PERF_EVENT_SIGNAL as i32 {
1357            // A kept fake success, announced: once per run, the coordinator
1358            // names the signal and says the disposition was not installed.
1359            if kernel_action.is_some_and(|action| action.handler != libc::SIG_DFL as u64) {
1360                crate::tool_global::announce_signal_handler_not_installed(guest, call.signum())
1361                    .await;
1362            }
1363            // The go runtime attempts to register this (unused) signal handler.  We will never
1364            // deliver signals of this kind to the guest, so we just turn this action into a noop
1365            // rather than returning `Err(Errno::EINVAL.into())`. Preserve that established
1366            // policy while still honoring the raw syscall's pointer accesses: the virtual old
1367            // disposition is the default action, never Reverie's private preemption handler.
1368            if call.old_action().is_some() {
1369                // SIGKILL's disposition cannot be changed by userspace, so its
1370                // query copies the same four zero words as our virtual default.
1371                // Let the kernel copy them: a short write_value can finish with
1372                // PTRACE_POKEDATA and overwrite a protected final word. This also
1373                // preserves the kernel's partial-copy effects on EFAULT without
1374                // exposing Reverie's private preemption action.
1375                return Ok(guest
1376                    .inject(call.with_signum(libc::SIGKILL).with_action(None))
1377                    .await?);
1378            }
1379            return Ok(0);
1380        }
1381        // Signal phase 1 (step I3): on a backend that keeps a handled SIGALRM
1382        // virtual, the scheduler's ledger must agree with the runtime.
1383        let phase1 = self.cfg.backend.virtualizes_guest_sigalrm
1384            && call.signum() == libc::SIGALRM
1385            && kernel_action.is_some();
1386        let was_handled = guest.thread_state().sigalrm_handled;
1387        let to_handler = kernel_action.is_some_and(|action| {
1388            action.handler != libc::SIG_DFL as u64 && action.handler != libc::SIG_IGN as u64
1389        });
1390        if phase1 {
1391            if to_handler {
1392                self.refuse_sigalrm_handler(guest).await?;
1393            } else if was_handled
1394                && kernel_action.is_some_and(|action| action.handler == libc::SIG_DFL as u64)
1395            {
1396                refuse_sigalrm(guest, SigalrmControl::HandlerToDefault).await?;
1397            }
1398        }
1399        // Whether this call moves the signal from `SIG_IGN` to an action that
1400        // does not ignore it, read before the change: Linux then re-queues a
1401        // periodic POSIX timer's ignored expiry (`posixtimer_sig_unignore`,
1402        // <https://github.com/rrnewton/hermit/issues/4293>).
1403        let unignores = crate::scheduler::discarded_at_send::records(guest.config())
1404            && (1..32).contains(&call.signum())
1405            && kernel_action.is_some_and(|action| {
1406                action.handler != libc::SIG_IGN as u64
1407                    && !(action.handler == libc::SIG_DFL as u64
1408                        && [libc::SIGCONT, libc::SIGCHLD, libc::SIGWINCH, libc::SIGURG]
1409                            .contains(&call.signum()))
1410            })
1411            && match crate::syscalls::signal_shadow::copied_dispositions_for(guest, call.signum()) {
1412                Some(copied) => copied.ignored & kernel_sigset_bit(call.signum()) != 0,
1413                None => {
1414                    crate::syscalls::threads::read_kernel_signal_state(guest.pid(), guest.tid())
1415                        .is_ok_and(|state| state.ignored & kernel_sigset_bit(call.signum()) != 0)
1416                }
1417            };
1418        // D37 rule (iii) under the in-guest delivery gate (A5 step 3c.1d): a
1419        // call that moves the signal away from ignored first removes the
1420        // ignored signals Linux would not hold, before the kernel installs the
1421        // new action, which would otherwise make one deliverable.
1422        if self.cfg.in_guest_signal_delivery
1423            && let Some(action) = kernel_action
1424        {
1425            Box::pin(remove_ignored_before_unignore(
1426                guest,
1427                call.signum(),
1428                action.handler,
1429            ))
1430            .await?;
1431        }
1432        let result = if let Some(kernel_action) = kernel_action {
1433            // The kernel treats `action` as input. Sanitize a private copy instead
1434            // of writing through the guest pointer: the input may be read-only,
1435            // and changing it would make the syscall wrapper observable.
1436            let mut kernel_action = kernel_action;
1437            kernel_action.mask = without_perf_event_signal(kernel_action.mask);
1438            let mut stack = guest.stack().await;
1439            let sanitized_action = stack.push(kernel_action);
1440            let _stack_guard = stack.commit()?;
1441            guest
1442                .inject(call.with_action(Some(sanitized_action.cast())))
1443                .await
1444        } else {
1445            guest.inject(call).await
1446        };
1447        super::signal_shadow::record_rt_sigaction(
1448            guest,
1449            call.signum(),
1450            kernel_action.map(|action| KernelSigaction {
1451                mask: without_perf_event_signal(action.mask),
1452                ..action
1453            }),
1454            call.old_action(),
1455            &result,
1456        );
1457        // Publish the state the runtime now holds whenever the disposition may
1458        // have changed: from a handler, or to one the runtime did not refuse.
1459        // Any result counts, since an EFAULT copying the old action out comes
1460        // after the change. A default or ignored action set while none is
1461        // handled changes nothing the scheduler records, so it costs nothing.
1462        if phase1 && (was_handled || (to_handler && result != Err(Errno::EPERM))) {
1463            self.publish_virtual_sigalrm(guest).await?;
1464        }
1465        // Linux reaps a process's children automatically at exit when its
1466        // SIGCHLD action is SIG_IGN or carries SA_NOCLDWAIT, and a signal to
1467        // such a child then finds no zombie. Mirror the new action for the
1468        // scheduler's exit model, with the same success rule as above
1469        // (https://github.com/rrnewton/hermit/issues/4091).
1470        if call.signum() == libc::SIGCHLD
1471            && guest.config().sequentialize_threads
1472            && let Some(action) = kernel_action
1473            && matches!(result, Ok(_) | Err(Errno::EFAULT))
1474        {
1475            let dettid = guest.thread_state().dettid;
1476            if let Some(process) = registered_process_of(guest, dettid).await {
1477                set_sigchld_disposition(
1478                    guest,
1479                    process,
1480                    action.handler == libc::SIG_IGN as u64,
1481                    action.flags & libc::SA_NOCLDWAIT as u64 != 0,
1482                )
1483                .await;
1484            }
1485        }
1486        // Linux flushes a pending SIGCHLD when its action becomes SIG_IGN or
1487        // SIG_DFL, which ignores it (`do_sigaction`, `sig_handler_ignored`): the
1488        // child-exit ledger's send window goes with it, in this committed turn
1489        // (https://github.com/rrnewton/hermit/issues/4126).
1490        if call.signum() == libc::SIGCHLD
1491            && guest.config().sequentialize_threads
1492            && matches!(result, Ok(_) | Err(Errno::EFAULT))
1493            && kernel_action.is_some_and(|action| {
1494                action.handler == libc::SIG_IGN as u64 || action.handler == libc::SIG_DFL as u64
1495            })
1496        {
1497            crate::tool_global::child_exit_sigchld(
1498                guest,
1499                crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::Flushed,
1500            )
1501            .await;
1502        }
1503        // Linux flushes the pending instances of a signal whose disposition
1504        // becomes SIG_IGN (`do_sigaction`), among them a pending real-time timer
1505        // expiration (https://github.com/rrnewton/hermit/issues/4089).
1506        if result.is_ok()
1507            && kernel_action.is_some_and(|action| action.handler == libc::SIG_IGN as u64)
1508            && (1..=64).contains(&call.signum())
1509        {
1510            let bit = kernel_sigset_bit(call.signum());
1511            let pending = pending_real_time_among(guest, bit).await;
1512            act_on_pending_real_time(guest, pending, 0, bit).await;
1513        }
1514        // The same flush takes a `SIGEV_THREAD_ID` timer's pending copy of a
1515        // standard signal, for SIG_IGN or for SIG_DFL on a signal whose
1516        // default is to ignore it (`sig_handler_ignored`), so it no longer
1517        // counts as the timer's own (https://github.com/rrnewton/hermit/issues/4164).
1518        let ignored = |handler: u64| {
1519            handler == libc::SIG_IGN as u64
1520                || (handler == libc::SIG_DFL as u64
1521                    && [libc::SIGCONT, libc::SIGCHLD, libc::SIGWINCH, libc::SIGURG]
1522                        .contains(&call.signum()))
1523        };
1524        // The kernel reads the new action from Detcore's sanitized copy, so an
1525        // EFAULT can only come from writing `old_action`, after the new action
1526        // is installed.
1527        if (result.is_ok() || matches!(result, Err(Errno::EFAULT)))
1528            && kernel_action.is_some_and(|action| ignored(action.handler))
1529            && (1..32).contains(&call.signum())
1530            && crate::scheduler::timer_copies_recorded()
1531        {
1532            crate::tool_global::forget_thread_timer_copies(guest, call.signum()).await;
1533        }
1534        // A handler's mask decides where a POSIX timer's next entry goes when a
1535        // delivery promotes it (<https://github.com/rrnewton/hermit/issues/4262>).
1536        if crate::scheduler::discarded_at_send::records(guest.config())
1537            && (result.is_ok() || matches!(result, Err(Errno::EFAULT)))
1538            && (1..32).contains(&call.signum())
1539            && let Some(action) = kernel_action
1540        {
1541            crate::tool_global::discarded_signal(
1542                guest,
1543                DiscardedSignalControl::Handler {
1544                    signal: call.signum(),
1545                    caught: action.handler != libc::SIG_IGN as u64
1546                        && action.handler != libc::SIG_DFL as u64,
1547                    mask: action.mask,
1548                    nodefer: action.flags & libc::SA_NODEFER as u64 != 0,
1549                },
1550            )
1551            .await;
1552        }
1553        // The flush takes every pending copy of the signal from every queue of
1554        // the process, so none of them is one Linux discarded any more; and an
1555        // unignore makes a still-current periodic timer expiry a kept one
1556        // (`scheduler::discarded_at_send`).
1557        if crate::scheduler::discarded_at_send::records(guest.config())
1558            && (result.is_ok() || matches!(result, Err(Errno::EFAULT)))
1559            && (1..32).contains(&call.signum())
1560        {
1561            let signal = call.signum();
1562            if kernel_action.is_some_and(|action| ignored(action.handler)) {
1563                crate::tool_global::discarded_signal(
1564                    guest,
1565                    DiscardedSignalControl::Flushed { signal },
1566                )
1567                .await;
1568            } else if unignores {
1569                crate::tool_global::discarded_signal(
1570                    guest,
1571                    DiscardedSignalControl::Unignored { signal },
1572                )
1573                .await;
1574            }
1575        }
1576        Ok(result?)
1577    }
1578
1579    /// Signal phase 1's Detcore-side refusals of a new guest SIGALRM handler,
1580    /// each EPERM before anything changes (design section 6 and closure 5):
1581    /// threads not sequentialized (`alarm` would arm a kernel timer; named
1582    /// with a WARN, `unsequentialized_handler_refused_warning`), a signalfd in
1583    /// the process's descriptor table, and a kernel producer of SIGALRM armed
1584    /// anywhere in the run.
1585    async fn refuse_sigalrm_handler<G: Guest<Self>>(&self, guest: &mut G) -> Result<(), Error> {
1586        // Without sequentialization a run has no premise P (design 16.2: one
1587        // guest thread runs guest code at a time), so it may not catch a
1588        // signal: the first caught-handler install is refused, by name, with
1589        // EPERM and a WARN (coordinator decision D32). A run that catches no
1590        // signal keeps running, a labelled relaxation as on ptrace. Today this
1591        // phase 1 SIGALRM handler is the only handler the in-guest runtime
1592        // lets through to Detcore (it refuses every other one itself, and
1593        // resets any handler installed before it to SIG_DFL). A5 step 3c.2's
1594        // handler admission must carry the same rule for every handler
1595        // install under --no-sequentialize-threads.
1596        if !guest.config().sequentialize_threads {
1597            tracing::warn!(
1598                "{}",
1599                crate::refused_signal_controls::unsequentialized_handler_refused_warning(
1600                    libc::SIGALRM,
1601                    guest.thread_state().dettid.as_raw(),
1602                )
1603            );
1604            return Err(Errno::EPERM.into());
1605        }
1606        // The model's own signalfds, and any the kernel's table holds that
1607        // the model labels otherwise (an inherited descriptor).
1608        let holds_signalfd = guest
1609            .thread_state()
1610            .file_metadata
1611            .lock()
1612            .expect("file metadata mutex poisoned")
1613            .file_handles
1614            .values()
1615            .any(|fd| fd.ty() == FdType::Signalfd)
1616            || crate::sigalrm_phase1::process_holds_signalfd(guest.pid().as_raw());
1617        if holds_signalfd {
1618            return Err(Errno::EPERM.into());
1619        }
1620        Ok(refuse_sigalrm(guest, SigalrmControl::InstallHandler).await?)
1621    }
1622
1623    /// Read the guest's SIGALRM disposition and blocked bit back from the
1624    /// runtime (which answers these queries from its virtual state), then
1625    /// publish them to the scheduler and record the disposition in the thread
1626    /// state, inside this call's turn.
1627    async fn publish_virtual_sigalrm<G: Guest<Self>>(&self, guest: &mut G) -> Result<(), Error> {
1628        let (handled, blocked) = virtual_sigalrm_state(guest).await?;
1629        sigalrm_refuses(guest, SigalrmControl::Publish { handled, blocked }).await;
1630        guest.thread_state_mut().sigalrm_handled = handled;
1631        guest.thread_state_mut().sigalrm_blocked_published = blocked;
1632        Ok(())
1633    }
1634
1635    /// After a guest call that may have changed its virtual mask in a process
1636    /// that handles SIGALRM, publish its SIGALRM blocked bit.
1637    async fn publish_virtual_sigalrm_blocked<G: Guest<Self>>(
1638        &self,
1639        guest: &mut G,
1640    ) -> Result<(), Error> {
1641        let blocked = virtual_sigalrm_blocked(guest).await?;
1642        sigalrm_refuses(guest, SigalrmControl::PublishBlocked(blocked)).await;
1643        guest.thread_state_mut().sigalrm_blocked_published = blocked;
1644        Ok(())
1645    }
1646
1647    // AUTONOMOUS-BOT-IMPLEMENTED
1648    // TODO-HUMAN-REVIEW(#1046): Review retrying interrupted signal-mask injections.
1649    /// rt_sigprocmask
1650    pub async fn handle_rt_sigprocmask<G: Guest<Self>>(
1651        &self,
1652        guest: &mut G,
1653        call: syscalls::RtSigprocmask,
1654    ) -> Result<i64, Error> {
1655        // The kernel checks sigsetsize before copying from either user pointer.
1656        validate_kernel_sigset_size(call.sigsetsize())?;
1657
1658        let (result, validated_set) = self.rt_sigprocmask_inner(guest, call).await;
1659        let set_not_read = matches!(validated_set, ValidatedSet::NotRun);
1660        if guest.thread_state().signal_shadow.is_some() {
1661            use super::signal_shadow::SetInput;
1662            let errno_result = match &result {
1663                Ok(value) => Some(Ok(*value)),
1664                Err(Error::Errno(errno)) => Some(Err(*errno)),
1665                Err(_) => {
1666                    super::signal_shadow::give_up(
1667                        guest,
1668                        "rt_sigprocmask failed without an errno, so its effect is unknown",
1669                    );
1670                    None
1671                }
1672            };
1673            let set = match (call.set(), validated_set) {
1674                (None, _) => SetInput::None,
1675                (Some(_), ValidatedSet::Validated(set)) => {
1676                    SetInput::Validated(without_perf_event_signal(set))
1677                }
1678                (Some(_), ValidatedSet::NotRun) => SetInput::NotRun,
1679                // Read after the call, from memory the kernel itself just
1680                // read, and only when it succeeded; an old mask written over
1681                // any of the set's bytes leaves it unknown.
1682                (Some(set), ValidatedSet::NotValidated) => SetInput::Unvalidated(
1683                    (result.is_ok()
1684                        && !call.oldset().is_some_and(|old| {
1685                            old.as_raw().abs_diff(set.as_raw()) < KERNEL_SIGSET_SIZE
1686                        }))
1687                    .then(|| {
1688                        guest
1689                            .memory()
1690                            .read_value::<_, KernelSigset>(set.cast::<KernelSigset>())
1691                            .ok()
1692                    })
1693                    .flatten()
1694                    .map(without_perf_event_signal),
1695                ),
1696            };
1697            if let Some(errno_result) = errno_result {
1698                super::signal_shadow::record_rt_sigprocmask(
1699                    guest,
1700                    call.how(),
1701                    set,
1702                    call.oldset(),
1703                    &errno_result,
1704                );
1705            }
1706        }
1707        // The set's probe did not run, and the call restarts
1708        // (`restart_if_first_probe_not_run`): nothing changed, so nothing
1709        // follows from a change; the restarted call does the rest.
1710        if set_not_read && matches!(result, Err(Error::Errno(Errno::ERESTARTNOINTR))) {
1711            return result;
1712        }
1713        if call.how() != libc::SIG_BLOCK && call.set().is_some() {
1714            // The new mask may unblock a pending real-time timer expiration
1715            // that every thread blocked when it expired, which the kernel then
1716            // delivers (https://github.com/rrnewton/hermit/issues/4089). Whatever
1717            // the call returned: an EFAULT copying the old set out comes after
1718            // the change. A mask that cannot be read could unblock any of them.
1719            let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
1720            if pending != 0 {
1721                let (observable, discarded) =
1722                    match read_thread_wait_signal_state(guest.pid(), guest.tid()) {
1723                        Ok(state) => unblocked_by(&state, state.blocked),
1724                        Err(_) => (REAL_TIME_SIGNALS, 0),
1725                    };
1726                act_on_pending_real_time(guest, pending, observable, discarded).await;
1727            }
1728        }
1729        if self.cfg.backend.virtualizes_guest_sigalrm && guest.thread_state().sigalrm_handled {
1730            // Signal phase 1: the scheduler's blocked bit follows the virtual
1731            // mask, whatever the call returned (an EFAULT copying the old set
1732            // out comes after the change).
1733            self.publish_virtual_sigalrm_blocked(guest).await?;
1734        }
1735        result
1736    }
1737
1738    /// The call itself. Also says whether, and with what set, the kernel ran
1739    /// it: the `SIG_BLOCK` and `SIG_SETMASK` paths read the set through the
1740    /// kernel first and inject nothing when that fails.
1741    async fn rt_sigprocmask_inner<G: Guest<Self>>(
1742        &self,
1743        guest: &mut G,
1744        call: syscalls::RtSigprocmask,
1745    ) -> (Result<i64, Error>, ValidatedSet) {
1746        if call.how() != libc::SIG_BLOCK && call.how() != libc::SIG_SETMASK {
1747            let result = guest.inject_with_retry(call).await;
1748            (result.map_err(Into::into), ValidatedSet::NotValidated)
1749        } else if let Some(set) = call.set() {
1750            let set_mask =
1751                match restart_if_first_probe_not_run(read_kernel_sigset(guest, set).await) {
1752                    Ok(set_mask) => set_mask,
1753                    Err(error) => return (Err(error), ValidatedSet::NotRun),
1754                };
1755            let mut stack = guest.stack().await;
1756            let new_set = stack.push(without_perf_event_signal(set_mask));
1757            let _stack_guard = match stack.commit() {
1758                Ok(guard) => guard,
1759                Err(error) => return (Err(error.into()), ValidatedSet::NotRun),
1760            };
1761            let modified_call = syscalls::RtSigprocmask::new()
1762                .with_how(call.how())
1763                .with_set(Some(new_set.cast()))
1764                .with_oldset(call.oldset())
1765                .with_sigsetsize(call.sigsetsize());
1766            // Keep returning to the handler so post_handler_hook can run, but
1767            // do not expose a tracer preemption as ERESTARTSYS to the guest.
1768            let result = guest.inject_with_retry(modified_call).await;
1769            (
1770                result.map_err(Into::into),
1771                ValidatedSet::Validated(set_mask),
1772            )
1773        } else {
1774            let result = guest.inject_with_retry(call).await;
1775            (result.map_err(Into::into), ValidatedSet::NotValidated)
1776        }
1777    }
1778
1779    /// rt_sigtimedwait system call
1780    ///
1781    /// This is handled by the scheduler and not passed to the record/replay layer,
1782    /// because currently signals are not recorded.
1783    pub async fn handle_rt_sigtimedwait<G: Guest<Self>>(
1784        &self,
1785        guest: &mut G,
1786        call: syscalls::RtSigtimedwait,
1787    ) -> Result<i64, Error> {
1788        // Linux rejects an invalid mask width before inspecting its pointers.
1789        validate_kernel_sigset_size(call.sigsetsize())?;
1790        // Linux keeps this entry snapshot for the whole wait. The retry helper
1791        // currently injects from the caller's pointer again, so another thread
1792        // can change the set between retries. Fixing that requires sharing one
1793        // scratch-stack guard with the helper's zero-timeout object; do not hide
1794        // that remaining difference by treating this validation read as a snapshot.
1795
1796        let dettid = guest.thread_state().dettid;
1797
1798        let ns_delta = if let Some(timeout) = call.timeout() {
1799            let ts: Timespec = guest.memory().read_value(timeout)?;
1800            Some(Duration::from_secs(ts.tv_sec as u64) + Duration::from_nanos(ts.tv_nsec as u64))
1801        } else {
1802            None
1803        };
1804        let mut rsrc = Resources::new(dettid);
1805        rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
1806        rsrc.fyi("rt_sigtimedwait");
1807        // While it polls, a signal in the set is taken at the next poll
1808        // whatever the thread's mask, which a process-directed signal another
1809        // thread sends must account for
1810        // (`Scheduler::own_process_signal_inputs`). An unreadable set fails in
1811        // the kernel at the first poll.
1812        let waited = match call.set() {
1813            Some(set) => match read_kernel_sigset(guest, set.into()).await {
1814                Err(error @ Error::Errno(Errno::ERESTARTSYS)) => {
1815                    return restart_if_first_probe_not_run(Err(error));
1816                }
1817                read => read.ok(),
1818            },
1819            None => None,
1820        };
1821        // After the set's probe, the call's first injection: observing the
1822        // virtual clock asks the scheduler, which a restart would repeat.
1823        let maybe_timeout = match ns_delta {
1824            Some(ns_delta) => Some(thread_observe_time(guest).await + ns_delta),
1825            None => None,
1826        };
1827        if waited.is_some() {
1828            set_sigtimedwait_set(guest, waited).await;
1829        }
1830        // Linux writes the siginfo only for a signal it returns. A dropped
1831        // copy's siginfo is replaced by what the buffer held before.
1832        let records = crate::scheduler::discarded_at_send::records(guest.config());
1833        let before = match call.info() {
1834            Some(info) if self.cfg.backend.reports_child_exit_publication || records => {
1835                Some(read_mapped_prefix(guest, info.as_raw(), 128))
1836            }
1837            _ => None,
1838        };
1839        let result = loop {
1840            let result =
1841                retry_nonblocking_syscall_with_timeout(guest, call, rsrc.clone(), maybe_timeout)
1842                    .await;
1843            // A consumed child-exit copy that the notification ledger drops
1844            // is not returned: the wait goes on, to the same deadline.
1845            let stands = self
1846                .synchronous_sigchld_stands(guest, call, waited, &result)
1847                .await;
1848            // Nor is a copy Linux discarded when it was sent, which Linux never
1849            // dequeues (https://github.com/rrnewton/hermit/issues/4293). The
1850            // take went into the guest's own buffer, so the kernel made the
1851            // copy-out with its own partial-write and EFAULT rules for a kept
1852            // copy; for a dropped one, the bytes it changed are put back.
1853            let outcome = if records {
1854                self.take_outcome_of(guest, waited, &result).await
1855            } else {
1856                crate::scheduler::posix_timer_coalesce::TakeOutcome {
1857                    skip: false,
1858                    rewrite: None,
1859                }
1860            };
1861            if stands && !outcome.skip {
1862                // A POSIX timer's entry, or a kept signal standing on another
1863                // sender's record, is returned with its own siginfo
1864                // (<https://github.com/rrnewton/hermit/issues/4262>); the
1865                // kernel's copy-out succeeded, so the buffer is writable.
1866                if let (Ok(_), Some(info), Some(rewrite)) = (&result, call.info(), outcome.rewrite)
1867                    && guest
1868                        .memory()
1869                        .write_value(info.cast::<[u8; 128]>(), &rewrite.to_user())
1870                        .is_err()
1871                {
1872                    tracing::warn!(
1873                        "[dtid {}] rt_sigtimedwait's siginfo keeps the physical record",
1874                        guest.thread_state().dettid
1875                    );
1876                }
1877                break result;
1878            }
1879            if let (Some(info), Some(bytes)) = (call.info(), before.as_ref()) {
1880                restore_changed_bytes(guest, info, bytes);
1881            }
1882        };
1883        if waited.is_some() {
1884            set_sigtimedwait_set(guest, None).await;
1885        }
1886        // O2: any completion of a wait for SIGALRM may have dequeued the
1887        // process's ITIMER_REAL copy, including one that then failed copying
1888        // out the siginfo (https://github.com/rrnewton/hermit/issues/4139).
1889        if waited.is_some_and(|set| {
1890            set & crate::syscalls::threads::kernel_sigset_bit(libc::SIGALRM) != 0
1891        }) {
1892            crate::tool_global::reconcile_alarm(guest).await;
1893        }
1894        result
1895    }
1896
1897    /// What to do with the take that ended this thread's `rt_sigtimedwait`
1898    /// for the signals `waited` with `result` (`take_outcome`, from the
1899    /// queues read after it; for an EFAULT, which names no signal,
1900    /// `DiscardedSignalControl::TakeFaulted`).
1901    async fn take_outcome_of<G: Guest<Self>>(
1902        &self,
1903        guest: &mut G,
1904        waited: Option<u64>,
1905        result: &Result<i64, Error>,
1906    ) -> crate::scheduler::posix_timer_coalesce::TakeOutcome {
1907        use crate::scheduler::posix_timer_coalesce::TakeOutcome;
1908        let skip = |skip| TakeOutcome {
1909            skip,
1910            rewrite: None,
1911        };
1912        match result {
1913            Ok(signal) if (1..32).contains(&(*signal as i32)) => {
1914                let after =
1915                    crate::syscalls::threads::read_kernel_signal_state(guest.pid(), guest.tid())
1916                        .ok()
1917                        .map(|state| (state.thread_pending, state.shared_pending));
1918                crate::syscalls::helpers::take_outcome(guest, *signal as i32, after).await
1919            }
1920            Err(Error::Errno(Errno::EFAULT)) => {
1921                let Some(set) = waited else {
1922                    return skip(false);
1923                };
1924                let Ok(state) =
1925                    crate::syscalls::threads::read_kernel_signal_state(guest.pid(), guest.tid())
1926                else {
1927                    return skip(false);
1928                };
1929                let at = guest.thread_state().thread_logical_time.as_nanos();
1930                skip(matches!(
1931                    crate::tool_global::discarded_signal(
1932                        guest,
1933                        DiscardedSignalControl::TakeFaulted {
1934                            set,
1935                            thread_pending: state.thread_pending,
1936                            shared_pending: state.shared_pending,
1937                            at,
1938                        },
1939                    )
1940                    .await,
1941                    DiscardedSignalAnswer::Discarded(true)
1942                ))
1943            }
1944            _ => skip(false),
1945        }
1946    }
1947
1948    /// The `signalfd_siginfo` records a successful signalfd `read` returned,
1949    /// `len` bytes at `buf`, reconciled with the child-exit ledger
1950    /// (`reconcile_signalfd_extents`). Returns the bytes kept, or `None` to
1951    /// refuse.
1952    pub(crate) async fn reconcile_signalfd_records<G: Guest<Self>>(
1953        &self,
1954        guest: &mut G,
1955        buf: AddrMut<'_, u8>,
1956        len: usize,
1957        before: &[u8],
1958    ) -> Option<usize> {
1959        self.reconcile_signalfd_extents(guest, &[(buf.as_raw(), len)], len, before)
1960            .await
1961    }
1962
1963    /// The `signalfd_siginfo` records a successful signalfd read returned:
1964    /// the first `read` bytes of the guest buffers `extents` (address,
1965    /// length), in order, as `read`, `readv`, `preadv` and `preadv2` scatter
1966    /// them. They are reconciled with the child-exit ledger as a SIGCHLD
1967    /// delivered to a handler (`Detcore::child_exit_sigchld_delivers`) or
1968    /// consumed by `rt_sigtimedwait` (`synchronous_sigchld_stands`) is: the
1969    /// scheduler's copy gets the siginfo Linux gives the parent, the kernel's
1970    /// coalesced copy for a SIGKILL cohort names the cohort's lowest pid, and
1971    /// a copy for an exit a SIGCHLD already stood for is dropped. Other
1972    /// records are kept as read. The kept records are scattered back over the
1973    /// same extents, in order, with the bytes the buffer held before the read
1974    /// (`before`) over the bytes of dropped ones. Returns the bytes kept, a
1975    /// whole number of records, or `None` when a record is dropped whose bytes
1976    /// `before` does not cover, which the caller refuses
1977    /// (`refuse_unrestorable_signalfd_record`).
1978    pub(crate) async fn reconcile_signalfd_extents<G: Guest<Self>>(
1979        &self,
1980        guest: &mut G,
1981        extents: &[(usize, usize)],
1982        read: usize,
1983        before: &[u8],
1984    ) -> Option<usize> {
1985        let len = read - read % SIGNALFD_RECORD;
1986        let pieces = signalfd_extent_pieces(extents, len);
1987        let mut bytes = Vec::with_capacity(len);
1988        for (address, length) in &pieces {
1989            let mut piece = vec![0u8; *length];
1990            let Some(address_mut) = AddrMut::<u8>::from_raw(*address) else {
1991                return Some(len);
1992            };
1993            if guest.memory().read_exact(address_mut, &mut piece).is_err() {
1994                use std::os::unix::fs::FileExt;
1995                let read = std::fs::File::open(format!("/proc/{}/mem", guest.tid().as_raw()))
1996                    .and_then(|mem| mem.read_exact_at(&mut piece, *address as u64));
1997                if read.is_err() {
1998                    // The kernel has just written these bytes, so the buffer
1999                    // is mapped and writable; `/proc/<tid>/mem` reads any
2000                    // such page.
2001                    return Some(len);
2002                }
2003            }
2004            bytes.extend_from_slice(&piece);
2005        }
2006        let controls = signalfd_record_controls(&bytes, std::process::id());
2007        let mut answers = Vec::with_capacity(controls.len());
2008        for control in controls {
2009            answers.push(match control {
2010                Some(control) => Some(crate::tool_global::child_exit_sigchld(guest, control).await),
2011                None => None,
2012            });
2013        }
2014        // A record of a copy Linux discarded when it was sent is dropped, as a
2015        // duplicate is: the guest never sees it
2016        // (`crate::syscalls::helpers::take_was_discarded`,
2017        // <https://github.com/rrnewton/hermit/issues/4293>).
2018        // A POSIX timer's entry, or a kept signal standing on another
2019        // sender's record, gets its own record in place of the kernel's
2020        // (<https://github.com/rrnewton/hermit/issues/4262>), and a stale
2021        // timer entry is dropped as a discarded copy is.
2022        if crate::scheduler::discarded_at_send::records(guest.config()) {
2023            for (record, answer) in bytes.chunks_mut(SIGNALFD_RECORD).zip(answers.iter_mut()) {
2024                let signal = signalfd_field(record, 0) as i32;
2025                if !(1..32).contains(&signal) {
2026                    continue;
2027                }
2028                let outcome = crate::syscalls::helpers::take_outcome(guest, signal, None).await;
2029                if outcome.skip {
2030                    *answer = Some(
2031                        crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer::Claimed(true),
2032                    );
2033                } else if let Some(rewrite) = outcome.rewrite {
2034                    record.copy_from_slice(&crate::scheduler::signal_lists::signalfd_record(
2035                        &rewrite,
2036                    ));
2037                }
2038            }
2039        }
2040        let rewritten = match apply_signalfd_answers(&bytes, &answers, before) {
2041            Ok(Some(rewritten)) => rewritten,
2042            Ok(None) => return Some(len),
2043            Err(()) => return None,
2044        };
2045        let mut offset = 0;
2046        for (address, length) in &pieces {
2047            let written = AddrMut::<u8>::from_raw(*address).map(|address| {
2048                guest
2049                    .memory()
2050                    .write_exact(address, &rewritten.bytes[offset..offset + length])
2051            });
2052            if !matches!(written, Some(Ok(()))) {
2053                tracing::warn!(
2054                    "[dtid {}] the signalfd records at {:#x} keep the kernel's siginfo",
2055                    guest.thread_state().dettid,
2056                    address,
2057                );
2058                return Some(len);
2059            }
2060            offset += length;
2061        }
2062        Some(rewritten.kept)
2063    }
2064
2065    /// A child-exit SIGCHLD that a synchronous wait consumed goes through the
2066    /// same notification ledger as one delivered to a handler
2067    /// (`Detcore::child_exit_sigchld_delivers`), on a backend where both the
2068    /// kernel and the scheduler send it (`scheduler::child_exit_sigchld`).
2069    /// The scheduler's copy is returned with the siginfo Linux gives the
2070    /// parent, and a copy for an exit that a SIGCHLD already stood for is
2071    /// dropped. Returns whether the consumed signal stands.
2072    async fn synchronous_sigchld_stands<G: Guest<Self>>(
2073        &self,
2074        guest: &mut G,
2075        call: syscalls::RtSigtimedwait,
2076        waited: Option<u64>,
2077        result: &Result<i64, Error>,
2078    ) -> bool {
2079        if !self.cfg.backend.reports_child_exit_publication {
2080            return true;
2081        }
2082        // Linux copies the set in before it dequeues, and the siginfo out
2083        // after, so an EFAULT with a readable set (`waited`) took a signal it
2084        // never reports; an unreadable set dequeued nothing. Which copy it
2085        // took: a recorded private one if SIGCHLD is gone from the thread's
2086        // private queue, which only committed guest sends fill; otherwise,
2087        // with SIGCHLD the only signal in the set, the shared queue's. A wider
2088        // set falls back to the shared queue's pending bit (the Claude and
2089        // Codex re-checks of https://github.com/rrnewton/hermit/pull/4131).
2090        if matches!(result, Err(Error::Errno(Errno::EFAULT)))
2091            && guest.config().sequentialize_threads
2092            && let Some(set) = waited
2093        {
2094            let sigchld = kernel_sigset_bit(libc::SIGCHLD);
2095            if set & sigchld != 0
2096                && let Ok(state) = crate::syscalls::threads::read_thread_wait_signal_state(
2097                    guest.pid(),
2098                    guest.tid(),
2099                )
2100            {
2101                let shared_pending = if set & !sigchld == 0 {
2102                    false
2103                } else {
2104                    state.shared_pending & sigchld != 0
2105                };
2106                crate::tool_global::child_exit_sigchld(
2107                    guest,
2108                    crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::WaitFaulted {
2109                        private_pending: state.thread_pending & sigchld != 0,
2110                        shared_pending,
2111                    },
2112                )
2113                .await;
2114            }
2115            return true;
2116        }
2117        if !matches!(result, Ok(signal) if *signal == libc::SIGCHLD as i64) {
2118            return true;
2119        }
2120        // Without a siginfo buffer the guest sees only the signal number,
2121        // which every copy shares; the ledger still learns that the wait took
2122        // one (`DequeuedCopy::Unknown`), which may be the shared queue's.
2123        let Some(info) = call.info().map(AddrMut::cast::<GuestSiginfo>) else {
2124            crate::tool_global::child_exit_sigchld(
2125                guest,
2126                crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::Dequeue {
2127                    copy: crate::scheduler::child_exit_sigchld::DequeuedCopy::Unknown,
2128                    admits: false,
2129                },
2130            )
2131            .await;
2132            return true;
2133        };
2134        let Some(bytes) = read_siginfo(guest, info.cast()) else {
2135            return true;
2136        };
2137        Self::synchronous_sigchld_copy_stands(guest, info, &bytes).await
2138    }
2139
2140    /// The child-exit ledger's answer for the `SIGCHLD` with siginfo `bytes`
2141    /// that `rt_sigtimedwait` returned into `info`
2142    /// (`synchronous_sigchld_stands`).
2143    async fn synchronous_sigchld_copy_stands<G: Guest<Self>>(
2144        guest: &mut G,
2145        info: AddrMut<'_, GuestSiginfo>,
2146        bytes: &[u8; 128],
2147    ) -> bool {
2148        use crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer;
2149        use crate::scheduler::child_exit_sigchld::OwnCopy;
2150        // Taken by the wait, a kept copy makes no `InboundSignal` request.
2151        let Some(control) =
2152            crate::scheduler::child_exit_sigchld::control_for(bytes, std::process::id(), false)
2153        else {
2154            return true;
2155        };
2156        match crate::tool_global::child_exit_sigchld(guest, control).await {
2157            ChildExitSigchldAnswer::Claimed(dropped) => !dropped,
2158            ChildExitSigchldAnswer::OwnCopy(OwnCopy::Duplicate) => false,
2159            ChildExitSigchldAnswer::OwnCopy(OwnCopy::Deliver(own))
2160            | ChildExitSigchldAnswer::ClaimedFor(own) => {
2161                if let Err(error) = guest
2162                    .memory()
2163                    .write_value(info.cast::<[u8; 128]>(), &own.to_bytes())
2164                {
2165                    tracing::warn!(
2166                        "[dtid {}] the SIGCHLD for child {} keeps its siginfo: {}",
2167                        guest.thread_state().dettid,
2168                        own.pid,
2169                        error
2170                    );
2171                }
2172                true
2173            }
2174            _ => true,
2175        }
2176    }
2177
2178    /// Fence an exact self-SIGKILL at the scheduler-selected syscall turn.
2179    ///
2180    /// This applies only when Detcore holds the backend's process signal
2181    /// control (`BackendSignalControlMode::ToolControlled`). A backend that
2182    /// installed that control commits this syscall as an immediate,
2183    /// nonreturning process exit. It therefore has no later return boundary at
2184    /// which the ordinary pending signal path could acquire a delivery permit.
2185    /// Without this preflight, the backend's terminal child callback arrives
2186    /// with no generation-bound reservation and must fail closed.
2187    /// `ResourceID::Exit` is the same fence used by `exit_group`. A run without
2188    /// the installed control (`Unchanged`, including every non-sequentialized
2189    /// run) keeps its ordinary signal path and reserves nothing.
2190    pub(crate) async fn reserve_controlled_self_sigkill_exit<G: Guest<Self>>(
2191        &self,
2192        guest: &mut G,
2193        signal: libc::c_int,
2194        target_process: Option<DetPid>,
2195        target_thread: Option<DetTid>,
2196    ) -> bool {
2197        if guest.signal_control_mode() != reverie::BackendSignalControlMode::ToolControlled {
2198            return false;
2199        }
2200        let (current_thread, mm) = {
2201            let state = guest.thread_state();
2202            (state.dettid, state.mm_id)
2203        };
2204        if !self_sigkill_targets_current_task(
2205            signal,
2206            target_process,
2207            target_thread,
2208            self.detpid,
2209            current_thread,
2210        ) {
2211            return false;
2212        }
2213        crate::reserved_input::before_exit_group(guest)
2214            .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
2215        let request = guest.thread_state().mk_request(
2216            ResourceID::Exit {
2217                group: true,
2218                process: self.detpid,
2219                mm,
2220            },
2221            Permission::RW,
2222        );
2223        resource_request(guest, request).await;
2224        // The SIGKILL ends the process as an exit_group does: once the grant
2225        // has logically ended the process's other threads, a table only it
2226        // holds goes, with what the queues of its sockets held, just before
2227        // the call that ends it (`Self::release_table_at_exit`).
2228        self.release_table_at_exit(guest, true).await;
2229        true
2230    }
2231
2232    /// A backend that cannot deliver a guest's signal (the KVM backend, to
2233    /// another process for example) refuses it with ENOSYS and the typed
2234    /// refusal `UnsupportedOperation::Signal`. Report that refusal by name and
2235    /// route it through the unsupported-operation policy, instead of handing
2236    /// the guest the backend's bare ENOSYS, which left a waiting parent
2237    /// deadlocked. The name goes to tracing and directly to stderr in both
2238    /// dispositions, so neither --log=off nor --log-file hides it. Callers pass
2239    /// the handler's result right after the injection that may have been
2240    /// refused: the next injection replaces the backend's record. Every other
2241    /// result passes through unchanged. No backend is named here; a backend
2242    /// that records no refusal is unaffected.
2243    pub(crate) async fn report_backend_signal_refusal<G: Guest<Self>>(
2244        &self,
2245        guest: &mut G,
2246        sysno: Sysno,
2247        result: Result<i64, Error>,
2248    ) -> Result<i64, Error> {
2249        if !matches!(result, Err(Error::Errno(Errno::ENOSYS))) {
2250            return result;
2251        }
2252        let Some(refusal) = guest
2253            .take_unsupported_refusal()
2254            .filter(|refusal| refusal.operation == reverie::UnsupportedOperation::Signal)
2255        else {
2256            return result;
2257        };
2258        let message = format!(
2259            "[dtid {}] {sysno} refused by the backend: {}. No signal was sent.",
2260            guest.thread_state().dettid,
2261            refusal.diagnostic
2262        );
2263        tracing::error!("{message}");
2264        let _ = std::io::Write::write_fmt(
2265            &mut crate::util::RetryingStderr,
2266            format_args!("hermit: {message}\n"),
2267        );
2268        if !self.cfg.panic_on_unsupported_syscalls {
2269            crate::tool_global::report_unsupported_syscall(guest, sysno).await;
2270        }
2271        self.refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
2272            .await
2273    }
2274
2275    // AUTONOMOUS-BOT-IMPLEMENTED
2276    // TODO-HUMAN-REVIEW(#663)
2277    // TODO-HUMAN-REVIEW(PR-1058): Review process-pending signal preservation.
2278    // TODO-HUMAN-REVIEW(PR-1119): Review unmaskable process-group SIGKILL forwarding.
2279    /// Resolve signal-zero existence checks in the fixed PID namespace, then route an
2280    /// unambiguous positive-PID process signal through the backend. Backends that can execute
2281    /// with guest PIDs preserve process-directed delivery; DBT translates it to the sole live
2282    /// thread because its native process uses a host PID. An unmaskable SIGKILL to a specific
2283    /// process group is also safe to preserve on backends whose guests use real namespace PIDs;
2284    /// other process-group and broadcast delivery remains refused until Detcore models eligible
2285    /// signal masks.
2286    pub async fn handle_kill<G: Guest<Self>>(
2287        &self,
2288        guest: &mut G,
2289        call: syscalls::Kill,
2290    ) -> Result<i64, Error> {
2291        // A group send (pid 0 or below) is not a target here: the paths below
2292        // refuse it (`refuse_group_signal`) unless it is a SIGKILL.
2293        if let Some(refused) = self
2294            .refuse_reserved_signal_send(
2295                guest,
2296                Sysno::kill,
2297                call.sig(),
2298                ReservedSendTarget::Process(call.pid()),
2299                ReservedSendSiginfo::None,
2300            )
2301            .await
2302        {
2303            return refused;
2304        }
2305        // D48 (A5 step 3c.1c): a stop signal under the in-guest delivery
2306        // gate, the caller's own process included.
2307        if let Some(refused) = self
2308            .refuse_stop_signal_send(
2309                guest,
2310                Sysno::kill,
2311                call.sig(),
2312                ReservedSendTarget::Process(call.pid()),
2313                ReservedSendSiginfo::None,
2314            )
2315            .await
2316        {
2317            return refused;
2318        }
2319        self.record_orphaned_group_signals(guest, call.sig()).await;
2320        // A thread below the container's PID namespace names pids of its own
2321        // namespace, where none of Hermit's tasks is visible
2322        // (https://github.com/rrnewton/hermit/issues/4155).
2323        let nested = guest.thread_state().nested_pid_namespace;
2324        if !guest.config().sequentialize_threads {
2325            // Without sequentialization every other kill goes to the kernel as
2326            // is, but never one to a process group or to every process: the
2327            // guest's process group is inherited from Hermit's, so it can hold
2328            // Hermit and the user's shell or test runner, which a group kill
2329            // would reach (https://github.com/rrnewton/hermit/issues/4057).
2330            // Signal 0 sends nothing, and INT_MIN is ESRCH before any lookup.
2331            if call.sig() != 0 && call.pid() <= 0 && call.pid() != libc::pid_t::MIN {
2332                return self
2333                    .refuse_group_signal(guest, call.pid(), call.sig())
2334                    .await;
2335            }
2336            if !nested
2337                && self
2338                    .drop_signal_to_hermit(guest, Sysno::kill, None, call.pid(), call.sig())
2339                    .await
2340            {
2341                return Ok(0);
2342            }
2343            return Ok(self.record_or_replay(guest, call).await?);
2344        }
2345
2346        // A nested caller's process-group ids are its namespace's, which
2347        // Detcore does not translate yet; a SIGKILL to one would otherwise be
2348        // reserved for the container's group of that number.
2349        if nested && call.pid() <= 0 && call.pid() != libc::pid_t::MIN && call.sig() != 0 {
2350            return self
2351                .refuse_group_signal(guest, call.pid(), call.sig())
2352                .await;
2353        }
2354        let (named, own_init_caught) = if call.pid() > 0 {
2355            match self.name_guest_pid(guest, Sysno::kill, call.pid()).await {
2356                NamedGuestPid::Task {
2357                    task,
2358                    own_init_caught,
2359                } => (task.as_raw(), own_init_caught),
2360                NamedGuestPid::Answer(answer) => return answer,
2361            }
2362        } else {
2363            (call.pid(), None)
2364        };
2365        if Self::own_namespace_init_drops(own_init_caught, call.sig()) {
2366            let task = DetTid::from_raw(named);
2367            return self
2368                .send_to_own_init(guest, call, task, QueueId::Process(task), call.sig())
2369                .await;
2370        }
2371
2372        // Linux's `kill_something_info` answers ESRCH for pid INT_MIN before
2373        // anything else ("-INT_MIN is undefined"), so the kernel's answer is
2374        // deterministic and sends nothing; negating it here would overflow
2375        // (https://github.com/rrnewton/hermit/issues/4046).
2376        if call.sig() == 0 && call.pid() > 0 {
2377            // An existence check answers from the same model as a signal, so
2378            // that one pid never answers 0 to one and ESRCH to the other: a
2379            // process with no live threads is a zombie (0) or gone (ESRCH)
2380            // whenever Detcore saw it exit, whatever the host has reaped by
2381            // now (https://github.com/rrnewton/hermit/issues/4091).
2382            let (targets, zombie) =
2383                resolve_kill_targets_and_exit(guest, DetPid::from_raw(named)).await;
2384            if targets.is_empty() {
2385                match zombie {
2386                    Some(true) => return Ok(0),
2387                    Some(false) => return Err(Errno::ESRCH.into()),
2388                    None => {}
2389                }
2390            }
2391        }
2392        if call.sig() == 0 || call.pid() == libc::pid_t::MIN {
2393            return Ok(self.record_or_replay(guest, call).await?);
2394        }
2395
2396        let tgid = named;
2397        // A process-group or broadcast SIGALRM is refused by name below. The
2398        // scheduler resolves a thread id to its process itself.
2399        if call.sig() == libc::SIGALRM && tgid > 0 {
2400            refuse_sigalrm(guest, SigalrmControl::SendTo(DetTid::from_raw(tgid))).await?;
2401        }
2402        if self
2403            .drop_signal_to_hermit(guest, Sysno::kill, None, tgid, call.sig())
2404            .await
2405        {
2406            return Ok(0);
2407        }
2408        if can_forward_process_group_signal(
2409            tgid,
2410            call.sig(),
2411            guest
2412                .config()
2413                .backend
2414                .requires_thread_directed_process_signals,
2415        ) {
2416            // The scheduler is not told about each killed process, but it must
2417            // know the group was killed: a held process in it will die, so a
2418            // wait for it is not part of a deadlock
2419            // (https://github.com/rrnewton/hermit/issues/3904). Told before
2420            // the kill, inside this committed turn: the sender may be in the
2421            // group and die before the call returns. A kill that then fails
2422            // only suppresses a deadlock report, which is the safe direction.
2423            // `kill(INT_MIN)` names no group; Linux refuses it with ESRCH.
2424            let group = tgid.checked_neg().map(DetPid::from_raw);
2425            if group.is_some() {
2426                crate::tool_global::notify_unresolved_sigkill(guest, group).await;
2427            }
2428            // Every member is retired at this turn
2429            // (https://github.com/rrnewton/hermit/issues/3994).
2430            if guest.config().sequentialize_threads
2431                && let Some(group) = group
2432                && let Some(reservation) = reserve_sigkill_group(guest, group).await
2433            {
2434                return self.send_reserved_sigkill(guest, reservation, call).await;
2435            }
2436            crate::reserved_input::before_signal_send(guest, call.sig(), false)?;
2437            return Ok(self.record_or_replay(guest, call).await?);
2438        }
2439        if tgid <= 0 {
2440            return self.refuse_group_signal(guest, tgid, call.sig()).await;
2441        }
2442        // A kill may name the process by any of its live threads' ids (Linux's
2443        // kill_pid_info looks the id up as a task); everything below that acts
2444        // on the process uses its leader
2445        // (https://github.com/rrnewton/hermit/issues/4094).
2446        let process = signal_target_process(guest, DetPid::from_raw(tgid))
2447            .await
2448            .map_or(tgid, |process| process.as_raw());
2449        // Exact self-SIGKILL is group-fatal, so it has no recipient-selection
2450        // ambiguity even when the process has several live threads. Reserve it
2451        // before the generic process-signal path rejects that thread set.
2452        if self
2453            .reserve_controlled_self_sigkill_exit(
2454                guest,
2455                call.sig(),
2456                Some(DetPid::from_raw(process)),
2457                None,
2458            )
2459            .await
2460        {
2461            return Ok(self.record_or_replay(guest, call).await?);
2462        }
2463        // A SIGKILL to another process retires every thread of it at this
2464        // turn (https://github.com/rrnewton/hermit/issues/3994). It needs no
2465        // recipient, so a multithreaded target is accepted.
2466        if call.sig() == libc::SIGKILL
2467            && let Some(reservation) = reserve_sigkill(guest, DetTid::from_raw(tgid)).await
2468        {
2469            return self.send_reserved_sigkill(guest, reservation, call).await;
2470        }
2471        match self
2472            .own_process_signal(guest, Sysno::kill, tgid, process, call.sig())
2473            .await?
2474        {
2475            OwnProcessSend::Admitted => {
2476                return self
2477                    .forward_own_process_signal(guest, call.sig(), call, DetTid::from_raw(tgid))
2478                    .await;
2479            }
2480            OwnProcessSend::Refused(answer) => return answer,
2481            OwnProcessSend::NotOwn => {}
2482        }
2483        let named = DetTid::from_raw(tgid);
2484        let target = self
2485            .process_signal_target(
2486                guest,
2487                Sysno::kill,
2488                DetPid::from_raw(process),
2489                named,
2490                call.sig(),
2491            )
2492            .await?;
2493        let tid = match target {
2494            ProcessSignalTarget::Thread(tid) | ProcessSignalTarget::Chosen(tid) => tid,
2495            ProcessSignalTarget::SharedQueue => {
2496                return self
2497                    .send_guest_signal(
2498                        guest,
2499                        call,
2500                        DetTid::from_raw(process),
2501                        call.sig(),
2502                        Some(DetPid::from_raw(process)),
2503                        false,
2504                        named,
2505                        false,
2506                    )
2507                    .await;
2508            }
2509            ProcessSignalTarget::Zombie => {
2510                // A zombie: exited, and its parent can still wait for it.
2511                // Linux's kill_pid_info still finds it and the send succeeds,
2512                // discarding the signal, so answer 0 without a system call:
2513                // the host process may already be gone if a backend reaped
2514                // it, and the logical answer must not depend on when
2515                // (https://github.com/rrnewton/hermit/issues/4091). Linux
2516                // checks the signal number after finding the task, so an
2517                // invalid one is EINVAL.
2518                if !(1..=libc::SIGRTMAX()).contains(&call.sig()) {
2519                    return Err(Errno::EINVAL.into());
2520                }
2521                return Ok(0);
2522            }
2523        };
2524        let value = if !guest
2525            .config()
2526            .backend
2527            .requires_thread_directed_process_signals
2528        {
2529            self.send_guest_signal(
2530                guest,
2531                call,
2532                tid,
2533                call.sig(),
2534                Some(DetPid::from_raw(process)),
2535                false,
2536                DetTid::from_raw(tgid),
2537                false,
2538            )
2539            .await?
2540        } else if process == self.detpid.as_raw()
2541            && tid == guest.thread_state().dettid
2542            && (1..=libc::SIGRTMAX()).contains(&call.sig())
2543        {
2544            // This backend must name the receiving thread, but an original
2545            // process-directed kill still carries SI_USER, not tgkill's
2546            // SI_TKILL. For the current task Linux accepts an explicit SI_USER
2547            // payload. Keep the sender in the same deterministic PID namespace
2548            // as getpid; obtain its real UID from this actual task, not a host
2549            // lookup using the deterministic PID. Other tasks cannot accept
2550            // caller-supplied SI_USER and retain the existing tgkill route.
2551            let uid = guest.inject_with_retry(syscalls::Getuid::new()).await? as u32;
2552            let mut info = [0_u8; 128];
2553            info[0..4].copy_from_slice(&call.sig().to_ne_bytes());
2554            info[8..12].copy_from_slice(&libc::SI_USER.to_ne_bytes());
2555            // Linux x86-64 siginfo's sender union follows three ints and
2556            // alignment padding: pid_t at 16, uid_t at 20.
2557            info[16..20].copy_from_slice(&self.detpid.as_raw().to_ne_bytes());
2558            info[20..24].copy_from_slice(&uid.to_ne_bytes());
2559            let mut stack = guest.stack().await;
2560            let info = stack.push(info);
2561            let _stack_guard = stack.commit()?;
2562            let targeted = syscalls::RtTgsigqueueinfo::new()
2563                .with_tgid(process)
2564                .with_tid(tid.as_raw())
2565                .with_sig(call.sig())
2566                // The syscall wrapper uses AddrMut, but Linux only reads this
2567                // private siginfo. Its committed storage outlives the send.
2568                .with_siginfo(Some(unsafe { info.cast().into_mut() }));
2569            // A process-directed kill: Linux classifies the send against
2570            // the leader (https://github.com/rrnewton/hermit/issues/4293).
2571            self.send_guest_signal(
2572                guest,
2573                targeted,
2574                tid,
2575                call.sig(),
2576                Some(DetPid::from_raw(process)),
2577                false,
2578                DetTid::from_raw(tgid),
2579                false,
2580            )
2581            .await?
2582        } else {
2583            let targeted = syscalls::Tgkill::new()
2584                .with_tgid(process)
2585                .with_tid(tid.as_raw())
2586                .with_sig(call.sig());
2587            self.send_guest_signal(
2588                guest,
2589                targeted,
2590                tid,
2591                call.sig(),
2592                Some(DetPid::from_raw(process)),
2593                false,
2594                DetTid::from_raw(tgid),
2595                false,
2596            )
2597            .await?
2598        };
2599        if let ProcessSignalTarget::Chosen(_) = target {
2600            crate::tool_global::process_signal_sent(guest, DetPid::from_raw(process)).await;
2601        }
2602        self.notify_cross_task_signal(
2603            guest,
2604            tid,
2605            call.sig(),
2606            Some(DetPid::from_raw(process)),
2607            crate::tool_global::SignalScope::Process,
2608        )
2609        .await;
2610        Ok(value)
2611    }
2612
2613    // AUTONOMOUS-BOT-IMPLEMENTED
2614    // TODO-HUMAN-REVIEW(https://github.com/rrnewton/hermit/issues/4046)
2615    /// Refuse by name a `kill` to a process group (`pid` 0 for the caller's own,
2616    /// `-pgid` for another) or to every process it may signal (`pid` -1).
2617    ///
2618    /// Linux sends the signal to each process in the set
2619    /// (`__kill_pgrp_info`, `kill_something_info` in kernel/signal.c), and each
2620    /// process then picks its receiving thread as a process-directed signal
2621    /// does. Detcore models neither the set's membership nor that choice, so
2622    /// the call goes through the unsupported-operation policy: a fail-closed
2623    /// run stops with the policy-refusal status and
2624    /// `--allow-unsupported-syscalls` returns ENOSYS, as the call did before.
2625    /// A SIGKILL to another group is forwarded before this point
2626    /// (`can_forward_process_group_signal`) when threads are sequentialized,
2627    /// except on a backend that needs thread-directed process signals (DBT),
2628    /// where it is refused here too. Without sequentialization every such kill
2629    /// is refused here, for host safety: the guest's process group is
2630    /// inherited from Hermit's (https://github.com/rrnewton/hermit/issues/4057).
2631    async fn refuse_group_signal<G: Guest<Self>>(
2632        &self,
2633        guest: &mut G,
2634        pid: libc::pid_t,
2635        sig: libc::c_int,
2636    ) -> Result<i64, Error> {
2637        let target = match pid {
2638            0 => "its own process group".to_owned(),
2639            -1 => "every process it may signal".to_owned(),
2640            pid => format!("process group {}", pid.unsigned_abs()),
2641        };
2642        let reason = if guest.config().sequentialize_threads {
2643            "Detcore does not model process-group membership or each process's receiving \
2644             thread (https://github.com/rrnewton/hermit/issues/4046)"
2645        } else {
2646            "with threads not sequentialized (--no-sequentialize-threads, --strace-only) it \
2647             would go to the host kernel as is, and the guest's process group is inherited from \
2648             Hermit's, so it can reach Hermit and the processes that started it \
2649             (https://github.com/rrnewton/hermit/issues/4057)"
2650        };
2651        tracing::error!(
2652            "[detcore, dtid {}] signal {} to {} by kill is not supported: {}",
2653            guest.thread_state().dettid,
2654            sig,
2655            target,
2656            reason,
2657        );
2658        self.refuse_unserviceable_operation(guest, Sysno::kill, Errno::ENOSYS)
2659            .await
2660    }
2661
2662    /// Decide whether a process-directed `sig` that the calling thread sends
2663    /// by `sysno` to `pid`, when `pid` names its own process (`process`, the
2664    /// process that `pid` names: any of its live threads' ids names it), and
2665    /// the process has another live thread, goes to the kernel unchanged
2666    /// (https://github.com/rrnewton/hermit/issues/4034). It does when the
2667    /// schedule forces the kernel's choice of receiver and the sender's
2668    /// dequeue (`own_process_signal_refusal` finds nothing against it);
2669    /// otherwise the ordinary path chooses the receiver
2670    /// (`process_signal_target`).
2671    /// Only where the kernel's signal state for a guest thread is the guest's
2672    /// own (`BackendCapabilities::kernel_signal_state_is_guests`: ptrace and
2673    /// e9patch): the decision reads each thread's blocked, pending, ignored
2674    /// and caught sets from the kernel, and Detcore reads every other input
2675    /// from the schedule.
2676    async fn own_process_signal<G: Guest<Self>>(
2677        &self,
2678        guest: &mut G,
2679        sysno: Sysno,
2680        pid: libc::pid_t,
2681        process: libc::pid_t,
2682        sig: libc::c_int,
2683    ) -> Result<OwnProcessSend, Error> {
2684        if !guest.config().backend.kernel_signal_state_is_guests {
2685            return Ok(OwnProcessSend::NotOwn);
2686        }
2687        let sender = guest.thread_state().dettid;
2688        if process != self.detpid.as_raw() {
2689            return Ok(OwnProcessSend::NotOwn);
2690        }
2691        // Linux prefers the thread `pid` names when it takes the signal; a
2692        // sibling's id, like the process's, leaves the sender as the receiver
2693        // only when every sibling blocks it.
2694        let names_sender = pid == sender.as_raw();
2695        let inputs = own_process_signal_inputs(guest).await;
2696        if inputs
2697            .siblings
2698            .iter()
2699            .all(|(_, state)| *state == SiblingSignalState::ExitGranted)
2700        {
2701            return Ok(OwnProcessSend::NotOwn);
2702        }
2703        let state = read_thread_wait_signal_state(guest.pid(), guest.tid())?;
2704        let send = OwnProcessSignal {
2705            sig,
2706            names_sender,
2707            blocked: state.blocked,
2708            thread_pending: state.thread_pending,
2709            ignored: state.ignored,
2710            caught: state.caught,
2711            inputs,
2712            record_replay: self.cfg.recordreplay_modes,
2713            schedule_replay: self.cfg.replay_schedule_from.is_some(),
2714            stacktrace_signal: self.cfg.stacktrace_signal.is_some(),
2715            posthook_anchor: !guest.thread_state().stats.hb_pending_posthook.is_empty(),
2716            signalfd: guest.thread_state().holds_a_signalfd(),
2717        };
2718        tracing::debug!(
2719            "[detcore, dtid {}] signal {} to its own process: {:?}",
2720            sender,
2721            sig,
2722            send
2723        );
2724        let Some(refusal) = own_process_signal_refusal(&send) else {
2725            return Ok(OwnProcessSend::Admitted);
2726        };
2727        // A recorded schedule of a send that was forwarded, its sender holding
2728        // its turn, cannot be replayed by a send Detcore routes.
2729        if refusal == OwnProcessSignalRefusal::ScheduleReplay
2730            && own_process_signal_refusal(&OwnProcessSignal {
2731                schedule_replay: false,
2732                ..send.clone()
2733            })
2734            .is_none()
2735        {
2736            tracing::error!(
2737                "[detcore, dtid {}] process-directed signal {} to process {} by {} is not \
2738                 supported: {} (https://github.com/rrnewton/hermit/issues/4034)",
2739                sender,
2740                sig,
2741                self.detpid,
2742                sysno,
2743                refusal,
2744            );
2745            return Ok(OwnProcessSend::Refused(
2746                self.refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
2747                    .await,
2748            ));
2749        }
2750        tracing::debug!(
2751            "[detcore, dtid {}] process-directed signal {} to its own process {} by {} is not \
2752             forwarded as it is: {}; Detcore chooses the receiving thread",
2753            sender,
2754            sig,
2755            self.detpid,
2756            sysno,
2757            refusal,
2758        );
2759        Ok(OwnProcessSend::NotOwn)
2760    }
2761
2762    /// Forward an admitted process-directed signal to the sender's own process
2763    /// unchanged, holding the sender's turn until it takes the signal
2764    /// (`own_signal_turn_hold`). A call the kernel fails sends nothing and
2765    /// holds nothing. A call that succeeds moves the process's `curr_target`
2766    /// where Linux's walk reached the sender
2767    /// (`Scheduler::own_process_signal_choice`).
2768    async fn forward_own_process_signal<G: Guest<Self>, C>(
2769        &self,
2770        guest: &mut G,
2771        sig: libc::c_int,
2772        call: C,
2773        named: DetTid,
2774    ) -> Result<i64, Error>
2775    where
2776        C: Into<syscalls::Syscall>,
2777        syscalls::Syscall: From<C>,
2778    {
2779        let token = self
2780            .discard_pre_send(guest, named, QueueId::Process(self.detpid), sig)
2781            .await;
2782        crate::tool_global::own_process_signal_choice(guest, named, sig).await;
2783        let call: syscalls::Syscall = call.into();
2784        let siginfo = sent_siginfo(guest, &call, sig);
2785        guest.thread_state_mut().own_signal_turn_hold = Some(sig);
2786        crate::reserved_input::before_signal_send(guest, sig, false)?;
2787        let result = self.record_or_replay(guest, call).await;
2788        if result.is_err() {
2789            guest.thread_state_mut().own_signal_turn_hold = None;
2790        } else {
2791            self.discard_sent(guest, token, siginfo).await;
2792            crate::tool_global::process_signal_sent(guest, self.detpid).await;
2793        }
2794        Ok(result?)
2795    }
2796
2797    /// Forward a signal to a task of the caller's own PID namespace's init
2798    /// that the kernel drops (`own_namespace_init_drops`), recording, around
2799    /// the send, the copy that ptrace still queues (`discard_pre_send`).
2800    async fn send_to_own_init<G: Guest<Self>, C>(
2801        &self,
2802        guest: &mut G,
2803        call: C,
2804        named: DetTid,
2805        queue: QueueId,
2806        sig: libc::c_int,
2807    ) -> Result<i64, Error>
2808    where
2809        C: Into<syscalls::Syscall>,
2810        syscalls::Syscall: From<C>,
2811    {
2812        let token = self.discard_pre_send(guest, named, queue, sig).await;
2813        let call: syscalls::Syscall = call.into();
2814        let siginfo = sent_siginfo(guest, &call, sig);
2815        let result = self.record_or_replay(guest, call).await;
2816        if result.is_ok() {
2817            self.discard_sent(guest, token, siginfo).await;
2818        }
2819        Ok(result?)
2820    }
2821
2822    /// Classify, in the sender's turn and before the physical send, a guest's
2823    /// send of `raw_signal` into `queue` naming `named`, and read the queue
2824    /// (`scheduler::discarded_at_send`, <https://github.com/rrnewton/hermit/issues/4293>).
2825    /// `None` where Detcore does not sequentialize threads, or for a signal
2826    /// the record does not track.
2827    pub(crate) async fn discard_pre_send<G: Guest<Self>>(
2828        &self,
2829        guest: &mut G,
2830        named: DetTid,
2831        queue: QueueId,
2832        raw_signal: i32,
2833    ) -> Option<DiscardToken> {
2834        if !crate::scheduler::discarded_at_send::records(guest.config())
2835            || !(1..32).contains(&raw_signal)
2836        {
2837            return None;
2838        }
2839        match crate::tool_global::discarded_signal(
2840            guest,
2841            DiscardedSignalControl::PreSend {
2842                named,
2843                queue,
2844                signal: raw_signal,
2845            },
2846        )
2847        .await
2848        {
2849            DiscardedSignalAnswer::Token(token) => Some(token),
2850            _ => None,
2851        }
2852    }
2853
2854    /// Record a send `token` classified (`discard_pre_send`) once the send
2855    /// succeeded, with the siginfo the receiver sees (`sent_siginfo`).
2856    pub(crate) async fn discard_sent<G: Guest<Self>>(
2857        &self,
2858        guest: &mut G,
2859        token: Option<DiscardToken>,
2860        siginfo: Option<KernelSiginfo>,
2861    ) {
2862        if let Some(token) = token {
2863            crate::tool_global::discarded_signal(
2864                guest,
2865                DiscardedSignalControl::Sent(token, siginfo),
2866            )
2867            .await;
2868        }
2869    }
2870
2871    /// What an `rt_sigqueueinfo` whose `siginfo_t` Hermit may not be able to
2872    /// read comes to, decided before admission (`do_rt_sigqueueinfo` in
2873    /// kernel/signal.c). No call that Linux could deliver reaches the kernel
2874    /// without admission (Codex re-check 2 of
2875    /// https://github.com/rrnewton/hermit/pull/4116).
2876    ///
2877    /// The kernel always copies the first 48 bytes (`kernel_siginfo_t`) and
2878    /// the other 80 only for a `si_code` whose layout it does not know
2879    /// (`copy_siginfo_from_user`). Its `copy_from_user` reads what user mode
2880    /// can: on x86 a page with any of read, write or execute permission; it
2881    /// faults on a `PROT_NONE` page; and a fault in a hole just below a
2882    /// grow-down mapping can grow that mapping and succeed. The tracer's own
2883    /// reads say nothing about that (`process_vm_readv` refuses a write-only
2884    /// page, and `/proc/<tid>/mem` force-reads a `PROT_NONE` one), so the
2885    /// mappings of `/proc/<tid>/smaps` decide first:
2886    /// - the first byte of the 48 that user mode cannot read lies in a
2887    ///   `PROT_NONE` mapping, or in a hole whose next mapping above is not
2888    ///   grow-down (`VmFlags` `gd`; only that mapping can grow into it):
2889    ///   EFAULT is certain, and Hermit answers it without the kernel;
2890    /// - any other byte of the 128 that user mode cannot read (a hole below a
2891    ///   grow-down mapping, a tail running into a hole or a `PROT_NONE` page),
2892    ///   or a listing that cannot be read: `Unreadable`, refused by name;
2893    /// - all 128 user-readable: the tracer reads them (`process_vm_readv`, then
2894    ///   `/proc/<tid>/mem` for a write-only page), and a page neither reaches
2895    ///   (a `VM_IO`/`VM_PFNMAP` page such as the vDSO's `[vvar]`) is
2896    ///   `Unreadable`. A `si_code` only the target thread itself may use
2897    ///   (`si_code >= 0` or `SI_TKILL`, from a caller that is not `pid`) is
2898    ///   then `KernelFails`: after a complete copy Linux fails it with EPERM,
2899    ///   or E2BIG first, and sends nothing, so it goes to the kernel unchanged.
2900    fn rt_sigqueueinfo_precheck<G: Guest<Self>>(
2901        &self,
2902        guest: &mut G,
2903        call: &syscalls::RtSigqueueinfo,
2904    ) -> RtSigqueueinfoPrecheck {
2905        const SIZE: usize = std::mem::size_of::<libc::siginfo_t>();
2906        // `sizeof(kernel_siginfo_t)` on x86_64: the part every copy reads.
2907        const KERNEL_SIGINFO: usize = 48;
2908        let address = call.siginfo().map_or(0, |address| address.as_raw());
2909        let Ok(smaps) = std::fs::read_to_string(format!("/proc/{}/smaps", guest.tid().as_raw()))
2910        else {
2911            return RtSigqueueinfoPrecheck::Unreadable;
2912        };
2913        let mappings = parse_smaps(&smaps);
2914        match first_user_unreadable(&mappings, address, KERNEL_SIGINFO) {
2915            None => {}
2916            Some(Unreadable::NoPermission)
2917            | Some(Unreadable::Hole {
2918                below_growsdown: false,
2919            }) => {
2920                return RtSigqueueinfoPrecheck::Efault;
2921            }
2922            Some(Unreadable::Hole {
2923                below_growsdown: true,
2924            }) => {
2925                return RtSigqueueinfoPrecheck::Unreadable;
2926            }
2927        }
2928        if first_user_unreadable(&mappings, address, SIZE).is_some() {
2929            return RtSigqueueinfoPrecheck::Unreadable;
2930        }
2931        let read = call.siginfo().and_then(|info| {
2932            guest.memory().read_value(info).ok().or_else(|| {
2933                use std::os::unix::fs::FileExt;
2934                let mut bytes = [0u8; SIZE];
2935                std::fs::File::open(format!("/proc/{}/mem", guest.tid().as_raw()))
2936                    .and_then(|mem| mem.read_exact_at(&mut bytes, address as u64))
2937                    .ok()?;
2938                // SAFETY: `siginfo_t` is plain data, and `bytes` holds exactly
2939                // one, read from the guest.
2940                Some(unsafe { std::ptr::read_unaligned(bytes.as_ptr().cast::<libc::siginfo_t>()) })
2941            })
2942        });
2943        let Some(info) = read else {
2944            return RtSigqueueinfoPrecheck::Unreadable;
2945        };
2946        let code = info.si_code;
2947        if (code >= 0 || code == libc::SI_TKILL)
2948            && guest.thread_state().dettid.as_raw() != call.tgid()
2949        {
2950            RtSigqueueinfoPrecheck::KernelFails
2951        } else {
2952            RtSigqueueinfoPrecheck::Proceeds
2953        }
2954    }
2955
2956    /// Record, in the sending call's turn and before it reaches the kernel, the
2957    /// signals a send of a default-stop signal lets Linux send to an orphaned
2958    /// process group later (`orphaned_group_signals`), for every process of
2959    /// the container, whatever the target and its disposition and whether the
2960    /// send then succeeds: no gated wait ends for them, and a process-directed
2961    /// send the sender does not block them for is refused
2962    /// (https://github.com/rrnewton/hermit/issues/4034). A send of SIGSTKFLT,
2963    /// Reverie's timer marker, is recorded too
2964    /// (`Scheduler::record_guest_marker_signal`).
2965    pub(crate) async fn record_orphaned_group_signals<G: Guest<Self>>(
2966        &self,
2967        guest: &mut G,
2968        sig: libc::c_int,
2969    ) {
2970        if sig == libc::SIGSTKFLT {
2971            crate::tool_global::record_guest_marker_signal(guest).await;
2972        }
2973        let signals = orphaned_group_signals(sig);
2974        if signals != 0 && guest.config().backend.host_timed_signals_are_physical {
2975            record_host_timed_signals(guest, HostTimedSignalScope::Container, signals).await;
2976        }
2977    }
2978
2979    /// Whether to drop, answering 0 and sending nothing, a signal `sig` the
2980    /// guest addresses to `target` (a pid, or a tid in thread group `tgid` for
2981    /// the calls that name one) that is one of Hermit's own tasks: its container
2982    /// init, PID 1 of the guest's PID namespace, or another thread of that
2983    /// process. A forwarded SIGTERM ended Hermit's run
2984    /// (https://github.com/rrnewton/hermit/issues/4066).
2985    ///
2986    /// This is what Linux does for a signal sent from inside a PID namespace to
2987    /// its init when the init has no handler for it: `sig_task_ignored` drops
2988    /// it (the init is `SIGNAL_UNKILLABLE`, and a sender in its own namespace
2989    /// does not force it), SIGKILL included, and the call returns 0. To the
2990    /// guest Hermit is such an init; the handlers Hermit installs are for
2991    /// supervisors outside the namespace. Signal 0, an invalid signal number
2992    /// and a nonpositive target go to the caller's own path, as do targets
2993    /// that are guest tasks ([`crate::tool_global::hermit_task_named`]).
2994    /// The task a positive pid the calling thread names refers to. At depth 0
2995    /// the pid is the DetTid. In a nested PID namespace it is a pid of that
2996    /// namespace, translated through the scheduler's record
2997    /// (`scheduler::pid_namespaces`, https://github.com/rrnewton/hermit/issues/4155);
2998    /// a pid no task of the namespace has is ESRCH, as Linux's `find_vpid`
2999    /// answers. Detcore's own bookkeeping uses the translated task; a call
3000    /// forwarded to the kernel keeps the guest's number, which the kernel reads
3001    /// in the caller's namespace. On a backend that rewrites a process signal
3002    /// into a thread-directed one with Detcore's ids, a nested caller is
3003    /// refused by name.
3004    async fn name_guest_pid<G: Guest<Self>>(
3005        &self,
3006        guest: &mut G,
3007        sysno: Sysno,
3008        pid: libc::pid_t,
3009    ) -> NamedGuestPid {
3010        if !guest.thread_state().nested_pid_namespace {
3011            return NamedGuestPid::Task {
3012                task: DetTid::from_raw(pid),
3013                own_init_caught: None,
3014            };
3015        }
3016        let refusal = if guest
3017            .config()
3018            .backend
3019            .requires_thread_directed_process_signals
3020        {
3021            "this backend sends process signals with Detcore's container-namespace ids".to_owned()
3022        } else {
3023            match crate::tool_global::guest_pid(guest, pid).await {
3024                GuestPidAnswer::Task {
3025                    task,
3026                    own_init_caught,
3027                } => {
3028                    return NamedGuestPid::Task {
3029                        task,
3030                        own_init_caught,
3031                    };
3032                }
3033                GuestPidAnswer::NoSuchPid => {
3034                    return NamedGuestPid::Answer(Err(Errno::ESRCH.into()));
3035                }
3036                GuestPidAnswer::Untranslatable(reason) => reason,
3037            }
3038        };
3039        tracing::error!(
3040            "[detcore, dtid {}] {} naming pid {} from a nested PID namespace is not supported: {} (https://github.com/rrnewton/hermit/issues/4155)",
3041            guest.thread_state().dettid,
3042            sysno,
3043            pid,
3044            refusal,
3045        );
3046        NamedGuestPid::Answer(
3047            self.refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
3048                .await,
3049        )
3050    }
3051
3052    /// Whether the kernel drops `sig` sent to a task of the caller's own PID
3053    /// namespace's init (`own_init_caught`, `name_guest_pid`): Linux drops it
3054    /// unless the init has a handler for it, and SIGKILL and SIGSTOP always
3055    /// (`SIGNAL_UNKILLABLE`, `sig_task_ignored`). Such a call is forwarded
3056    /// unchanged: the kernel makes its own checks (signal number, siginfo),
3057    /// then drops the signal and answers 0, and nothing reaches a task, so
3058    /// Detcore has nothing to schedule or record.
3059    pub(crate) fn own_namespace_init_drops(own_init_caught: Option<u64>, sig: libc::c_int) -> bool {
3060        own_init_caught.is_some_and(|caught| {
3061            (1..=libc::SIGRTMAX()).contains(&sig)
3062                && (sig == libc::SIGKILL
3063                    || sig == libc::SIGSTOP
3064                    || caught & kernel_sigset_bit(sig) == 0)
3065        })
3066    }
3067
3068    async fn drop_signal_to_hermit<G: Guest<Self>>(
3069        &self,
3070        guest: &mut G,
3071        sysno: Sysno,
3072        tgid: Option<libc::pid_t>,
3073        target: libc::pid_t,
3074        sig: libc::c_int,
3075    ) -> bool {
3076        if !(1..=libc::SIGRTMAX()).contains(&sig) || target <= 0 {
3077            return false;
3078        }
3079        if !crate::tool_global::names_hermit_task(guest, tgid, target).await {
3080            return false;
3081        }
3082        tracing::info!(
3083            "[detcore, dtid {}] {} of signal {} to task {}, one of Hermit's own: dropped and \
3084             answered 0, as Linux drops a signal from inside a PID namespace to its init \
3085             (https://github.com/rrnewton/hermit/issues/4066)",
3086            guest.thread_state().dettid,
3087            sysno,
3088            sig,
3089            target,
3090        );
3091        true
3092    }
3093
3094    // AUTONOMOUS-BOT-IMPLEMENTED
3095    // TODO-HUMAN-REVIEW(https://github.com/rrnewton/hermit/issues/4034)
3096    /// How the process-directed signal `sig`, which the calling thread sends
3097    /// by `sysno` naming the task `named` of process `tgid`, reaches it.
3098    ///
3099    /// With one live thread, that thread takes it however it is sent. With
3100    /// several, Linux's `complete_signal` (kernel/signal.c) picks the thread,
3101    /// and Detcore applies the same rule to its own model of the threads'
3102    /// masks, pending signals and run states
3103    /// (`Scheduler::process_signal_receiver`,
3104    /// https://github.com/rrnewton/hermit/issues/4034): the receiver, or no
3105    /// thread when none wants it. Where that model is not available (a
3106    /// backend that does not model signal targets, a mask Detcore does not
3107    /// know, an exec in flight, a run that records or replays), the send is
3108    /// refused through the unsupported-operation policy, so a fail-closed run
3109    /// stops with the policy-refusal status and `--allow-unsupported-syscalls`
3110    /// returns ENOSYS.
3111    pub(crate) async fn process_signal_target<G: Guest<Self>>(
3112        &self,
3113        guest: &mut G,
3114        sysno: Sysno,
3115        tgid: DetPid,
3116        named: DetTid,
3117        sig: libc::c_int,
3118    ) -> Result<ProcessSignalTarget, Error> {
3119        let (targets, zombie) = resolve_kill_targets_and_exit(guest, tgid).await;
3120        // SIGKILL ends the whole thread group whichever thread receives it, so
3121        // it needs no recipient choice. On a backend that sends process
3122        // signals to a named thread (DBT), whose kill notice retires the
3123        // target's whole group (`Scheduler::note_process_sigkill`), send it to
3124        // the lowest live thread (https://github.com/rrnewton/hermit/issues/4362).
3125        // Elsewhere a SIGKILL to another process is reserved and retired
3126        // before this point.
3127        if sig == libc::SIGKILL
3128            && targets.len() > 1
3129            && guest
3130                .config()
3131                .backend
3132                .requires_thread_directed_process_signals
3133        {
3134            return Ok(ProcessSignalTarget::Thread(targets[0]));
3135        }
3136        match deterministic_kill_target(&targets, zombie, sig) {
3137            Err(Errno::ENOSYS) => {}
3138            Ok(None) => return Ok(ProcessSignalTarget::Zombie),
3139            Ok(Some(thread)) => return Ok(ProcessSignalTarget::Thread(thread)),
3140            Err(errno) => return Err(errno.into()),
3141        }
3142        // Reverie takes these for itself, so the receiver's handler would never
3143        // run (`APPROPRIATED_SIGNALS`).
3144        let appropriated = APPROPRIATED_SIGNALS
3145            .iter()
3146            .find(|(signal, _)| *signal == sig)
3147            .map(|(_, why)| *why);
3148        // A schedule replay decides as a run does: every input of the choice
3149        // is a function of the schedule, which the replay follows, and the
3150        // forwarded copy is taken by the first thread the replay resumes that
3151        // does not block it, which is the thread its recording resumed
3152        // (`Scheduler::process_signal_receiver` refuses a replay whose
3153        // schedule runs another first).
3154        let receiver = if appropriated.is_some() || self.cfg.recordreplay_modes {
3155            None
3156        } else {
3157            Some(crate::tool_global::process_signal_receiver(guest, tgid, named, sig).await)
3158        };
3159        let reason = match receiver {
3160            Some(ProcessSignalReceiver::Thread { thread, .. }) => {
3161                return Ok(ProcessSignalTarget::Chosen(thread));
3162            }
3163            // Linux queues no second copy and chooses no thread: the call is
3164            // forwarded, and the thread chosen for the copy already queued
3165            // keeps it.
3166            Some(ProcessSignalReceiver::AlreadyQueued) => {
3167                return Ok(ProcessSignalTarget::SharedQueue);
3168            }
3169            Some(ProcessSignalReceiver::SharedQueue) => {
3170                return Ok(ProcessSignalTarget::SharedQueue);
3171            }
3172            Some(ProcessSignalReceiver::UnknownMask(thread)) => {
3173                format!("thread {thread}'s signal mask is unknown")
3174            }
3175            Some(ProcessSignalReceiver::SenderTakesIt) => "the sending thread does not block the \
3176                 signal, so under ptrace, where every other thread of the process is stopped, it \
3177                 is the one thread the kernel's complete_signal admits, and it would take the \
3178                 copy before the receiving thread could"
3179                .to_owned(),
3180            Some(ProcessSignalReceiver::OutOfSchedule(thread)) => format!(
3181                "thread {thread} runs or sleeps outside the schedule and does not block the \
3182                 signal, so the kernel can wake it for the copy at a moment host timing sets"
3183            ),
3184            Some(ProcessSignalReceiver::TakenOutOfTurn(thread)) => format!(
3185                "thread {thread} can take the copy from the shared queue outside its turn: its \
3186                 call's temporary mask admits the signal, a signalfd of the process covers it, \
3187                 or it waits for it in rt_sigtimedwait"
3188            ),
3189            Some(ProcessSignalReceiver::ExecInFlight) => {
3190                "an exec is in flight in the process".to_owned()
3191            }
3192            Some(ProcessSignalReceiver::NotModeled) => {
3193                "Detcore models the threads' signal masks only where the kernel's signal \
3194                 state is the guest's (ptrace)"
3195                    .to_owned()
3196            }
3197            Some(ProcessSignalReceiver::ScheduleRunsFirst { receiver, first }) => format!(
3198                "the replayed schedule runs thread {first} next, which could take the copy \
3199                 before thread {receiver}, the thread its recording gave it: the replay has \
3200                 left its recording"
3201            ),
3202            None => match appropriated {
3203                Some(why) => format!("the receiving thread could not take it: {why}"),
3204                None => "the run records or replays, and a replay does not reproduce the masks \
3205                         the choice reads"
3206                    .to_owned(),
3207            },
3208        };
3209        tracing::error!(
3210            "[detcore, dtid {}] process-directed signal {} to process {} by {} is not supported: \
3211             the process has {} live threads, and Detcore cannot choose the receiving thread \
3212             as Linux's complete_signal does: {} \
3213             (https://github.com/rrnewton/hermit/issues/4034)",
3214            guest.thread_state().dettid,
3215            sig,
3216            tgid,
3217            sysno,
3218            targets.len(),
3219            reason,
3220        );
3221        self.refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
3222            .await
3223            .and(Err(Errno::ENOSYS.into()))
3224    }
3225
3226    // AUTONOMOUS-BOT-IMPLEMENTED
3227    // TODO-HUMAN-REVIEW(#663)
3228    /// Send a thread-directed signal through the kernel. Guest PID/TID values are
3229    /// stable in the fresh PID namespace and delivery is scheduler-serialized.
3230    pub async fn handle_tgkill<G: Guest<Self>>(
3231        &self,
3232        guest: &mut G,
3233        call: syscalls::Tgkill,
3234    ) -> Result<i64, Error> {
3235        if let Some(refused) = self
3236            .refuse_reserved_signal_send(
3237                guest,
3238                Sysno::tgkill,
3239                call.sig(),
3240                ReservedSendTarget::Thread {
3241                    tgid: Some(call.tgid()),
3242                    tid: call.tid(),
3243                },
3244                ReservedSendSiginfo::None,
3245            )
3246            .await
3247        {
3248            return refused;
3249        }
3250        // D48 (A5 step 3c.1c): a stop signal under the in-guest delivery
3251        // gate, the caller's own process included.
3252        if let Some(refused) = self
3253            .refuse_stop_signal_send(
3254                guest,
3255                Sysno::tgkill,
3256                call.sig(),
3257                ReservedSendTarget::Thread {
3258                    tgid: Some(call.tgid()),
3259                    tid: call.tid(),
3260                },
3261                ReservedSendSiginfo::None,
3262            )
3263            .await
3264        {
3265            return refused;
3266        }
3267        self.record_orphaned_group_signals(guest, call.sig()).await;
3268        // A nested caller names its own namespace's ids; Detcore's model uses
3269        // the tasks they name, and the forwarded call keeps them
3270        // (https://github.com/rrnewton/hermit/issues/4155). Non-positive ids
3271        // are EINVAL in Linux before any lookup.
3272        let nested = guest.thread_state().nested_pid_namespace;
3273        let (tgid, tid) = if nested && call.tgid() > 0 && call.tid() > 0 {
3274            let (tid, own_init_caught) =
3275                match self.name_guest_pid(guest, Sysno::tgkill, call.tid()).await {
3276                    NamedGuestPid::Task {
3277                        task,
3278                        own_init_caught,
3279                    } => (task, own_init_caught),
3280                    NamedGuestPid::Answer(answer) => return answer,
3281                };
3282            let tgid = match self.name_guest_pid(guest, Sysno::tgkill, call.tgid()).await {
3283                NamedGuestPid::Task { task, .. } => task,
3284                NamedGuestPid::Answer(answer) => return answer,
3285            };
3286            if Self::own_namespace_init_drops(own_init_caught, call.sig()) {
3287                return self
3288                    .send_to_own_init(guest, call, tid, QueueId::Thread(tid), call.sig())
3289                    .await;
3290            }
3291            (tgid.as_raw(), tid.as_raw())
3292        } else {
3293            (call.tgid(), call.tid())
3294        };
3295        if call.sig() == libc::SIGALRM {
3296            refuse_sigalrm(guest, SigalrmControl::SendTo(DetTid::from_raw(tid))).await?;
3297        }
3298        if !nested
3299            && self
3300                .drop_signal_to_hermit(guest, Sysno::tgkill, Some(tgid), tid, call.sig())
3301                .await
3302        {
3303            return Ok(0);
3304        }
3305        let reserved_exit = self
3306            .reserve_controlled_self_sigkill_exit(
3307                guest,
3308                call.sig(),
3309                Some(DetPid::from_raw(tgid)),
3310                Some(DetTid::from_raw(tid)),
3311            )
3312            .await;
3313        // A SIGKILL to a thread of another process ends that whole process,
3314        // and is retired at this turn, like `kill`'s. Only a valid pair is
3315        // reserved: Linux rejects a thread outside the named group (ESRCH) or a
3316        // non-positive id (EINVAL) before anything happens, so such a send
3317        // takes the ordinary path and gets the kernel's errno. Nor is a send
3318        // to a leader that has exited while its siblings run on
3319        // (`sigkill_names_an_exited_leader`).
3320        let mut exited_leader = false;
3321        if call.sig() == libc::SIGKILL
3322            && guest.config().sequentialize_threads
3323            && tgid > 0
3324            && tid > 0
3325            && registered_process_of(guest, DetTid::from_raw(tid)).await
3326                == Some(DetPid::from_raw(tgid))
3327        {
3328            exited_leader = sigkill_names_an_exited_leader(guest, DetTid::from_raw(tid)).await;
3329            if !exited_leader
3330                && let Some(reservation) = reserve_sigkill(guest, DetTid::from_raw(tid)).await
3331            {
3332                return self.send_reserved_sigkill(guest, reservation, call).await;
3333            }
3334        }
3335        let value = self
3336            .send_guest_signal(
3337                guest,
3338                call,
3339                DetTid::from_raw(call.tid()),
3340                call.sig(),
3341                None,
3342                true,
3343                DetTid::from_raw(tid),
3344                reserved_exit || exited_leader || tid <= 0 || tgid <= 0,
3345            )
3346            .await?;
3347        // The kernel accepted a SIGKILL that changes nothing: no thread takes
3348        // it and no process dies, so the scheduler is not told of a kill.
3349        if exited_leader {
3350            return Ok(value);
3351        }
3352        // `pthread_kill` lowers to `tgkill`, so this is the ordinary way one
3353        // guest THREAD signals a sibling. Like `kill`, a successful cross-task
3354        // send must tell the scheduler, or a target parked in a child wait is
3355        // never woken and the wait hangs.
3356        self.notify_cross_task_signal(
3357            guest,
3358            DetTid::from_raw(tid),
3359            call.sig(),
3360            None,
3361            crate::tool_global::SignalScope::Thread,
3362        )
3363        .await;
3364        Ok(value)
3365    }
3366
3367    // AUTONOMOUS-BOT-IMPLEMENTED
3368    // TODO-HUMAN-REVIEW(#812)
3369    /// Send a thread-directed signal through the older two-argument `tkill`.
3370    /// Like its `tgkill` sibling, the target thread is addressed by a guest TID
3371    /// that is stable in the fresh PID namespace and delivery is
3372    /// scheduler-serialized, so forwarding the kernel call is deterministic.
3373    pub async fn handle_tkill<G: Guest<Self>>(
3374        &self,
3375        guest: &mut G,
3376        call: syscalls::Tkill,
3377    ) -> Result<i64, Error> {
3378        if let Some(refused) = self
3379            .refuse_reserved_signal_send(
3380                guest,
3381                Sysno::tkill,
3382                call.sig(),
3383                ReservedSendTarget::Thread {
3384                    tgid: None,
3385                    tid: call.tid(),
3386                },
3387                ReservedSendSiginfo::None,
3388            )
3389            .await
3390        {
3391            return refused;
3392        }
3393        // D48 (A5 step 3c.1c): a stop signal under the in-guest delivery
3394        // gate, the caller's own process included.
3395        if let Some(refused) = self
3396            .refuse_stop_signal_send(
3397                guest,
3398                Sysno::tkill,
3399                call.sig(),
3400                ReservedSendTarget::Thread {
3401                    tgid: None,
3402                    tid: call.tid(),
3403                },
3404                ReservedSendSiginfo::None,
3405            )
3406            .await
3407        {
3408            return refused;
3409        }
3410        self.record_orphaned_group_signals(guest, call.sig()).await;
3411        // A nested caller names its own namespace's tid
3412        // (https://github.com/rrnewton/hermit/issues/4155).
3413        let nested = guest.thread_state().nested_pid_namespace;
3414        let tid = if nested && call.tid() > 0 {
3415            match self.name_guest_pid(guest, Sysno::tkill, call.tid()).await {
3416                NamedGuestPid::Task {
3417                    task,
3418                    own_init_caught,
3419                } => {
3420                    if Self::own_namespace_init_drops(own_init_caught, call.sig()) {
3421                        return self
3422                            .send_to_own_init(guest, call, task, QueueId::Thread(task), call.sig())
3423                            .await;
3424                    }
3425                    task.as_raw()
3426                }
3427                NamedGuestPid::Answer(answer) => return answer,
3428            }
3429        } else {
3430            call.tid()
3431        };
3432        if call.sig() == libc::SIGALRM {
3433            refuse_sigalrm(guest, SigalrmControl::SendTo(DetTid::from_raw(tid))).await?;
3434        }
3435        if !nested
3436            && self
3437                .drop_signal_to_hermit(guest, Sysno::tkill, None, tid, call.sig())
3438                .await
3439        {
3440            return Ok(0);
3441        }
3442        let reserved_exit = self
3443            .reserve_controlled_self_sigkill_exit(
3444                guest,
3445                call.sig(),
3446                None,
3447                Some(DetTid::from_raw(tid)),
3448            )
3449            .await;
3450        // A non-positive tid is EINVAL in Linux, before anything happens.
3451        let mut exited_leader = false;
3452        if call.sig() == libc::SIGKILL && guest.config().sequentialize_threads && tid > 0 {
3453            exited_leader = sigkill_names_an_exited_leader(guest, DetTid::from_raw(tid)).await;
3454            if !exited_leader
3455                && let Some(reservation) = reserve_sigkill(guest, DetTid::from_raw(tid)).await
3456            {
3457                return self.send_reserved_sigkill(guest, reservation, call).await;
3458            }
3459        }
3460        let value = self
3461            .send_guest_signal(
3462                guest,
3463                call,
3464                DetTid::from_raw(call.tid()),
3465                call.sig(),
3466                None,
3467                true,
3468                DetTid::from_raw(tid),
3469                reserved_exit || exited_leader || tid <= 0,
3470            )
3471            .await?;
3472        // As in `tgkill`, a SIGKILL to an exited leader changes nothing.
3473        if exited_leader {
3474            return Ok(value);
3475        }
3476        // Same wakeup obligation as `tgkill`; `tkill` is the older two-argument
3477        // spelling of the same thread-directed send.
3478        self.notify_cross_task_signal(
3479            guest,
3480            DetTid::from_raw(tid),
3481            call.sig(),
3482            None,
3483            crate::tool_global::SignalScope::Thread,
3484        )
3485        .await;
3486        Ok(value)
3487    }
3488
3489    /// Complete a reserved SIGKILL (https://github.com/rrnewton/hermit/issues/3994).
3490    ///
3491    /// A send whose victims exclude the caller's own process returns: it is
3492    /// sent, and then committed with its real result. One that includes the
3493    /// caller's process cannot return, because the kernel ends the caller:
3494    /// the caller records its own exit status for its parent (`CLD_KILLED`,
3495    /// `SIGKILL`), takes the `exit_group` grant for its own process, as
3496    /// `exit_group` does, commits the other victims inside that exit turn, and
3497    /// then sends. The commit comes after the grant: it installs the physical
3498    /// fence, which waits for victims that only the send itself kills. The
3499    /// reservation validated the target against the scheduler's record, so the
3500    /// send cannot fail in a way the caller sees.
3501    pub(crate) async fn send_reserved_sigkill<G: Guest<Self>>(
3502        &self,
3503        guest: &mut G,
3504        reservation: crate::scheduler::sigkill_retirement::SigkillReservation,
3505        call: impl Into<syscalls::Syscall> + Copy,
3506    ) -> Result<i64, Error> {
3507        if !reservation.includes_sender {
3508            self.record_sigkill_robust_lists(guest, reservation.token)
3509                .await;
3510            let result = self.record_or_replay(guest, call).await;
3511            commit_sigkill(guest, reservation.token, result.is_ok()).await;
3512            return Ok(result?);
3513        }
3514        if self.cfg.backend.reports_kernel_child_exit_signals {
3515            let uid = crate::syscalls::real_uid(guest.tid().as_raw())
3516                .unwrap_or_else(|| unsafe { libc::getuid() });
3517            crate::tool_global::child_exit_sigchld(
3518                guest,
3519                crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::ExitStatus {
3520                    code: libc::CLD_KILLED,
3521                    status: libc::SIGKILL,
3522                    uid,
3523                },
3524            )
3525            .await;
3526        }
3527        let request = guest.thread_state().mk_request(
3528            ResourceID::Exit {
3529                group: true,
3530                process: guest.thread_state().detpid.expect("detpid unset"),
3531                mm: guest.thread_state().mm_id,
3532            },
3533            Permission::RW,
3534        );
3535        resource_request(guest, request).await;
3536        // After the grant: from filing the request until it is granted, other
3537        // threads may still run and change their lists.
3538        self.record_sigkill_robust_lists(guest, reservation.token)
3539            .await;
3540        commit_sigkill(guest, reservation.token, true).await;
3541        // The caller's own process ends with this send, as with an
3542        // exit_group: once the grant has logically ended the process's other
3543        // threads, a table only it holds goes, with what the queues of its
3544        // sockets held, just before the send (`Self::release_table_at_exit`).
3545        self.release_table_at_exit(guest, true).await;
3546        Ok(self.record_or_replay(guest, call).await?)
3547    }
3548
3549    /// Send a guest's signal with `syscall`, first recording, for a `SIGCHLD`,
3550    /// the queue its copy will join (`record_guest_sigchld_send`), and undoing
3551    /// that record if the send fails. Recorded before the physical send: a
3552    /// thread outside the schedule (in a real `rt_sigsuspend` or blocking
3553    /// call) can take the copy the moment it is queued, before this handler
3554    /// could record it afterwards (the Claude re-check of
3555    /// https://github.com/rrnewton/hermit/pull/4131). `thread_directed`: the
3556    /// call names one thread (`tkill`, `tgkill`, `rt_tgsigqueueinfo`, a
3557    /// thread-scoped `pidfd_send_signal`), whose private queue the copy
3558    /// joins, as it does on a backend that must name a thread for every
3559    /// process signal. `named`: the task Linux reads to decide whether it
3560    /// discards the signal (`kill`'s pid, `tgkill`'s tid), which the
3561    /// discarded-at-send record classifies the send against
3562    /// (`discard_pre_send`).
3563    #[expect(
3564        clippy::too_many_arguments,
3565        reason = "each argument is one fact about the send the kernel call cannot tell"
3566    )]
3567    pub(crate) async fn send_guest_signal<G, S>(
3568        &self,
3569        guest: &mut G,
3570        syscall: S,
3571        target: DetTid,
3572        raw_signal: i32,
3573        target_process: Option<DetPid>,
3574        thread_directed: bool,
3575        named: DetTid,
3576        terminal_covered: bool,
3577    ) -> Result<i64, Error>
3578    where
3579        G: Guest<Self>,
3580        S: Into<reverie::syscalls::Syscall>,
3581    {
3582        use crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer;
3583        use crate::scheduler::child_exit_sigchld::ChildExitSigchldControl;
3584        let syscall: syscalls::Syscall = syscall.into();
3585        // Whether the physical send names one thread, so its copy joins that
3586        // thread's private queue: the call does, or the backend must name a
3587        // thread for every process signal.
3588        let to_one_thread = thread_directed
3589            || guest
3590                .config()
3591                .backend
3592                .requires_thread_directed_process_signals;
3593        // D44 asks about the thread the send reaches: for a thread-directed
3594        // send the one it names, as the scheduler numbers it (`named`, which
3595        // the handlers translate from a nested PID namespace's id; `target`
3596        // there is the caller's own id), and for a process-directed one the
3597        // receiving thread the handler chose (`target`).
3598        let receiver = if thread_directed { named } else { target };
3599        if let Some(refused) = self
3600            .refuse_kernel_run_target_send(guest, syscall, receiver, named, raw_signal)
3601            .await
3602        {
3603            return refused;
3604        }
3605        let mut undo = None;
3606        if raw_signal == libc::SIGCHLD && guest.config().sequentialize_threads {
3607            // Without a process, or sent to one thread, the copy joins the
3608            // target's private queue.
3609            if to_one_thread || target_process.is_none() {
3610                let answer = crate::tool_global::child_exit_sigchld(
3611                    guest,
3612                    ChildExitSigchldControl::PrivateSent { target },
3613                )
3614                .await;
3615                // A copy already pending there coalesces: only a new record is
3616                // undone.
3617                if answer == ChildExitSigchldAnswer::Placed(true) {
3618                    undo = Some(ChildExitSigchldControl::SendFailed {
3619                        private: Some(target),
3620                        shared: None,
3621                    });
3622                }
3623            } else if let Some(process) = target_process {
3624                crate::tool_global::child_exit_sigchld(
3625                    guest,
3626                    ChildExitSigchldControl::SharedSent { target: process },
3627                )
3628                .await;
3629                undo = Some(ChildExitSigchldControl::SendFailed {
3630                    private: None,
3631                    shared: Some(process),
3632                });
3633            }
3634        }
3635        // Without a process, or sent to one thread, the copy joins the
3636        // target's private queue.
3637        let queue = match target_process {
3638            Some(process) if !to_one_thread => QueueId::Process(process),
3639            _ => QueueId::Thread(target),
3640        };
3641        let token = self.discard_pre_send(guest, named, queue, raw_signal).await;
3642        let siginfo = sent_siginfo(guest, &syscall, raw_signal);
3643        crate::reserved_input::before_signal_send(guest, raw_signal, terminal_covered)?;
3644        let result = self.record_or_replay(guest, syscall).await;
3645        if result.is_err()
3646            && let Some(undo) = undo
3647        {
3648            crate::tool_global::child_exit_sigchld(guest, undo).await;
3649        }
3650        if result.is_ok() {
3651            self.discard_sent(guest, token, siginfo).await;
3652        }
3653        Ok(result?)
3654    }
3655
3656    /// A5 step 3c.1c, design D44(b): under the in-guest delivery gate, refuse a
3657    /// send of `raw_signal` to `target`, which the scheduler's committed state
3658    /// shows blocked in a kernel-run call whose continuation turn has not been
3659    /// granted (`GlobalRequest::KernelRunTarget`, never which pool holds the
3660    /// thread), when the signal would interrupt that call
3661    /// (`wait_signal_end::kernel_run_send_refused`: unblocked by the mask its
3662    /// request published, and caught or default-fatal; for a vfork parent
3663    /// only a non-core-dumping default-fatal one). The call runs in the kernel
3664    /// under the in-guest runtime's mask, so the signal would stay pending and
3665    /// the call never end for it; a kill followed by a `waitpid` would hang.
3666    /// So the send gets `EPERM`, after the run-wide stopping refusal
3667    /// `HERMIT_KERNEL_RUN_SIGNAL_REFUSED` (D49), in the sender's turn; under
3668    /// the gate that ends the run and this does not return. Dispositions that
3669    /// cannot be read refuse, fail closed. Signal 0, SIGKILL (Detcore retires
3670    /// its victims at the sender's turn), a send to the sender itself, and
3671    /// every send with the gate off take the ordinary path (`None`).
3672    ///
3673    /// Only a valid send reaches the refusal: a send `syscall` the kernel
3674    /// would fail before sending anything gets that errno instead, with no
3675    /// refusal (`refused_send_kernel_errno`, with `named`, the task the call
3676    /// names, as the scheduler numbers it).
3677    async fn refuse_kernel_run_target_send<G: Guest<Self>>(
3678        &self,
3679        guest: &mut G,
3680        syscall: syscalls::Syscall,
3681        target: DetTid,
3682        named: DetTid,
3683        raw_signal: i32,
3684    ) -> Option<Result<i64, Error>> {
3685        let sysno = reverie::syscalls::SyscallInfo::number(&syscall);
3686        if !guest.config().in_guest_signal_delivery
3687            || raw_signal == 0
3688            || raw_signal == libc::SIGKILL
3689            || target == guest.thread_state().dettid
3690        {
3691            return None;
3692        }
3693        let block = crate::tool_global::kernel_run_target(guest, target).await?;
3694        let task = reverie::Pid::from_raw(target.as_raw());
3695        let state = crate::syscalls::read_thread_signal_state(task, task);
3696        let refused = match &state {
3697            Ok(state) => crate::wait_signal_end::kernel_run_send_refused(
3698                state,
3699                block.mask.unwrap_or(state.blocked),
3700                raw_signal,
3701                block.vfork,
3702                guest.config().in_guest_reserved_signals,
3703            ),
3704            Err(_) => true,
3705        };
3706        if !refused {
3707            return None;
3708        }
3709        let (siginfo, group) = refused_send_arguments(syscall);
3710        if let Some(errno) = self
3711            .refused_send_kernel_errno(guest, raw_signal, siginfo, named, group)
3712            .await
3713        {
3714            return Some(Err(errno.into()));
3715        }
3716        let reason = crate::refused_signal_controls::kernel_run_signal_refusal(
3717            sysno.name(),
3718            raw_signal,
3719            Some(guest.thread_state().dettid.as_raw()),
3720            target.as_raw(),
3721            block.vfork,
3722            state.is_err(),
3723        );
3724        tracing::error!("{}", reason);
3725        crate::tool_global::stopping_refusal(guest, reason).await;
3726        Some(Err(Errno::EPERM.into()))
3727    }
3728
3729    /// Tell the scheduler that a successful thread-directed send left a signal
3730    /// physically pending for another task.
3731    ///
3732    /// Shared by `kill`, `tgkill` and `tkill` so the three cannot drift: a fix
3733    /// applied to one spelling of "signal another task" must apply to all of
3734    /// them, which is exactly the gap that let a `pthread_kill` from a sibling
3735    /// thread hang a `waitid` that a `kill` from a sibling process could
3736    /// interrupt. Self-directed signals are excluded: the sender is running, so
3737    /// it is not parked waiting to be woken.
3738    pub(crate) async fn notify_cross_task_signal<G: Guest<Self>>(
3739        &self,
3740        guest: &mut G,
3741        target: DetTid,
3742        raw_signal: i32,
3743        target_process: Option<DetPid>,
3744        scope: crate::tool_global::SignalScope,
3745    ) {
3746        // ⚠️ NO `Signal::try_from` GATE. It used to stand here, and because
3747        // `nix`'s `Signal` models only 1..=31 it rejected EVERY realtime signal:
3748        // measured in-tree, the gate admitted exactly 1..=31 and zero of the 31
3749        // realtime signals. The notification was skipped silently, so a target
3750        // parked on `ResourceID::WaitChild` was never woken and the wait hung.
3751        // `SigWrapper` now carries the raw number, so every deliverable signal
3752        // can be represented and none is dropped for being unnameable. Signal
3753        // zero is only an existence/permission probe and queues no signal.
3754        if should_notify_cross_task_signal(guest.thread_state().dettid, target, raw_signal) {
3755            notify_signal_pending(guest, target, SigWrapper(raw_signal), target_process, scope)
3756                .await;
3757        }
3758    }
3759
3760    /// Refuse, with EPERM and a WARN, a send of one of the in-guest runtime's
3761    /// reserved signals (`Config::in_guest_reserved_signals`) to a process
3762    /// other than the caller's own. `None` leaves the call to its handler's
3763    /// ordinary path, unchanged. A5 step 3c, design section 16.30: such a
3764    /// signal would run the target's runtime handler on a thread that does not
3765    /// hold the scheduler's turn.
3766    ///
3767    /// Each send handler calls this beside its other opening checks (pidfd's
3768    /// once its descriptor names a target), so every later path (record and
3769    /// replay, a nested caller's own-init forward) is covered. It decides from
3770    /// the configured set, the signal number and Detcore's model of processes
3771    /// and their live threads alone:
3772    /// - a signal outside the set takes the ordinary path before any request
3773    ///   is made, so with the set empty (every backend but the in-guest ones'
3774    ///   default route) nothing at all changes; signal 0 is never in it;
3775    /// - a target in the caller's own process takes the ordinary path. A
3776    ///   caller in the container's PID namespace that names its own thread or
3777    ///   process makes no request for it; a caller in a nested namespace first
3778    ///   has each id translated (one `GuestPid` request per id);
3779    /// - only a target with a live thread in Detcore's model is refused. A
3780    ///   target with none takes the ordinary path, which keeps Linux's answer:
3781    ///   a zombie (exited, not yet reaped) still takes a signal (0), and a
3782    ///   reaped process, an id no task has, or a thread that has exited is
3783    ///   ESRCH. So are an id Detcore cannot translate, a non-positive id, and a
3784    ///   thread outside the group `tgkill` names;
3785    /// - group sends (`kill(0)`, `kill(-pgid)`, `kill(-1)`, and
3786    ///   `pidfd_send_signal`'s process-group scope) are not passed here: their
3787    ///   handlers refuse every non-SIGKILL group send first
3788    ///   (`refuse_group_signal`), a stopping refusal or ENOSYS by policy, and
3789    ///   SIGKILL is never reserved.
3790    ///
3791    /// A send it would refuse first gets the answers Linux gives before any
3792    /// permission check, without the WARN: a `tgkill` or `rt_tgsigqueueinfo`
3793    /// whose tgid is not the named thread's own process is ESRCH
3794    /// (`do_send_specific`), and an unreadable or malformed `siginfo`
3795    /// (`siginfo`) is EFAULT, E2BIG or, for `pidfd_send_signal`, EINVAL
3796    /// (`reserved_send_siginfo_errno`). Nothing is delivered either way.
3797    pub(crate) async fn refuse_reserved_signal_send<G: Guest<Self>>(
3798        &self,
3799        guest: &mut G,
3800        sysno: Sysno,
3801        signal: i32,
3802        target: ReservedSendTarget,
3803        siginfo: ReservedSendSiginfo,
3804    ) -> Option<Result<i64, Error>> {
3805        if !guest.config().reserves_in_guest_signal(signal.into()) {
3806            return None;
3807        }
3808        let own = self.detpid;
3809        let caller = guest.thread_state().dettid;
3810        // `query`, the id whose process's live threads decide, `thread`, the
3811        // thread a thread-directed send must reach, and `group`, the thread
3812        // group a `tgkill` or `rt_tgsigqueueinfo` names.
3813        let (query, thread, group) = match target {
3814            ReservedSendTarget::Process(pid) => {
3815                if pid <= 0 {
3816                    return None;
3817                }
3818                let named = self.reserved_send_task(guest, pid).await?;
3819                if reserved_send_names_own(own, caller, named, None) {
3820                    return None;
3821                }
3822                (named, None, None)
3823            }
3824            ReservedSendTarget::Thread { tgid, tid } => {
3825                if tid <= 0 || tgid.is_some_and(|tgid| tgid <= 0) {
3826                    return None;
3827                }
3828                let thread = self.reserved_send_task(guest, tid).await?;
3829                let group = match tgid {
3830                    Some(tgid) => Some(self.reserved_send_task(guest, tgid).await?),
3831                    None => None,
3832                };
3833                if reserved_send_names_own(own, caller, thread, group) {
3834                    return None;
3835                }
3836                (group.unwrap_or(thread), Some(thread), group)
3837            }
3838            ReservedSendTarget::Pidfd { named, thread } => {
3839                let named = named?;
3840                if reserved_send_names_own(own, caller, named, None) {
3841                    return None;
3842                }
3843                (named, thread.then_some(named), None)
3844            }
3845        };
3846        let (targets, _) = resolve_kill_targets_and_exit(guest, query).await;
3847        if !reserved_send_refuses(caller, thread, &targets) {
3848            return None;
3849        }
3850        let leader = registered_process_of(guest, targets[0]).await;
3851        if !reserved_send_names_its_group(group, leader) {
3852            return None;
3853        }
3854        if let Some(errno) = self.reserved_send_siginfo_errno(guest, signal, siginfo) {
3855            return Some(Err(errno.into()));
3856        }
3857        let process = leader.unwrap_or(query);
3858        tracing::warn!(
3859            "{}",
3860            crate::refused_signal_controls::reserved_signal_send_refused_warning(
3861                sysno.name(),
3862                signal,
3863                caller.as_raw(),
3864                process.as_raw(),
3865            )
3866        );
3867        Some(Err(Errno::EPERM.into()))
3868    }
3869
3870    /// A5 step 3c.1c, design D48 with IR-4(b): under the in-guest delivery
3871    /// gate, refuse with EPERM and a WARN a send of a stop signal to any
3872    /// in-guest process, the caller's own included: SIGSTOP always, and
3873    /// SIGTSTP, SIGTTIN or SIGTTOU while the target's action is `SIG_DFL`. The
3874    /// in-guest backend cannot stop a guest process on the schedule, and a
3875    /// stopped one would hold the run (16.1, D48(a)). SIGCONT is admitted: no
3876    /// guest process is stopped, so it acts as a default-ignored signal (D48(i)).
3877    ///
3878    /// "The target's action is `SIG_DFL`" is read from the named live
3879    /// thread's `/proc/<tid>/status` (`SigIgn` and `SigCgt`), in the sender's
3880    /// turn: under premise P the target is inside its runtime then, and on the
3881    /// in-guest backends a guest thread id names its host task
3882    /// (`procfs_inode::guest_tids_name_host_tasks`). A status that cannot be
3883    /// read refuses the send, fail closed. A target with no live thread in
3884    /// Detcore's model keeps the ordinary path (a zombie takes the signal, 0;
3885    /// a reaped or unknown id is ESRCH), as do a thread outside the group a
3886    /// `tgkill` names, a non-positive id, and group sends, which their handlers
3887    /// refuse first (F15). With the gate off, or another signal, `None`.
3888    ///
3889    /// Only a valid send reaches the refusal: one the kernel would fail
3890    /// before sending anything (its `siginfo`, a forged `si_code`;
3891    /// `refused_send_kernel_errno`) gets that errno, without the WARN.
3892    pub(crate) async fn refuse_stop_signal_send<G: Guest<Self>>(
3893        &self,
3894        guest: &mut G,
3895        sysno: Sysno,
3896        signal: i32,
3897        target: ReservedSendTarget,
3898        siginfo: ReservedSendSiginfo,
3899    ) -> Option<Result<i64, Error>> {
3900        use crate::refused_signal_controls::StopSendRefusal;
3901        if !guest.config().in_guest_signal_delivery
3902            || !matches!(
3903                signal,
3904                libc::SIGSTOP | libc::SIGTSTP | libc::SIGTTIN | libc::SIGTTOU
3905            )
3906        {
3907            return None;
3908        }
3909        let (query, thread) = match target {
3910            ReservedSendTarget::Process(pid) => {
3911                if pid <= 0 {
3912                    return None;
3913                }
3914                (self.reserved_send_task(guest, pid).await?, None)
3915            }
3916            ReservedSendTarget::Thread { tgid, tid } => {
3917                if tid <= 0 || tgid.is_some_and(|tgid| tgid <= 0) {
3918                    return None;
3919                }
3920                let thread = self.reserved_send_task(guest, tid).await?;
3921                let group = match tgid {
3922                    Some(tgid) => Some(self.reserved_send_task(guest, tgid).await?),
3923                    None => None,
3924                };
3925                (group.unwrap_or(thread), Some(thread))
3926            }
3927            ReservedSendTarget::Pidfd { named, thread } => {
3928                let named = named?;
3929                (named, thread.then_some(named))
3930            }
3931        };
3932        let (targets, _) = resolve_kill_targets_and_exit(guest, query).await;
3933        let receiver = match thread {
3934            Some(thread) if targets.contains(&thread) => thread,
3935            Some(_) => return None,
3936            None => *targets.first()?,
3937        };
3938        let why = if signal == libc::SIGSTOP {
3939            StopSendRefusal::Sigstop
3940        } else {
3941            let task = reverie::Pid::from_raw(receiver.as_raw());
3942            match crate::syscalls::read_thread_signal_state(task, task) {
3943                Ok(state) if (state.ignored | state.caught) & kernel_sigset_bit(signal) != 0 => {
3944                    return None;
3945                }
3946                Ok(_) => StopSendRefusal::DefaultAction,
3947                Err(_) => StopSendRefusal::StatusUnreadable,
3948            }
3949        };
3950        // A thread-directed send names `receiver`; a process-directed one
3951        // names the process `query` is.
3952        let named = thread.unwrap_or(query);
3953        if let Some(errno) = self
3954            .refused_send_kernel_errno(guest, signal, siginfo, named, None)
3955            .await
3956        {
3957            return Some(Err(errno.into()));
3958        }
3959        tracing::warn!(
3960            "{}",
3961            crate::refused_signal_controls::stop_signal_send_refused_warning(
3962                sysno.name(),
3963                signal,
3964                guest.thread_state().dettid.as_raw(),
3965                receiver.as_raw(),
3966                why,
3967            )
3968        );
3969        Some(Err(Errno::EPERM.into()))
3970    }
3971
3972    /// A5 step 3c.1c: Linux's answer, before anything is sent, for a send to
3973    /// the live task `named` (as the scheduler numbers it) that a D44 or D48
3974    /// refusal would otherwise refuse, so that only a valid send reaches a
3975    /// refusal. In the kernel's order (v6.12 kernel/signal.c):
3976    ///
3977    /// 1. the copy of the `siginfo` from the caller's memory, EFAULT or E2BIG,
3978    ///    and for `pidfd_send_signal` EINVAL when its `si_signo` is not the
3979    ///    signal (`reserved_send_siginfo_errno`; `rt_sigqueueinfo`,
3980    ///    :4079-4087, `rt_tgsigqueueinfo`, :4119-4127, `pidfd_send_signal`,
3981    ///    :3959-3966);
3982    /// 2. a non-positive tgid named by `tgkill` or `rt_tgsigqueueinfo`
3983    ///    (`group`), EINVAL (:4038-4039, :4106-4107);
3984    /// 3. a `si_code` only the target thread itself may use (`si_code >= 0`
3985    ///    or `SI_TKILL`) from a caller that is not the task the call names,
3986    ///    EPERM (`do_rt_sigqueueinfo`, :4065-4067; `do_rt_tgsigqueueinfo`,
3987    ///    :4112-4114; `pidfd_send_signal`, :3968-3972);
3988    /// 4. a tgid that is not `named`'s process, ESRCH (`do_send_specific`,
3989    ///    :3994). A tgid Detcore cannot name (a nested caller's, on a backend
3990    ///    that names no nested ids) is not judged here.
3991    ///
3992    /// `None` for a valid send. The signal number is valid: D44 and D48 pass
3993    /// only signals 1 to 64, which `valid_signal` (:836) admits, and the target
3994    /// exists. A siginfo Linux could copy but Detcore cannot read
3995    /// (`refused_send_siginfo`, `Unverifiable`) leaves its later checks
3996    /// unjudged, so the send takes its refusal, fail closed, rather than an
3997    /// errno Linux would not give.
3998    async fn refused_send_kernel_errno<G: Guest<Self>>(
3999        &self,
4000        guest: &mut G,
4001        signal: i32,
4002        siginfo: ReservedSendSiginfo,
4003        named: DetTid,
4004        group: Option<libc::pid_t>,
4005    ) -> Option<Errno> {
4006        let code = match refused_send_siginfo(guest, signal, siginfo) {
4007            RefusedSendSiginfo::Errno(errno) => return Some(errno),
4008            RefusedSendSiginfo::NoCopy => None,
4009            RefusedSendSiginfo::Copied { code } => Some(code),
4010            RefusedSendSiginfo::Unverifiable => {
4011                if group.is_some_and(|tgid| tgid <= 0) {
4012                    return Some(Errno::EINVAL);
4013                }
4014                tracing::info!(
4015                    "[detcore, dtid {}] a send's siginfo lies in memory the caller's own reads \
4016                     cannot reach though Linux could copy it: its si_code and target checks are \
4017                     not judged, and the send takes its refusal",
4018                    guest.thread_state().dettid,
4019                );
4020                return None;
4021            }
4022        };
4023        if group.is_some_and(|tgid| tgid <= 0) {
4024            return Some(Errno::EINVAL);
4025        }
4026        if let Some(code) = code
4027            && (code >= 0 || code == libc::SI_TKILL)
4028            && named != guest.thread_state().dettid
4029        {
4030            return Some(Errno::EPERM);
4031        }
4032        if let Some(tgid) = group
4033            && let Some(process) = self.reserved_send_task(guest, tgid).await
4034        {
4035            let leader = registered_process_of(guest, named).await;
4036            if leader != Some(DetPid::from_raw(process.as_raw())) {
4037                return Some(Errno::ESRCH);
4038            }
4039        }
4040        None
4041    }
4042
4043    /// Linux's answer for the `siginfo` of a send that would otherwise be
4044    /// refused (`refuse_reserved_signal_send`), which it gives before any
4045    /// permission check: the copy from the caller's memory, read here in the
4046    /// caller's own turn and in Linux's order (`siginfo_copy_errno`): the
4047    /// head, then the expansion only for a layout Linux does not know. A
4048    /// range that would pass the end of the address space is EFAULT, never
4049    /// computed. `None` when the copy succeeds or the call carries none.
4050    ///
4051    /// The bytes are read through `Guest::memory()`. On the in-guest backends
4052    /// that is `process_vm_readv` (reverie-inguest `read_guest`,
4053    /// src/guest/host.rs:2463-2476 at a214eb85), which refuses some memory
4054    /// x86's `copy_from_user` can read, such as a write-only mapping: such a
4055    /// siginfo is EFAULT here where Linux would copy it. Detcore's ptrace-side
4056    /// readers (`read_siginfo`, `read_mapped_prefix`,
4057    /// `rt_sigqueueinfo_precheck`) fall back to `/proc/<tid>/mem` for that,
4058    /// which cannot be reused in-guest, where guest thread ids are not host
4059    /// thread ids.
4060    fn reserved_send_siginfo_errno<G: Guest<Self>>(
4061        &self,
4062        guest: &mut G,
4063        signal: i32,
4064        siginfo: ReservedSendSiginfo,
4065    ) -> Option<Errno> {
4066        let (address, pidfd) = match siginfo {
4067            ReservedSendSiginfo::None => return None,
4068            ReservedSendSiginfo::Queued(address) => (address, false),
4069            ReservedSendSiginfo::Pidfd(None) => return None,
4070            ReservedSendSiginfo::Pidfd(Some(address)) => (address, true),
4071        };
4072        // Each range must end inside the address space: a pointer within a
4073        // siginfo's length of its top cannot be copied (EFAULT).
4074        let head = address
4075            .checked_add(KERNEL_SIGINFO_SIZE)
4076            .and_then(|_| AddrMut::<[u8; KERNEL_SIGINFO_SIZE]>::from_raw(address))
4077            .and_then(|head| guest.memory().read_value(head).ok());
4078        let read_expansion = || {
4079            let start = address.checked_add(KERNEL_SIGINFO_SIZE)?;
4080            start.checked_add(SI_EXPANSION_SIZE)?;
4081            let expansion = AddrMut::<[u8; SI_EXPANSION_SIZE]>::from_raw(start)?;
4082            guest.memory().read_value(expansion).ok()
4083        };
4084        siginfo_copy_errno(signal, pidfd, head, read_expansion)
4085    }
4086
4087    /// The task a reserved send's `id` names (`refuse_reserved_signal_send`):
4088    /// the id itself for a caller in the container's PID namespace, and for a
4089    /// caller in a nested one the task its namespace's `id` is. `None` when
4090    /// Detcore cannot name one, which leaves the call to its ordinary path.
4091    async fn reserved_send_task<G: Guest<Self>>(&self, guest: &mut G, id: i32) -> Option<DetTid> {
4092        if !guest.thread_state().nested_pid_namespace {
4093            return Some(DetTid::from_raw(id));
4094        }
4095        if guest
4096            .config()
4097            .backend
4098            .requires_thread_directed_process_signals
4099        {
4100            return None;
4101        }
4102        match crate::tool_global::guest_pid(guest, id).await {
4103            GuestPidAnswer::Task { task, .. } => Some(task),
4104            GuestPidAnswer::NoSuchPid | GuestPidAnswer::Untranslatable(_) => None,
4105        }
4106    }
4107
4108    /// Refuse, with EPERM and a WARN, an arming of one of the in-guest
4109    /// runtime's reserved signals (`Config::in_guest_reserved_signals`) as a
4110    /// notification signal by `arming`, whatever the target: its deliveries
4111    /// would reach the runtime's handler for the signal rather than the
4112    /// guest's action (A5 step 3c, design section 16.30). The caller makes
4113    /// Linux's earlier checks of the call first. A disarm (signal 0) is never
4114    /// reserved, and with the set empty nothing changes.
4115    pub(crate) fn refuse_reserved_signal_arming<G: Guest<Self>>(
4116        guest: &mut G,
4117        arming: crate::refused_signal_controls::ReservedArming,
4118        signal: i64,
4119    ) -> Result<(), Error> {
4120        if !guest.config().reserves_in_guest_signal(signal) {
4121            return Ok(());
4122        }
4123        tracing::warn!(
4124            "{}",
4125            crate::refused_signal_controls::reserved_signal_arming_refused_warning(
4126                arming,
4127                signal as i32,
4128                guest.thread_state().dettid.as_raw(),
4129            )
4130        );
4131        Err(Errno::EPERM.into())
4132    }
4133
4134    /// Refuse by name a guest's `SIGCHLD` that carries the scheduler's own-copy
4135    /// mark (`SI_QUEUE` with `OWN_SIGCHLD_MARK`), which a receiver in a nested
4136    /// PID namespace would take for the scheduler's copy
4137    /// (<https://github.com/rrnewton/hermit/issues/4155>). `None` for any other
4138    /// call; an unreadable siginfo is left to the kernel's EFAULT.
4139    pub(crate) async fn refuse_forged_own_sigchld<G: Guest<Self>>(
4140        &self,
4141        guest: &mut G,
4142        sysno: Sysno,
4143        signal: i32,
4144        info: Option<AddrMut<'_, GuestSiginfo>>,
4145    ) -> Option<Result<i64, Error>> {
4146        if signal != libc::SIGCHLD {
4147            return None;
4148        }
4149        let bytes = read_siginfo(guest, info?.cast())?;
4150        let (code, _) = crate::scheduler::child_exit_sigchld::siginfo_code_and_pid(&bytes);
4151        let value = i32::from_ne_bytes(bytes[24..28].try_into().expect("four bytes"));
4152        if code != libc::SI_QUEUE || value != crate::scheduler::child_exit_sigchld::OWN_SIGCHLD_MARK
4153        {
4154            return None;
4155        }
4156        tracing::error!(
4157            "[detcore, dtid {}] {} sends a SIGCHLD carrying the scheduler's own-copy mark {:#x}, which a nested PID namespace's receiver could not tell from the scheduler's (https://github.com/rrnewton/hermit/issues/4155)",
4158            guest.thread_state().dettid,
4159            sysno,
4160            crate::scheduler::child_exit_sigchld::OWN_SIGCHLD_MARK,
4161        );
4162        Some(
4163            self.refuse_unserviceable_operation(guest, sysno, Errno::EPERM)
4164                .await,
4165        )
4166    }
4167
4168    // AUTONOMOUS-BOT-IMPLEMENTED
4169    // TODO-HUMAN-REVIEW(#812)
4170    /// Queue a thread-directed signal with an accompanying `siginfo_t`. Like
4171    /// `tgkill`, the target is a specific thread named by stable guest TGID/TID
4172    /// and delivery is scheduler-serialized; the guest-supplied siginfo is
4173    /// deterministic input, so forwarding the kernel call is deterministic.
4174    pub async fn handle_rt_tgsigqueueinfo<G: Guest<Self>>(
4175        &self,
4176        guest: &mut G,
4177        call: syscalls::RtTgsigqueueinfo,
4178    ) -> Result<i64, Error> {
4179        if let Some(refused) = self
4180            .refuse_forged_own_sigchld(
4181                guest,
4182                Sysno::rt_tgsigqueueinfo,
4183                call.sig(),
4184                call.siginfo().map(AddrMut::cast),
4185            )
4186            .await
4187        {
4188            return refused;
4189        }
4190        if let Some(refused) = self
4191            .refuse_reserved_signal_send(
4192                guest,
4193                Sysno::rt_tgsigqueueinfo,
4194                call.sig(),
4195                ReservedSendTarget::Thread {
4196                    tgid: Some(call.tgid()),
4197                    tid: call.tid(),
4198                },
4199                ReservedSendSiginfo::Queued(call.siginfo().map_or(0, |info| info.as_raw())),
4200            )
4201            .await
4202        {
4203            return refused;
4204        }
4205        // D48 (A5 step 3c.1c): a stop signal under the in-guest delivery
4206        // gate, the caller's own process included.
4207        if let Some(refused) = self
4208            .refuse_stop_signal_send(
4209                guest,
4210                Sysno::rt_tgsigqueueinfo,
4211                call.sig(),
4212                ReservedSendTarget::Thread {
4213                    tgid: Some(call.tgid()),
4214                    tid: call.tid(),
4215                },
4216                ReservedSendSiginfo::Queued(call.siginfo().map_or(0, |info| info.as_raw())),
4217            )
4218            .await
4219        {
4220            return refused;
4221        }
4222        self.record_orphaned_group_signals(guest, call.sig()).await;
4223        // A nested caller names its own namespace's tid
4224        // (https://github.com/rrnewton/hermit/issues/4155); a tid no task of
4225        // that namespace has goes to the kernel, which checks the siginfo
4226        // first and then answers ESRCH.
4227        let nested = guest.thread_state().nested_pid_namespace;
4228        let tid = if nested && call.tgid() > 0 && call.tid() > 0 {
4229            match self
4230                .name_guest_pid(guest, Sysno::rt_tgsigqueueinfo, call.tid())
4231                .await
4232            {
4233                NamedGuestPid::Task {
4234                    task,
4235                    own_init_caught,
4236                } => {
4237                    if Self::own_namespace_init_drops(own_init_caught, call.sig()) {
4238                        return self
4239                            .send_to_own_init(guest, call, task, QueueId::Thread(task), call.sig())
4240                            .await;
4241                    }
4242                    task.as_raw()
4243                }
4244                NamedGuestPid::Answer(Err(Error::Errno(Errno::ESRCH))) => {
4245                    return Ok(self.record_or_replay(guest, call).await?);
4246                }
4247                NamedGuestPid::Answer(answer) => return answer,
4248            }
4249        } else {
4250            call.tid()
4251        };
4252        if call.sig() == libc::SIGALRM {
4253            refuse_sigalrm(guest, SigalrmControl::SendTo(DetTid::from_raw(tid))).await?;
4254        }
4255        if !nested
4256            && self
4257                .drop_signal_to_hermit(
4258                    guest,
4259                    Sysno::rt_tgsigqueueinfo,
4260                    Some(call.tgid()),
4261                    tid,
4262                    call.sig(),
4263                )
4264                .await
4265        {
4266            return Ok(0);
4267        }
4268        // A SIGKILL to an exited leader changes nothing, as in `tgkill`
4269        // (https://github.com/rrnewton/hermit/issues/4445).
4270        let exited_leader = call.sig() == libc::SIGKILL
4271            && guest.config().sequentialize_threads
4272            && tid > 0
4273            && sigkill_names_an_exited_leader(guest, DetTid::from_raw(tid)).await;
4274        // Any other SIGKILL to a thread of a valid pair in another process
4275        // ends that thread's whole process, and is reserved and retired at
4276        // this turn as `tgkill`'s is
4277        // (https://github.com/rrnewton/hermit/issues/3994); before, its victims
4278        // were retired at their host-timed deaths. A send the kernel then
4279        // refuses (EFAULT, EPERM, E2BIG: its siginfo checks are subtle)
4280        // releases the reservation, because `send_reserved_sigkill` commits a
4281        // send that excludes the sender with its outcome. A send to the
4282        // sender's own process is not reserved: it would take the sender's
4283        // Exit grant before the call runs, and so must know beforehand that
4284        // the kernel accepts the siginfo (Codex review); it keeps its
4285        // host-timed retirement. Nor is a nested caller's send, whose tgid
4286        // names its own namespace.
4287        if call.sig() == libc::SIGKILL
4288            && !nested
4289            && !exited_leader
4290            && guest.config().sequentialize_threads
4291            && call.tgid() > 0
4292            && tid > 0
4293            && guest
4294                .thread_state()
4295                .detpid
4296                .is_none_or(|own| own.as_raw() != call.tgid())
4297            && registered_process_of(guest, DetTid::from_raw(tid)).await
4298                == Some(DetPid::from_raw(call.tgid()))
4299            && let Some(reservation) = reserve_sigkill(guest, DetTid::from_raw(tid)).await
4300        {
4301            if reservation.includes_sender {
4302                // Only when the sender's own process was not known above: give
4303                // the reservation back and take the unreserved path.
4304                commit_sigkill(guest, reservation.token, false).await;
4305            } else {
4306                return self.send_reserved_sigkill(guest, reservation, call).await;
4307            }
4308        }
4309        let value = self
4310            .send_guest_signal(
4311                guest,
4312                call,
4313                DetTid::from_raw(call.tid()),
4314                call.sig(),
4315                None,
4316                true,
4317                DetTid::from_raw(tid),
4318                exited_leader || tid <= 0 || call.tgid() <= 0,
4319            )
4320            .await?;
4321        if exited_leader {
4322            return Ok(value);
4323        }
4324        // Thread-directed like `tgkill`, so it carries the same wakeup
4325        // obligation. `sigqueue`/`pthread_sigqueue` reach a parked sibling
4326        // through here.
4327        self.notify_cross_task_signal(
4328            guest,
4329            DetTid::from_raw(tid),
4330            call.sig(),
4331            None,
4332            crate::tool_global::SignalScope::Thread,
4333        )
4334        .await;
4335        Ok(value)
4336    }
4337
4338    // AUTONOMOUS-BOT-IMPLEMENTED
4339    // TODO-HUMAN-REVIEW(#812)
4340    // TODO-HUMAN-REVIEW(PR-1058): Review queued process-signal preservation.
4341    /// Queue a process-directed signal with an accompanying `siginfo_t`. Mirrors `handle_kill`:
4342    /// preserve process-directed delivery when the backend accepts guest PIDs, otherwise route an
4343    /// unambiguous positive-PID target to its sole live thread via `rt_tgsigqueueinfo`. Ambiguous
4344    /// multithreaded process-directed delivery is refused until Detcore models eligible masks.
4345    pub async fn handle_rt_sigqueueinfo<G: Guest<Self>>(
4346        &self,
4347        guest: &mut G,
4348        call: syscalls::RtSigqueueinfo,
4349    ) -> Result<i64, Error> {
4350        if let Some(refused) = self
4351            .refuse_forged_own_sigchld(
4352                guest,
4353                Sysno::rt_sigqueueinfo,
4354                call.sig(),
4355                call.siginfo().map(AddrMut::cast),
4356            )
4357            .await
4358        {
4359            return refused;
4360        }
4361        // Linux names no group here: a non-positive pid is ESRCH.
4362        if let Some(refused) = self
4363            .refuse_reserved_signal_send(
4364                guest,
4365                Sysno::rt_sigqueueinfo,
4366                call.sig(),
4367                ReservedSendTarget::Process(call.tgid()),
4368                ReservedSendSiginfo::Queued(call.siginfo().map_or(0, |info| info.as_raw())),
4369            )
4370            .await
4371        {
4372            return refused;
4373        }
4374        // D48 (A5 step 3c.1c): a stop signal under the in-guest delivery
4375        // gate, the caller's own process included.
4376        if let Some(refused) = self
4377            .refuse_stop_signal_send(
4378                guest,
4379                Sysno::rt_sigqueueinfo,
4380                call.sig(),
4381                ReservedSendTarget::Process(call.tgid()),
4382                ReservedSendSiginfo::Queued(call.siginfo().map_or(0, |info| info.as_raw())),
4383            )
4384            .await
4385        {
4386            return refused;
4387        }
4388        self.record_orphaned_group_signals(guest, call.sig()).await;
4389        let nested = guest.thread_state().nested_pid_namespace;
4390        if !guest.config().sequentialize_threads {
4391            if !nested
4392                && self
4393                    .drop_signal_to_hermit(
4394                        guest,
4395                        Sysno::rt_sigqueueinfo,
4396                        None,
4397                        call.tgid(),
4398                        call.sig(),
4399                    )
4400                    .await
4401            {
4402                return Ok(0);
4403            }
4404            return Ok(self.record_or_replay(guest, call).await?);
4405        }
4406
4407        // A nested caller names its own namespace's pid
4408        // (https://github.com/rrnewton/hermit/issues/4155); a pid no task of
4409        // that namespace has goes to the kernel, which checks the siginfo
4410        // first and then answers ESRCH.
4411        let tgid = if nested && call.tgid() > 0 {
4412            match self
4413                .name_guest_pid(guest, Sysno::rt_sigqueueinfo, call.tgid())
4414                .await
4415            {
4416                NamedGuestPid::Task {
4417                    task,
4418                    own_init_caught,
4419                } => {
4420                    if Self::own_namespace_init_drops(own_init_caught, call.sig()) {
4421                        return self
4422                            .send_to_own_init(guest, call, task, QueueId::Process(task), call.sig())
4423                            .await;
4424                    }
4425                    task.as_raw()
4426                }
4427                NamedGuestPid::Answer(Err(Error::Errno(Errno::ESRCH))) => {
4428                    return Ok(self.record_or_replay(guest, call).await?);
4429                }
4430                NamedGuestPid::Answer(answer) => return answer,
4431            }
4432        } else {
4433            call.tgid()
4434        };
4435        if tgid <= 0 {
4436            // POSIX.1b has no process groups: Linux's `do_rt_sigqueueinfo` looks
4437            // the pid up with `find_vpid`, which finds no process for 0 or a
4438            // negative pid, so the call fails (EFAULT for an unreadable
4439            // `siginfo_t`, EPERM for a forged `si_code`, E2BIG for an unknown
4440            // `si_code` with nonzero expansion bytes, otherwise ESRCH) and
4441            // sends nothing. The kernel's own answer is therefore deterministic
4442            // (https://github.com/rrnewton/hermit/issues/4046).
4443            return Ok(self.record_or_replay(guest, call).await?);
4444        }
4445        if call.sig() == 0 {
4446            // An existence check answers from the same exit model as a signal
4447            // (https://github.com/rrnewton/hermit/issues/4124): a process
4448            // Detcore saw exit and that is gone is ESRCH after the siginfo
4449            // checks, whatever the host has reaped by now.
4450            let (targets, zombie) =
4451                resolve_kill_targets_and_exit(guest, DetPid::from_raw(tgid)).await;
4452            if targets.is_empty() && zombie == Some(false) {
4453                return Ok(self
4454                    .record_or_replay(guest, call.with_tgid(NO_SUCH_PID))
4455                    .await?);
4456            }
4457            return Ok(self.record_or_replay(guest, call).await?);
4458        }
4459        if call.sig() == libc::SIGALRM {
4460            refuse_sigalrm(guest, SigalrmControl::SendTo(DetTid::from_raw(tgid))).await?;
4461        }
4462        if self
4463            .drop_signal_to_hermit(guest, Sysno::rt_sigqueueinfo, None, tgid, call.sig())
4464            .await
4465        {
4466            return Ok(0);
4467        }
4468        // A call the kernel fails without sending, after a complete copy of
4469        // its `siginfo_t` (EPERM for a `si_code` only the target thread itself
4470        // may use), goes to it unchanged; a copy that certainly faults is
4471        // answered EFAULT here; and a `siginfo_t` Hermit cannot read whole is
4472        // refused by name (`rt_sigqueueinfo_precheck`).
4473        if guest.config().backend.host_task_reads_reach_guest_threads {
4474            match self.rt_sigqueueinfo_precheck(guest, &call) {
4475                RtSigqueueinfoPrecheck::KernelFails => {
4476                    return Ok(self.record_or_replay(guest, call).await?);
4477                }
4478                RtSigqueueinfoPrecheck::Efault => return Err(Errno::EFAULT.into()),
4479                RtSigqueueinfoPrecheck::Unreadable => {
4480                    tracing::error!(
4481                        "[detcore, dtid {}] rt_sigqueueinfo with a siginfo_t at {:#x} is not \
4482                         supported: Hermit cannot read all 128 bytes of it to decide the call \
4483                         (https://github.com/rrnewton/hermit/issues/4034)",
4484                        guest.thread_state().dettid,
4485                        call.siginfo().map_or(0, |info| info.as_raw()),
4486                    );
4487                    return self
4488                        .refuse_unserviceable_operation(
4489                            guest,
4490                            Sysno::rt_sigqueueinfo,
4491                            Errno::ENOSYS,
4492                        )
4493                        .await;
4494                }
4495                RtSigqueueinfoPrecheck::Proceeds => {}
4496            }
4497        }
4498        // As for kill, a live non-leader thread's id names its process
4499        // (https://github.com/rrnewton/hermit/issues/4094).
4500        let process = signal_target_process(guest, DetPid::from_raw(tgid))
4501            .await
4502            .map_or(tgid, |process| process.as_raw());
4503        match self
4504            .own_process_signal(guest, Sysno::rt_sigqueueinfo, tgid, process, call.sig())
4505            .await?
4506        {
4507            OwnProcessSend::Admitted => {
4508                return self
4509                    .forward_own_process_signal(guest, call.sig(), call, DetTid::from_raw(tgid))
4510                    .await;
4511            }
4512            OwnProcessSend::Refused(answer) => return answer,
4513            OwnProcessSend::NotOwn => {}
4514        }
4515        let named = DetTid::from_raw(tgid);
4516        let target = match self
4517            .process_signal_target(
4518                guest,
4519                Sysno::rt_sigqueueinfo,
4520                DetPid::from_raw(process),
4521                named,
4522                call.sig(),
4523            )
4524            .await
4525        {
4526            // Gone: Linux checks the siginfo before it looks the process up
4527            // (EFAULT, then E2BIG for an unknown code with padding, then EPERM
4528            // for a forged code), and only then answers ESRCH. Let the kernel
4529            // make those checks against a pid no task can have, so its answer
4530            // cannot depend on whether the host has reaped the real one yet
4531            // (https://github.com/rrnewton/hermit/issues/4124).
4532            Err(Error::Errno(Errno::ESRCH)) => {
4533                return Ok(self
4534                    .record_or_replay(guest, call.with_tgid(NO_SUCH_PID))
4535                    .await?);
4536            }
4537            target => target?,
4538        };
4539        let tid = match target {
4540            ProcessSignalTarget::Thread(tid) | ProcessSignalTarget::Chosen(tid) => tid,
4541            ProcessSignalTarget::SharedQueue => {
4542                return self
4543                    .send_guest_signal(
4544                        guest,
4545                        call,
4546                        DetTid::from_raw(process),
4547                        call.sig(),
4548                        Some(DetPid::from_raw(process)),
4549                        false,
4550                        named,
4551                        false,
4552                    )
4553                    .await;
4554            }
4555            ProcessSignalTarget::Zombie => {
4556                // A zombie: its parent can still wait for it, so Linux still
4557                // finds it. Unlike kill, the kernel checks the siginfo first
4558                // (EFAULT, EPERM for a forged si_code, E2BIG), so let it
4559                // answer: it validates, finds the zombie, and returns 0. A
4560                // backend that must name a receiving thread has none to name,
4561                // and keeps ESRCH
4562                // (https://github.com/rrnewton/hermit/issues/4091).
4563                if guest
4564                    .config()
4565                    .backend
4566                    .requires_thread_directed_process_signals
4567                {
4568                    return Err(Errno::ESRCH.into());
4569                }
4570                return Ok(self.record_or_replay(guest, call).await?);
4571            }
4572        };
4573        let value = if !guest
4574            .config()
4575            .backend
4576            .requires_thread_directed_process_signals
4577        {
4578            self.send_guest_signal(
4579                guest,
4580                call,
4581                tid,
4582                call.sig(),
4583                Some(DetPid::from_raw(process)),
4584                false,
4585                DetTid::from_raw(tgid),
4586                false,
4587            )
4588            .await?
4589        } else {
4590            let targeted = syscalls::RtTgsigqueueinfo::new()
4591                .with_tgid(process)
4592                .with_tid(tid.as_raw())
4593                .with_sig(call.sig())
4594                .with_siginfo(call.siginfo());
4595            self.send_guest_signal(
4596                guest,
4597                targeted,
4598                tid,
4599                call.sig(),
4600                Some(DetPid::from_raw(process)),
4601                false,
4602                DetTid::from_raw(tgid),
4603                false,
4604            )
4605            .await?
4606        };
4607        if let ProcessSignalTarget::Chosen(_) = target {
4608            crate::tool_global::process_signal_sent(guest, DetPid::from_raw(process)).await;
4609        }
4610        self.notify_cross_task_signal(
4611            guest,
4612            tid,
4613            call.sig(),
4614            Some(DetPid::from_raw(process)),
4615            crate::tool_global::SignalScope::Process,
4616        )
4617        .await;
4618        Ok(value)
4619    }
4620
4621    // AUTONOMOUS-BOT-IMPLEMENTED
4622    // TODO-HUMAN-REVIEW(#663)
4623    /// Read the kernel pending-signal mask after Detcore has serialized all signal
4624    /// generation and delivery events that can change it.
4625    pub async fn handle_rt_sigpending<G: Guest<Self>>(
4626        &self,
4627        guest: &mut G,
4628        call: syscalls::RtSigpending,
4629    ) -> Result<i64, Error> {
4630        // Signal phase 1 refuses rt_sigpending in a process that handles
4631        // SIGALRM (`sigalrm_phase1`), so the ledger's entry never needs to be
4632        // shown here. The pending set would show a real-time timer expiration
4633        // that every thread blocked when it expired, which the kernel never
4634        // queued (https://github.com/rrnewton/hermit/issues/4089).
4635        let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
4636        act_on_pending_real_time(guest, pending, REAL_TIME_SIGNALS, 0).await;
4637        Ok(self.record_or_replay(guest, call).await?)
4638    }
4639
4640    /// A delivered signal may run a handler, whose return restores, through an
4641    /// `rt_sigreturn` that ptrace does not stop at, a mask Detcore cannot read
4642    /// here: the thread's own mask, or for a handler that interrupts a call
4643    /// with a temporary mask, the mask saved before the call. So every
4644    /// delivery records every real-time signal as possibly unblocked by a
4645    /// handler's return (`Scheduler::handler_restore_unblocked`), and a
4646    /// real-time timer expiration of the process is never kept pending after
4647    /// it (https://github.com/rrnewton/hermit/issues/4089). Only where real-time
4648    /// expirations are kept pending at all (`models_signal_targets`), and once
4649    /// per thread.
4650    pub(crate) async fn record_handler_restore_mask<G: Guest<Self>>(
4651        &self,
4652        guest: &mut G,
4653        signal: SigWrapper,
4654    ) {
4655        if !self.cfg.models_signal_targets()
4656            || guest.thread_state().handler_restore_rt_reported == REAL_TIME_SIGNALS
4657        {
4658            return;
4659        }
4660        // Only a caught signal runs a handler that returns. The dispositions
4661        // are the process's, so they say that whatever mask the delivery
4662        // interrupted; a signal acted on by default (the teardown's own
4663        // termination signals among them) records nothing.
4664        let caught =
4665            match crate::syscalls::signal_shadow::copied_dispositions_for(guest, signal.raw()) {
4666                Some(copied) => Ok(copied.caught),
4667                None => read_thread_wait_signal_state(guest.pid(), guest.tid())
4668                    .map(|state| state.caught),
4669            };
4670        if let Ok(caught) = caught
4671            && caught & kernel_sigset_bit(signal.raw()) == 0
4672        {
4673            return;
4674        }
4675        let Some(detpid) = guest.thread_state().detpid else {
4676            return;
4677        };
4678        crate::tool_global::record_handler_restore_mask(guest, detpid, REAL_TIME_SIGNALS).await;
4679        guest.thread_state_mut().handler_restore_rt_reported = REAL_TIME_SIGNALS;
4680    }
4681
4682    /// rt_sigreturn: the mask it restores, from the frame's `uc_sigmask`, may
4683    /// unblock a pending real-time timer expiration that every thread blocked
4684    /// when it expired, which the kernel then delivers as the call returns
4685    /// (https://github.com/rrnewton/hermit/issues/4089). The call itself
4686    /// passes through.
4687    pub(crate) async fn observe_sigreturn_mask<G: Guest<Self>>(&self, guest: &mut G) {
4688        let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
4689        if pending == 0 {
4690            return;
4691        }
4692        // `rt_sigreturn` restores the `uc_sigmask` of the frame at `sp`.
4693        let sp = crate::arch::stack_pointer(&guest.regs().await) as usize;
4694        let restored = Addr::<u64>::from_raw(sp.wrapping_add(UC_SIGMASK_OFFSET))
4695            .and_then(|addr| guest.memory().read_value(addr).ok());
4696        let (observable, discarded) = match (
4697            restored,
4698            read_thread_wait_signal_state(guest.pid(), guest.tid()),
4699        ) {
4700            (Some(mask), Ok(state)) => unblocked_by(&state, mask),
4701            // A frame the kernel cannot read either kills the guest with
4702            // SIGSEGV, which delivers nothing pending.
4703            (None, _) => (0, 0),
4704            // A state that cannot be read could hide any delivery.
4705            (Some(_), Err(_)) => (REAL_TIME_SIGNALS, 0),
4706        };
4707        act_on_pending_real_time(guest, pending, observable, discarded).await;
4708    }
4709}
4710
4711/// What [`Detcore::rt_sigprocmask_inner`] knows of the set the kernel used.
4712enum ValidatedSet {
4713    /// The kernel read and accepted this set before Detcore injected the call.
4714    Validated(KernelSigset),
4715    /// Detcore injected nothing: its own read of the set failed first.
4716    NotRun,
4717    /// The call ran as the guest made it; only the kernel read its set.
4718    NotValidated,
4719}
4720
4721/// The offset from the stack pointer at `rt_sigreturn` of the `uc_sigmask` it
4722/// restores.
4723pub(super) const UC_SIGMASK_OFFSET: usize = crate::arch::RT_SIGRETURN_SIGMASK_OFFSET;
4724
4725fn should_notify_cross_task_signal(sender: DetTid, target: DetTid, raw_signal: i32) -> bool {
4726    raw_signal != 0 && target != sender
4727}
4728
4729/// Which check [`sleep_signal`] makes for an emulated `pause` or `nanosleep`.
4730#[derive(Clone, Debug, Eq, PartialEq)]
4731pub(crate) enum SleepCheck {
4732    /// Before the sleep parks: whether a signal already pending for the thread
4733    /// ends it at once.
4734    BeforePark,
4735    /// After the scheduler woke the sleep for these signals.
4736    AfterWake(Option<Vec<SigWrapper>>),
4737}
4738
4739/// What ends an emulated `pause` or `nanosleep`, per [`sleep_signal`].
4740#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4741pub(crate) enum SleepSignal {
4742    /// A signal pending for this thread, unblocked and caught or acted on by
4743    /// default: the kernel delivers it as the call returns. `pause` returns
4744    /// `ERESTARTNOHAND` for it and `nanosleep` `EINTR`.
4745    Pending,
4746    /// The scheduler woke the sleep for a held signal of its own that ends it
4747    /// (`held_wake_ends_sleep`), or this backend does not report the guest's
4748    /// signal state: the call returns `EINTR`, as every wake did before.
4749    Held,
4750    /// Nothing ends the sleep, and it waits (again) with its original deadline.
4751    None,
4752    /// The scheduler's `SIGCHLD`, in a process whose send window is unusable,
4753    /// that this thread claimed (`claim_pending_sigchld`): it is held for
4754    /// delivery to this thread as it resumes, so a signal is always pending
4755    /// then. Each call returns its restart code, as Linux's does: `EINTR` when
4756    /// a handler runs, whatever its `SA_RESTART`, and a restart when none does
4757    /// (the child-exit ledger drops a duplicate copy). `pause` and an absolute
4758    /// `clock_nanosleep` return `ERESTARTNOHAND`, and the kernel re-executes
4759    /// them. A relative `nanosleep` returns `ERESTART_RESTARTBLOCK` and keeps
4760    /// its deadline for the kernel's `restart_syscall`, which only a restart
4761    /// makes (`helpers::RestartCall::Nanosleep`).
4762    Claimed,
4763}
4764
4765/// Decides, in this thread's own turn, whether a signal ends an emulated
4766/// `pause` or `nanosleep` (<https://github.com/rrnewton/hermit/issues/3982>).
4767///
4768/// Only on a backend whose kernel reports the guest's signal state; on any
4769/// other, a sleep never checks before it parks, and a wake always ends it
4770/// (`Held`). A signal ends the sleep when it is pending for this thread,
4771/// unblocked, and caught or acted on by default
4772/// (`KernelSignalState::pending_interrupting`). Before the sleep parks, a stop
4773/// signal does so only when `stops_end_it`, which `nanosleep` leaves unset
4774/// because Detcore does not keep its deadline for the kernel's restart after a
4775/// stop, so the stop takes effect when the sleep ends, as before. After a wake,
4776/// which only the scheduler's own sends (a timer, a child-exit `SIGCHLD`) can
4777/// commit for a stop on a `nanosleep`, a pending stop ends it, as every wake
4778/// did before, so that the stop takes effect promptly.
4779///
4780/// `SIGCHLD` and the signals a host-timed source armed by a guest can post
4781/// (`host_timed_signals`) are held: the kernel can post them at a moment set by
4782/// host timing, and `/proc` cannot tell that copy from one a guest sent. They
4783/// never end a sleep before it parks, nor does the scheduler record a guest's
4784/// cross-task send of one for a sleep. A wake the scheduler committed for its
4785/// own child-exit `SIGCHLD` ends the sleep with `Held` only while the copy it
4786/// sent is still pending in the process's shared queue
4787/// (`ChildExitSigchldControl::PlacedPending`, which `/proc` cannot answer),
4788/// `/proc` shows it pending, and it is unblocked and caught or default-fatal
4789/// (`held_wake_ends_sleep`); a host-timed held signal, from the mask and
4790/// dispositions alone. Like the precise futex
4791/// wait's check, this reads the pending set before the sleep parks as well as
4792/// after a wake: a signal sent while the thread was stopped short of filing
4793/// its request, as when its timeslice ended in the syscall's prehook, is
4794/// pending and was never recorded for its sleep. After a wake whose
4795/// non-held signals are no longer pending, a `SIGCONT` having cancelled a stop
4796/// or `sigaction(SIG_IGN)` having flushed it, a Linux sleep would still be
4797/// asleep, so this returns `None`.
4798///
4799/// A state that cannot be read is handled as a wait's first read is
4800/// (`read_wait_signal_state`, here `read_thread_wait_signal_state`, since
4801/// Reverie can report the creator's pid for a process a raw `clone` made): a
4802/// thread that no longer exists ends the call
4803/// with `ERESTARTNOINTR`, which nothing observes, and otherwise the run ends
4804/// with a diagnostic.
4805pub(crate) async fn sleep_signal<G, T>(
4806    guest: &mut G,
4807    check: SleepCheck,
4808    stops_end_it: bool,
4809) -> Result<SleepSignal, Error>
4810where
4811    G: Guest<Detcore<T>>,
4812    T: RecordOrReplay,
4813{
4814    // D50 reader 8 under the in-guest delivery gate (A5 step 3c.1c, design
4815    // 16.11): before the park and after a wake alike, the sleep ends exactly
4816    // when the design's deliverable set under the thread's published mask is
4817    // not empty, read here in its own turn (`gated_wait_ends`, which records
4818    // the end for the restart checkpoint, D40). Nothing is held: on in-guest
4819    // a child's exit is published inside the every-turn hold, and host-timed
4820    // producers are refused unless ignored (step 3c.0b). A default stop is
4821    // never deliverable (stop sends are refused under the gate, D48), so
4822    // `stops_end_it` changes nothing here.
4823    //
4824    // Signal phase 1's SIGALRM is virtual: the scheduler's ledger wakes a
4825    // `pause` for it (`Scheduler::sigalrm_wake`) with no physical copy
4826    // pending, so such a wake counts as a caught SIGALRM pending on the
4827    // process's shared queue, where Linux's timer would queue it, and takes
4828    // its place in Linux's dequeue order among the deliverable signals
4829    // (`wait_signal_end::classify`): a deliverable default-fatal signal that
4830    // does not dump core still ends the process, and so does a core-dumping
4831    // one Linux would dequeue first; one behind the SIGALRM ends it unless
4832    // the guest's SIGALRM handler's mask holds it, which the restart
4833    // checkpoint reads (`WaitSignalEnd::BehindCaught`). IR-12: the `pause`
4834    // returns `ERESTARTNOHAND`, which the restart checkpoint turns into
4835    // `EINTR`. Phase 1 refuses every other sleep in a process that handles
4836    // SIGALRM (F8).
4837    if guest.config().in_guest_signal_delivery {
4838        let _ = stops_end_it;
4839        let phase1_wake = matches!(&check, SleepCheck::AfterWake(Some(woken))
4840            if woken.iter().any(|signal| signal.raw() == libc::SIGALRM))
4841            && guest.config().backend.virtualizes_guest_sigalrm
4842            && guest.thread_state().sigalrm_handled;
4843        let virtual_caught = if phase1_wake {
4844            kernel_sigset_bit(libc::SIGALRM)
4845        } else {
4846            0
4847        };
4848        let end =
4849            crate::syscalls::helpers::gated_wait_end_with_virtual(guest, None, virtual_caught)?;
4850        let Some(end) = end else {
4851            return Ok(SleepSignal::None);
4852        };
4853        guest.thread_state_mut().wait_signal_end = Some(end);
4854        return Ok(SleepSignal::Pending);
4855    }
4856    if !guest
4857        .config()
4858        .backend
4859        .supports_blocked_wait_signal_interruption
4860    {
4861        return Ok(match check {
4862            SleepCheck::BeforePark => SleepSignal::None,
4863            SleepCheck::AfterWake(_) => SleepSignal::Held,
4864        });
4865    }
4866    let host_timed = host_timed_signals(guest).await;
4867    let held = kernel_sigset_bit(libc::SIGCHLD) | host_timed;
4868    let before_park = check == SleepCheck::BeforePark;
4869    // `held_woken`: the held signals the scheduler woke this sleep for.
4870    // `unknown_held_wake`: a wake that names no signal.
4871    let (deciding, held_woken, unknown_held_wake) = match check {
4872        SleepCheck::BeforePark => (!held, 0, false),
4873        SleepCheck::AfterWake(None) => (0, 0, true),
4874        SleepCheck::AfterWake(Some(woken)) => {
4875            let woken = woken
4876                .iter()
4877                .fold(0, |set, signal| set | kernel_sigset_bit(signal.raw()));
4878            (woken & !held, woken & held, false)
4879        }
4880    };
4881    if deciding != 0 || held_woken != 0 {
4882        let state = read_thread_wait_signal_state(guest.pid(), guest.tid())?;
4883        let mut ending = state.pending_interrupting(state.blocked, false) & deciding;
4884        if !stops_end_it && before_park {
4885            ending &= !(state.default_job_control_stops() | kernel_sigset_bit(libc::SIGSTOP));
4886        }
4887        // A copy Linux discarded when it was sent ends nothing, except a
4888        // signal the scheduler committed at the wake, which is kept
4889        // (<https://github.com/rrnewton/hermit/issues/4293>).
4890        let committed = if before_park { 0 } else { deciding };
4891        let ending =
4892            crate::syscalls::threads::without_discarded(guest, &state, ending, committed).await;
4893        if ending != 0 {
4894            return Ok(SleepSignal::Pending);
4895        }
4896        // A committed `SIGCHLD` counts only while the schedule says this thread
4897        // will take it (`PlacedPending`): a guest's thread-directed copy in its
4898        // private queue, or the scheduler's or a guest's shared copy still in
4899        // the window. `/proc` cannot tell either from a kernel copy that host
4900        // timing posted, so its bits only prompt the question: a woken sleep
4901        // asks for the `SIGCHLD` it was woken for, and any sleep asks when its
4902        // private queue holds one, which a guest sent before it parked
4903        // (https://github.com/rrnewton/hermit/issues/4132).
4904        let sigchld = kernel_sigset_bit(libc::SIGCHLD) & !host_timed;
4905        let committed = if asks_committed_sigchld(held_woken, &state, sigchld)
4906            && matches!(
4907                crate::tool_global::child_exit_sigchld(
4908                    guest,
4909                    crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::PlacedPending,
4910                )
4911                .await,
4912                crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer::Placed(true)
4913            ) {
4914            // A committed copy shows in `/proc`: an invariant, not a second
4915            // condition.
4916            if state.pending & sigchld == 0 {
4917                tracing::error!(
4918                    "[dtid {}] a committed SIGCHLD is pending by the schedule but not in /proc",
4919                    guest.thread_state().dettid
4920                );
4921                debug_assert!(false, "a committed SIGCHLD is not pending in /proc");
4922            }
4923            sigchld
4924        } else {
4925            0
4926        };
4927        if let Some(verdict) = committed_sigchld_verdict(&state, held_woken, committed) {
4928            return Ok(verdict);
4929        }
4930        // Where the process does not use the send window, a thread outside the
4931        // schedule could take the scheduler's copy at a moment host timing
4932        // chooses, so the window records none, and `PlacedPending` says no
4933        // whether or not that thread took it. Parked again, the sleep ran to its
4934        // deadline with the copy pending and the handler ran as it returned, an
4935        // outcome Linux never gives (https://github.com/rrnewton/hermit/issues/4229).
4936        // So this thread claims the copy itself, in its own turn
4937        // (`claim_pending_sigchld`), while SIGCHLD is the signal its kernel
4938        // dequeues next: held for delivery to it, where no other thread can
4939        // take it, so the handler runs as the sleep returns (`Claimed`). If the
4940        // outside thread took it first, the claim finds none and the sleep
4941        // parks again, as on Linux, where the woken sleeper finds no signal. Reading the pending bit and then ending the sleep
4942        // left a gap in which the outside thread could take it, and then the
4943        // sleep returned EINTR with no handler
4944        // (https://github.com/rrnewton/hermit/issues/4341).
4945        //
4946        // In record and replay too, but only while no other thread of the
4947        // process is outside the schedule in a call that could take the copy
4948        // (`Scheduler::outside_call_may_take_sigchld`, through
4949        // `WindowUnusable`). Then nothing but this claim can take it, so its
4950        // pending bit, and the claim, are the same in a recording and its
4951        // replay (https://github.com/rrnewton/hermit/issues/4343). While such a
4952        // call is out, whether it took the copy is host-timed, and replay
4953        // serves a signalfd read from the log without taking anything, so the
4954        // two could decide differently; the sleep parks again, as before.
4955        if committed == 0
4956            && held_woken & sigchld != 0
4957            && woken_sigchld_pending(&state, sigchld)
4958            && matches!(
4959                crate::tool_global::child_exit_sigchld(
4960                    guest,
4961                    crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::WindowUnusable,
4962                )
4963                .await,
4964                crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer::Placed(true)
4965            )
4966            && state.next_dequeued(state.blocked).map(|(signal, _)| signal) == Some(libc::SIGCHLD)
4967            && {
4968                send_claim_fault_signal(guest, ClaimFault::Usr1BeforeClaim);
4969                let claimed = claim_pending_sigchld(guest).await?;
4970                if claimed {
4971                    send_claim_fault_signal(guest, ClaimFault::Usr1AfterClaim);
4972                }
4973                claimed
4974            }
4975        {
4976            // Its delivery stop places it, so it is not deferred behind a
4977            // runnable sibling (`ThreadState::claimed_sigchld`).
4978            guest.thread_state_mut().claimed_sigchld = true;
4979            return Ok(SleepSignal::Claimed);
4980        }
4981        let held_woken = held_woken & !sigchld;
4982        if held_wake_ends_sleep(&state, held_woken) {
4983            return Ok(SleepSignal::Held);
4984        }
4985        return Ok(SleepSignal::None);
4986    }
4987    Ok(if unknown_held_wake {
4988        SleepSignal::Held
4989    } else {
4990        SleepSignal::None
4991    })
4992}
4993
4994/// Whether this backend's `rt_sigsuspend` returns at once rather than sleeping
4995/// until a signal arrives (`Detcore::handle_rt_sigsuspend`). Reverie names no
4996/// capability for it, so this derives it from the one that implies it: a
4997/// backend that owns guest signal delivery (`provides_process_signal_control`,
4998/// KVM) implements the call itself, and its scheduler-modeled wake does not
4999/// exist, so it returns EINTR for a signal already pending under the temporary
5000/// mask and refuses a wait for one that is not (ENOSYS) rather than parking
5001/// the vCPU outside the schedule. Every other backend runs the kernel's call,
5002/// which sleeps. `rt_sigsuspend_never_sleeps_per_backend` pins the answer for
5003/// every backend Reverie defines; a backend that gains a sleeping
5004/// `rt_sigsuspend` must change it.
5005pub(crate) fn rt_sigsuspend_never_sleeps(backend: &reverie::BackendCapabilities) -> bool {
5006    backend.provides_process_signal_control
5007}
5008
5009/// Whether a `SIGCHLD` this thread claimed (`ThreadState::claimed_sigchld`,
5010/// `claimed`) is still claimed at a syscall entry, or after a `SIGCHLD` the
5011/// ledger dropped: SIGCHLD is still pending in its queue or its process's,
5012/// whatever its mask. The claim may have held another signal instead
5013/// (https://github.com/rrnewton/hermit/issues/4346), whose handler can block
5014/// SIGCHLD and make syscalls before its `rt_sigreturn` lets SIGCHLD be
5015/// delivered (the Codex review of the claimed-delivery fix), or a duplicate
5016/// the ledger drops while another copy waits (the Claude review).
5017/// Once SIGCHLD is not pending, its delivery never came to this thread. A
5018/// state that cannot be read (`read_state`, asked only for a claim) forgets
5019/// the claim, so the delivery is deferred as before the claim existed.
5020pub(crate) fn claim_still_pending<E>(
5021    claimed: bool,
5022    read_state: impl FnOnce() -> Result<crate::syscalls::threads::KernelSignalState, E>,
5023) -> bool {
5024    claimed && read_state().is_ok_and(|state| state.pending & kernel_sigset_bit(libc::SIGCHLD) != 0)
5025}
5026
5027/// Whether `sigchld`, the scheduler's copy a sleep was woken for, is still
5028/// pending for it and ends it: in the thread's or its process's queue, and
5029/// unblocked and caught or acted on by default (`sleep_signal`, for a process
5030/// whose send window is unusable).
5031fn woken_sigchld_pending(
5032    state: &crate::syscalls::threads::KernelSignalState,
5033    sigchld: KernelSigset,
5034) -> bool {
5035    state.pending & state.interrupting(state.blocked) & sigchld != 0
5036}
5037
5038/// Whether a sleep's own check (`sleep_signal`) asks the scheduler whether a
5039/// committed `SIGCHLD` is pending for it (`PlacedPending`): it was woken for
5040/// one (`held_woken`), or its private queue holds one. The latter is how a
5041/// guest's thread-directed copy sent before the sleeper filed its request is
5042/// not lost: the sender's turn found no sleep to wake, so only the sleeper's
5043/// check before it parks sees it (https://github.com/rrnewton/hermit/issues/4132).
5044fn asks_committed_sigchld(
5045    held_woken: KernelSigset,
5046    state: &crate::syscalls::threads::KernelSignalState,
5047    sigchld: KernelSigset,
5048) -> bool {
5049    (held_woken | state.thread_pending) & sigchld != 0
5050}
5051
5052/// What a committed `SIGCHLD` (`committed`, the scheduler's answer) makes of
5053/// the sleep: it ends it if unblocked and caught or acted on by default, as
5054/// the held signal the wake named (`Held`) or as one pending for it
5055/// (`Pending`); `None` leaves the decision to the rest of the check.
5056fn committed_sigchld_verdict(
5057    state: &crate::syscalls::threads::KernelSignalState,
5058    held_woken: KernelSigset,
5059    committed: KernelSigset,
5060) -> Option<SleepSignal> {
5061    let verdict = if held_woken & committed != 0 {
5062        SleepSignal::Held
5063    } else {
5064        SleepSignal::Pending
5065    };
5066    (state.interrupting(state.blocked) & committed != 0).then_some(verdict)
5067}
5068
5069/// The size of a `struct signalfd_siginfo`, which has ssi_signo at 0,
5070/// ssi_code at 8, ssi_pid at 12, ssi_uid at 16, ssi_status at 40, ssi_int at
5071/// 44, ssi_utime at 56 and ssi_stime at 64.
5072pub(crate) const SIGNALFD_RECORD: usize = 128;
5073
5074/// The signals signalfd `fd` of process `pid` reads, from its `sigmask:`
5075/// line in `/proc/<pid>/fdinfo/<fd>` (a kernel sigset in hex), or `None`
5076/// when it cannot be read.
5077pub(crate) fn signalfd_mask(pid: i32, fd: i32) -> Option<u64> {
5078    let info = std::fs::read_to_string(format!("/proc/{pid}/fdinfo/{fd}")).ok()?;
5079    info.lines()
5080        .find_map(|line| line.strip_prefix("sigmask:"))
5081        .and_then(|mask| u64::from_str_radix(mask.trim(), 16).ok())
5082}
5083
5084fn signalfd_field(record: &[u8], at: usize) -> u32 {
5085    u32::from_ne_bytes(record[at..at + 4].try_into().expect("four bytes"))
5086}
5087
5088/// For each `signalfd_siginfo` record in `bytes`, the child-exit ledger
5089/// question it raises (`Detcore::reconcile_signalfd_records`): a SIGCHLD with
5090/// a child-exit code is the kernel's copy for that child, a SIGCHLD the
5091/// scheduler sent (`is_own_sigchld_mark`) is its copy, and any other record
5092/// raises none.
5093fn signalfd_record_controls(
5094    bytes: &[u8],
5095    tracer: u32,
5096) -> Vec<Option<crate::scheduler::child_exit_sigchld::ChildExitSigchldControl>> {
5097    use crate::scheduler::child_exit_sigchld::ChildExitSigchldControl;
5098    use crate::scheduler::child_exit_sigchld::DequeuedCopy;
5099    bytes
5100        .chunks(SIGNALFD_RECORD)
5101        .map(|record| {
5102            if signalfd_field(record, 0) != libc::SIGCHLD as u32 {
5103                return None;
5104            }
5105            let code = signalfd_field(record, 8) as i32;
5106            let pid = signalfd_field(record, 12);
5107            let value = signalfd_field(record, 44) as i32;
5108            let copy = if crate::scheduler::child_exit_sigchld::is_child_exit_code(code) {
5109                DequeuedCopy::Kernel {
5110                    child: DetPid::from_raw(pid as i32),
5111                }
5112            } else if crate::scheduler::child_exit_sigchld::is_own_sigchld_mark(
5113                code, pid as i32, value, tracer,
5114            ) {
5115                DequeuedCopy::Scheduler
5116            } else if code == libc::SI_TKILL {
5117                DequeuedCopy::Thread
5118            } else {
5119                DequeuedCopy::Other
5120            };
5121            // Read, a kept record makes no `InboundSignal` request.
5122            Some(ChildExitSigchldControl::Dequeue {
5123                copy,
5124                admits: false,
5125            })
5126        })
5127        .collect()
5128}
5129
5130/// The pieces of the guest buffers `extents` (address, length) that hold the
5131/// first `len` bytes a vectored read scattered, in order; empty extents are
5132/// skipped.
5133pub(crate) fn signalfd_extent_pieces(
5134    extents: &[(usize, usize)],
5135    len: usize,
5136) -> Vec<(usize, usize)> {
5137    let mut left = len;
5138    let mut pieces = Vec::new();
5139    for (address, length) in extents {
5140        if left == 0 {
5141            break;
5142        }
5143        let take = (*length).min(left);
5144        if take > 0 {
5145            pieces.push((*address, take));
5146            left -= take;
5147        }
5148    }
5149    pieces
5150}
5151
5152/// Signalfd records rewritten by the child-exit ledger's answers.
5153#[derive(Debug, PartialEq, Eq)]
5154struct RewrittenSignalfdRecords {
5155    /// The buffer to write back: the kept records in order, then what the
5156    /// buffer held before the read over the bytes of dropped ones, so no
5157    /// dropped record stays readable.
5158    bytes: Vec<u8>,
5159    /// How many bytes the read returns.
5160    kept: usize,
5161}
5162
5163/// Apply the ledger's `answers`, one per record of `bytes`
5164/// (`signalfd_record_controls`): a claimed duplicate or a duplicate scheduler
5165/// copy is dropped, a copy that stands for an owned child is given that
5166/// child's siginfo with zero CPU times, and every other record is kept as read.
5167/// `Ok(None)` when no record changed. `Err(())` when a record is dropped but
5168/// `before`, the buffer's bytes captured before the read
5169/// (`capture_signalfd_buffer`), does not cover the bytes it leaves: they are
5170/// never invented (the Codex re-check of
5171/// https://github.com/rrnewton/hermit/pull/4351, P2-2).
5172fn apply_signalfd_answers(
5173    bytes: &[u8],
5174    answers: &[Option<crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer>],
5175    before: &[u8],
5176) -> Result<Option<RewrittenSignalfdRecords>, ()> {
5177    use crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer;
5178    use crate::scheduler::child_exit_sigchld::OwnCopy;
5179    let mut kept = Vec::with_capacity(bytes.len());
5180    let mut changed = false;
5181    for (record, answer) in bytes.chunks(SIGNALFD_RECORD).zip(answers) {
5182        let mut record = record.to_vec();
5183        match answer {
5184            Some(ChildExitSigchldAnswer::Claimed(true))
5185            | Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Duplicate)) => {
5186                changed = true;
5187                continue;
5188            }
5189            Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Deliver(own)))
5190            | Some(ChildExitSigchldAnswer::ClaimedFor(own)) => {
5191                // The record Linux writes for a child exit (SIL_CHLD) starts
5192                // zeroed and fills in only these: nothing of the copy it
5193                // replaces is kept, such as the own copy's SI_QUEUE mark in
5194                // ssi_int and ssi_ptr. CPU times are 0 (natively, host time).
5195                let signo = signalfd_field(&record, 0);
5196                record.fill(0);
5197                record[0..4].copy_from_slice(&signo.to_ne_bytes());
5198                record[8..12].copy_from_slice(&own.code.to_ne_bytes());
5199                record[12..16].copy_from_slice(&(own.pid as u32).to_ne_bytes());
5200                record[16..20].copy_from_slice(&own.uid.to_ne_bytes());
5201                record[40..44].copy_from_slice(&own.status.to_ne_bytes());
5202                changed = true;
5203            }
5204            _ => {}
5205        }
5206        kept.extend_from_slice(&record);
5207    }
5208    if !changed {
5209        return Ok(None);
5210    }
5211    let kept_len = kept.len();
5212    let len = bytes.len() - bytes.len() % SIGNALFD_RECORD;
5213    // The bytes past the kept records are those the caller's buffer held
5214    // before the read: a dropped record must leave the buffer as Linux, which
5215    // never queued it, leaves it (https://github.com/rrnewton/hermit/issues/4293).
5216    if before.len() < len {
5217        return Err(());
5218    }
5219    kept.extend_from_slice(&before[kept_len..len]);
5220    Ok(Some(RewrittenSignalfdRecords {
5221        bytes: kept,
5222        kept: kept_len,
5223    }))
5224}
5225
5226#[cfg(test)]
5227mod signalfd_record_tests {
5228    use super::*;
5229    use crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer;
5230    use crate::scheduler::child_exit_sigchld::ChildExitSigchldControl;
5231    use crate::scheduler::child_exit_sigchld::ChildExitSiginfo;
5232    use crate::scheduler::child_exit_sigchld::OwnCopy;
5233
5234    const TRACER: u32 = 4242;
5235
5236    /// `apply_signalfd_answers` with a captured buffer of zeros, so a dropped
5237    /// record's bytes become zeros.
5238    fn apply_without_capture(
5239        bytes: &[u8],
5240        answers: &[Option<ChildExitSigchldAnswer>],
5241    ) -> Option<RewrittenSignalfdRecords> {
5242        apply_signalfd_answers(bytes, answers, &vec![0; bytes.len()]).unwrap()
5243    }
5244
5245    /// A record dropped where the capture does not reach (a buffer past
5246    /// `SIGNALFD_CAPTURED`) is refused, never given invented bytes (the Codex
5247    /// re-check of https://github.com/rrnewton/hermit/pull/4351, P2-2).
5248    #[test]
5249    fn a_dropped_record_past_the_capture_is_refused() {
5250        let mut bytes = vec![0u8; 2 * SIGNALFD_RECORD];
5251        bytes[0..4].copy_from_slice(&10u32.to_ne_bytes());
5252        bytes[SIGNALFD_RECORD..SIGNALFD_RECORD + 4].copy_from_slice(&12u32.to_ne_bytes());
5253        let before = vec![0xa5u8; SIGNALFD_RECORD];
5254        assert_eq!(
5255            apply_signalfd_answers(
5256                &bytes,
5257                &[Some(ChildExitSigchldAnswer::Claimed(true)), None],
5258                &before,
5259            ),
5260            Err(())
5261        );
5262        // Nothing dropped needs no capture.
5263        assert_eq!(apply_signalfd_answers(&bytes, &[None, None], &[]), Ok(None));
5264    }
5265
5266    /// A dropped record's bytes get what the buffer held before the read.
5267    #[test]
5268    fn a_dropped_record_leaves_the_captured_bytes() {
5269        let record = |signo: u32| {
5270            let mut bytes = vec![0u8; SIGNALFD_RECORD];
5271            bytes[0..4].copy_from_slice(&signo.to_ne_bytes());
5272            bytes
5273        };
5274        let bytes = [record(10), record(12)].concat();
5275        let before = vec![0xa5u8; 2 * SIGNALFD_RECORD];
5276        let rewritten = apply_signalfd_answers(
5277            &bytes,
5278            &[Some(ChildExitSigchldAnswer::Claimed(true)), None],
5279            &before,
5280        )
5281        .unwrap()
5282        .unwrap();
5283        assert_eq!(rewritten.kept, SIGNALFD_RECORD);
5284        assert_eq!(&rewritten.bytes[..SIGNALFD_RECORD], &record(12)[..]);
5285        assert!(
5286            rewritten.bytes[SIGNALFD_RECORD..]
5287                .iter()
5288                .all(|byte| *byte == 0xa5)
5289        );
5290    }
5291
5292    fn record(signo: i32, code: i32, pid: u32, status: i32) -> Vec<u8> {
5293        let mut record = vec![0u8; SIGNALFD_RECORD];
5294        record[0..4].copy_from_slice(&(signo as u32).to_ne_bytes());
5295        record[8..12].copy_from_slice(&code.to_ne_bytes());
5296        record[12..16].copy_from_slice(&pid.to_ne_bytes());
5297        record[40..44].copy_from_slice(&status.to_ne_bytes());
5298        record[56..72].fill(7);
5299        record
5300    }
5301
5302    /// At a receiver in a nested PID namespace, a record of the scheduler's
5303    /// marked `SI_QUEUE` copy (pid 0, `ssi_int` the mark) is its own copy; an
5304    /// ancestor's `SI_USER` from pid 0 and an unmarked `SI_QUEUE` are another
5305    /// (<https://github.com/rrnewton/hermit/issues/4155>).
5306    #[test]
5307    fn a_nested_receivers_marked_record_asks_for_the_own_copy() {
5308        use crate::scheduler::child_exit_sigchld::DequeuedCopy;
5309        let marked = |value: i32| {
5310            let mut record = record(libc::SIGCHLD, libc::SI_QUEUE, 0, 0);
5311            record[44..48].copy_from_slice(&value.to_ne_bytes());
5312            record
5313        };
5314        let bytes = [
5315            marked(crate::scheduler::child_exit_sigchld::OWN_SIGCHLD_MARK),
5316            marked(7),
5317            record(libc::SIGCHLD, libc::SI_USER, 0, 0),
5318        ]
5319        .concat();
5320        let dequeue = |copy| {
5321            Some(ChildExitSigchldControl::Dequeue {
5322                copy,
5323                admits: false,
5324            })
5325        };
5326        assert_eq!(
5327            signalfd_record_controls(&bytes, TRACER),
5328            vec![
5329                dequeue(DequeuedCopy::Scheduler),
5330                dequeue(DequeuedCopy::Other),
5331                dequeue(DequeuedCopy::Other),
5332            ]
5333        );
5334    }
5335
5336    /// Each `SIGCHLD` record from the shared queue tells the ledger it was
5337    /// taken, as which copy: a kernel child-exit copy names its child, the
5338    /// scheduler's `SI_USER` copy is its own, a guest's `SI_USER` is another,
5339    /// and a `SI_TKILL` copy is thread-directed; any other signal asks nothing.
5340    /// A read makes no `InboundSignal` request (`admits` unset).
5341    #[test]
5342    fn each_record_asks_the_ledger_only_about_a_child_exit_copy() {
5343        use crate::scheduler::child_exit_sigchld::DequeuedCopy;
5344        let bytes = [
5345            record(libc::SIGCHLD, libc::CLD_KILLED, 31, libc::SIGKILL),
5346            record(libc::SIGCHLD, libc::SI_USER, TRACER, 0),
5347            record(libc::SIGCHLD, libc::SI_USER, 77, 0),
5348            record(libc::SIGCHLD, libc::SI_TKILL, 77, 0),
5349            record(libc::SIGUSR1, libc::SI_USER, TRACER, 0),
5350        ]
5351        .concat();
5352        let dequeue = |copy| {
5353            Some(ChildExitSigchldControl::Dequeue {
5354                copy,
5355                admits: false,
5356            })
5357        };
5358        assert_eq!(
5359            signalfd_record_controls(&bytes, TRACER),
5360            vec![
5361                dequeue(DequeuedCopy::Kernel {
5362                    child: DetPid::from_raw(31)
5363                }),
5364                dequeue(DequeuedCopy::Scheduler),
5365                dequeue(DequeuedCopy::Other),
5366                dequeue(DequeuedCopy::Thread),
5367                None,
5368            ]
5369        );
5370    }
5371
5372    /// A dropped record leaves the read, the records after it move up, and
5373    /// zeros cover its old bytes; a standing copy gets its owned child's
5374    /// siginfo, a cohort's lowest pid included (`ClaimedFor`), with zero CPU
5375    /// times.
5376    #[test]
5377    fn dropped_records_leave_and_standing_ones_take_the_ledgers_siginfo() {
5378        let own = |pid| ChildExitSiginfo {
5379            code: libc::CLD_KILLED,
5380            pid,
5381            uid: 0,
5382            status: libc::SIGKILL,
5383        };
5384        let first = record(libc::SIGCHLD, libc::CLD_KILLED, 31, libc::SIGKILL);
5385        let second = record(libc::SIGCHLD, libc::SI_USER, TRACER, 0);
5386        let third = record(libc::SIGCHLD, libc::CLD_KILLED, 33, libc::SIGKILL);
5387        let bytes = [first, second, third].concat();
5388        let rewritten = apply_without_capture(
5389            &bytes,
5390            &[
5391                Some(ChildExitSigchldAnswer::Claimed(true)),
5392                Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Deliver(own(32)))),
5393                Some(ChildExitSigchldAnswer::ClaimedFor(own(30))),
5394            ],
5395        )
5396        .expect("records changed");
5397        assert_eq!(rewritten.kept, 2 * SIGNALFD_RECORD);
5398        assert_eq!(rewritten.bytes.len(), 3 * SIGNALFD_RECORD);
5399        let expect = |pid: u32| {
5400            let mut wanted = record(libc::SIGCHLD, libc::CLD_KILLED, pid, libc::SIGKILL);
5401            wanted[56..72].fill(0);
5402            wanted
5403        };
5404        assert_eq!(&rewritten.bytes[..SIGNALFD_RECORD], &expect(32)[..]);
5405        assert_eq!(
5406            &rewritten.bytes[SIGNALFD_RECORD..2 * SIGNALFD_RECORD],
5407            &expect(30)[..]
5408        );
5409        assert!(
5410            rewritten.bytes[2 * SIGNALFD_RECORD..]
5411                .iter()
5412                .all(|byte| *byte == 0)
5413        );
5414    }
5415
5416    /// The marked own copy a nested receiver reads is rewritten to the
5417    /// record Linux writes for a child exit, which keeps nothing of the
5418    /// copy: `ssi_int` and `ssi_ptr` (bytes 44..56), where the mark was, are
5419    /// 0, like every field a child exit does not fill in (Codex re-check #2
5420    /// of https://github.com/rrnewton/hermit/pull/4172).
5421    #[test]
5422    fn a_rewritten_own_copy_keeps_none_of_its_marked_record() {
5423        let mut marked = record(libc::SIGCHLD, libc::SI_QUEUE, 0, 0);
5424        marked[44..48]
5425            .copy_from_slice(&crate::scheduler::child_exit_sigchld::OWN_SIGCHLD_MARK.to_ne_bytes());
5426        marked[48..56].fill(9);
5427        marked[20..40].fill(5);
5428        marked[72..].fill(6);
5429        let rewritten = apply_without_capture(
5430            &marked,
5431            &[Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Deliver(
5432                ChildExitSiginfo {
5433                    code: libc::CLD_KILLED,
5434                    pid: 3,
5435                    uid: 0,
5436                    status: libc::SIGKILL,
5437                },
5438            )))],
5439        )
5440        .expect("records changed");
5441        let mut wanted = vec![0u8; SIGNALFD_RECORD];
5442        wanted[0..4].copy_from_slice(&(libc::SIGCHLD as u32).to_ne_bytes());
5443        wanted[8..12].copy_from_slice(&libc::CLD_KILLED.to_ne_bytes());
5444        wanted[12..16].copy_from_slice(&3u32.to_ne_bytes());
5445        wanted[40..44].copy_from_slice(&libc::SIGKILL.to_ne_bytes());
5446        assert_eq!(rewritten.bytes, wanted);
5447        assert!(rewritten.bytes[44..56].iter().all(|byte| *byte == 0));
5448    }
5449
5450    /// A vectored read's returned bytes are found across its buffers in
5451    /// order, a record split between two buffers included, and only as far
5452    /// as the read returned.
5453    #[test]
5454    fn a_vectored_reads_records_are_found_across_its_buffers() {
5455        let extents = [(0x1000, 100), (0x2000, 0), (0x3000, 200), (0x4000, 128)];
5456        assert_eq!(
5457            signalfd_extent_pieces(&extents, 256),
5458            vec![(0x1000, 100), (0x3000, 156)]
5459        );
5460        assert_eq!(
5461            signalfd_extent_pieces(&extents, 428),
5462            vec![(0x1000, 100), (0x3000, 200), (0x4000, 128)]
5463        );
5464        assert_eq!(signalfd_extent_pieces(&extents, 0), vec![]);
5465    }
5466
5467    /// A read whose every record is dropped returns nothing and leaves only
5468    /// zeros; a read the ledger leaves alone is not rewritten.
5469    #[test]
5470    fn a_fully_dropped_read_leaves_no_record_and_an_untouched_read_is_not_rewritten() {
5471        let bytes = [
5472            record(libc::SIGCHLD, libc::CLD_KILLED, 31, libc::SIGKILL),
5473            record(libc::SIGCHLD, libc::SI_USER, TRACER, 0),
5474        ]
5475        .concat();
5476        let rewritten = apply_without_capture(
5477            &bytes,
5478            &[
5479                Some(ChildExitSigchldAnswer::Claimed(true)),
5480                Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Duplicate)),
5481            ],
5482        )
5483        .expect("records changed");
5484        assert_eq!(rewritten.kept, 0);
5485        assert!(rewritten.bytes.iter().all(|byte| *byte == 0));
5486        assert_eq!(
5487            apply_without_capture(
5488                &bytes,
5489                &[
5490                    Some(ChildExitSigchldAnswer::Claimed(false)),
5491                    Some(ChildExitSigchldAnswer::OwnCopy(OwnCopy::Unowned)),
5492                ],
5493            ),
5494            None
5495        );
5496    }
5497}
5498
5499/// The signals a thread may send to its own multi-threaded process with `kill`
5500/// or `rt_sigqueueinfo` (https://github.com/rrnewton/hermit/issues/4034): each
5501/// is numbered below SIGCHLD, so a child-exit SIGCHLD, whenever the kernel
5502/// posts it, is dequeued after it; none is in the kernel's synchronous set
5503/// (`SYNCHRONOUS_MASK`); and none is a signal Reverie gives a meaning
5504/// (SIGTRAP, its stop signal, and SIGSTKFLT, its timer marker).
5505const OWN_PROCESS_SIGNALS: [libc::c_int; 7] = [
5506    libc::SIGHUP,
5507    libc::SIGINT,
5508    libc::SIGQUIT,
5509    libc::SIGUSR1,
5510    libc::SIGUSR2,
5511    libc::SIGPIPE,
5512    libc::SIGTERM,
5513];
5514
5515/// Why a process-directed signal a thread sends to its own multi-threaded
5516/// process does not go to the kernel unchanged, each a named refusal of the
5517/// design for https://github.com/rrnewton/hermit/issues/4034: Detcore then
5518/// chooses the receiving thread itself (`Detcore::process_signal_target`).
5519#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5520pub(crate) enum OwnProcessSignalRefusal {
5521    /// The signal is not one of `OWN_PROCESS_SIGNALS`.
5522    SignalNotAdmitted,
5523    /// The process leaves the signal at its default, fatal, disposition.
5524    DefaultDisposition,
5525    /// The run records or replays.
5526    RecordReplay,
5527    /// The run replays a recorded schedule (`--replay-schedule-from`).
5528    ScheduleReplay,
5529    /// A guest has sent SIGSTKFLT, which Reverie uses as its timer marker.
5530    GuestMarkerSignal,
5531    /// `--stacktrace-signal` is configured.
5532    StacktraceSignal,
5533    /// The sender blocks the signal.
5534    SenderBlocks,
5535    /// A thread of the process runs or sleeps outside the schedule.
5536    OutOfSchedule(DetTid),
5537    /// A thread of the process has an unknown mask.
5538    UnknownMask(DetTid),
5539    /// The sender is not the thread the call names, and a live thread does
5540    /// not block the signal.
5541    NotSoleReceiver(DetTid),
5542    /// The sender's private queue holds these signals, which it does not block.
5543    PrivatePending(u64),
5544    /// These host-timed sources, which the sender does not block, are armed.
5545    HostTimedSource(u64),
5546    /// Unreaped children have these exit signals, which the sender does not block.
5547    ForeignExitSignal(u64),
5548    /// The scheduler sent these signals, at or below the one sent, and no
5549    /// thread has taken them yet.
5550    UndeliveredSchedulerSignal(u64),
5551    /// A happens-before anchor is set on this call's completion.
5552    PosthookAnchor,
5553    /// The process holds a signalfd.
5554    Signalfd,
5555    /// An exec is in flight in the process.
5556    ExecInFlight,
5557}
5558
5559impl std::fmt::Display for OwnProcessSignalRefusal {
5560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5561        match self {
5562            Self::SignalNotAdmitted => write!(
5563                f,
5564                "only SIGHUP, SIGINT, SIGQUIT, SIGUSR1, SIGUSR2, SIGPIPE and SIGTERM are delivered \
5565                 deterministically to the sender"
5566            ),
5567            Self::DefaultDisposition => write!(
5568                f,
5569                "the signal has its default disposition, which ends the process"
5570            ),
5571            Self::RecordReplay => write!(f, "the run records or replays"),
5572            Self::ScheduleReplay => write!(
5573                f,
5574                "the run replays a recorded schedule (--replay-schedule-from), whose trace turn \
5575                 after the send can hand the turn to another thread"
5576            ),
5577            Self::GuestMarkerSignal => write!(
5578                f,
5579                "a guest sent SIGSTKFLT, which Reverie also uses as its timer marker, so a pending \
5580                 copy cannot be told from Reverie's"
5581            ),
5582            Self::StacktraceSignal => write!(f, "--stacktrace-signal is configured"),
5583            Self::SenderBlocks => write!(f, "the sending thread blocks the signal"),
5584            Self::OutOfSchedule(tid) => write!(
5585                f,
5586                "thread {tid} runs or sleeps outside the schedule, at moments host timing sets"
5587            ),
5588            Self::UnknownMask(tid) => write!(f, "thread {tid}'s signal mask is unknown"),
5589            Self::NotSoleReceiver(tid) => write!(
5590                f,
5591                "thread {tid} does not block the signal, so Linux could give it to either thread"
5592            ),
5593            Self::PrivatePending(set) => write!(
5594                f,
5595                "the sending thread has signals pending ({set:#x}) that it would take first"
5596            ),
5597            Self::HostTimedSource(set) => write!(
5598                f,
5599                "a source that sends signals {set:#x} at moments host timing sets is armed"
5600            ),
5601            Self::ForeignExitSignal(set) => write!(
5602                f,
5603                "a child with exit signal {set:#x} is unreaped, and the kernel sends that signal at \
5604                 a moment host timing sets"
5605            ),
5606            Self::UndeliveredSchedulerSignal(set) => write!(
5607                f,
5608                "the scheduler sent signals {set:#x} that no thread has taken yet"
5609            ),
5610            Self::PosthookAnchor => {
5611                write!(f, "a happens-before anchor is set on the call's completion")
5612            }
5613            Self::Signalfd => write!(f, "the process holds a signalfd"),
5614            Self::ExecInFlight => write!(f, "an exec is in flight in the process"),
5615        }
5616    }
5617}
5618
5619/// Everything a process-directed signal `sig` that a thread sends to its own
5620/// multi-threaded process is decided from, all fixed by the schedule at the
5621/// sender's turn (`own_process_signal_refusal`).
5622#[derive(Clone, Debug, Default)]
5623pub(crate) struct OwnProcessSignal {
5624    pub(crate) sig: libc::c_int,
5625    /// Whether the call names the sender itself (S1 of the design).
5626    pub(crate) names_sender: bool,
5627    /// The sender's mask (`SigBlk`).
5628    pub(crate) blocked: u64,
5629    /// The sender's private queue (`SigPnd`).
5630    pub(crate) thread_pending: u64,
5631    /// The process's `SIG_IGN` dispositions (`SigIgn`).
5632    pub(crate) ignored: u64,
5633    /// The process's handlers (`SigCgt`).
5634    pub(crate) caught: u64,
5635    pub(crate) inputs: OwnProcessSignalInputs,
5636    pub(crate) record_replay: bool,
5637    pub(crate) schedule_replay: bool,
5638    pub(crate) stacktrace_signal: bool,
5639    pub(crate) posthook_anchor: bool,
5640    pub(crate) signalfd: bool,
5641}
5642
5643/// The refusal for `send`, or `None` when the kernel's choice of receiver and
5644/// the sender's dequeue are forced: the sender takes the signal on its way back
5645/// to user mode, before any other thread of the process can run
5646/// (https://github.com/rrnewton/hermit/issues/4034).
5647pub(crate) fn own_process_signal_refusal(
5648    send: &OwnProcessSignal,
5649) -> Option<OwnProcessSignalRefusal> {
5650    use OwnProcessSignalRefusal as R;
5651    let bit = kernel_sigset_bit(send.sig);
5652    if !OWN_PROCESS_SIGNALS.contains(&send.sig) {
5653        return Some(R::SignalNotAdmitted);
5654    }
5655    if (send.ignored | send.caught) & bit == 0 {
5656        return Some(R::DefaultDisposition);
5657    }
5658    if send.record_replay {
5659        return Some(R::RecordReplay);
5660    }
5661    if send.schedule_replay {
5662        return Some(R::ScheduleReplay);
5663    }
5664    if send.stacktrace_signal {
5665        return Some(R::StacktraceSignal);
5666    }
5667    if send.blocked & bit != 0 {
5668        return Some(R::SenderBlocks);
5669    }
5670    for (tid, state) in &send.inputs.siblings {
5671        if *state == SiblingSignalState::OutOfSchedule {
5672            return Some(R::OutOfSchedule(*tid));
5673        }
5674    }
5675    for (tid, state) in &send.inputs.siblings {
5676        if *state == SiblingSignalState::InSchedule(None) {
5677            return Some(R::UnknownMask(*tid));
5678        }
5679    }
5680    if !send.names_sender {
5681        for (tid, state) in &send.inputs.siblings {
5682            if matches!(state, SiblingSignalState::InSchedule(Some(mask)) if mask & bit == 0) {
5683                return Some(R::NotSoleReceiver(*tid));
5684            }
5685        }
5686    }
5687    let unblocked = !send.blocked;
5688    let private = without_perf_event_signal(send.thread_pending) & unblocked;
5689    if private != 0 {
5690        return Some(R::PrivatePending(private));
5691    }
5692    let host_timed = send.inputs.host_timed & unblocked;
5693    if host_timed != 0 {
5694        return Some(R::HostTimedSource(host_timed));
5695    }
5696    let foreign = send.inputs.foreign_exit_signals & unblocked;
5697    if foreign != 0 {
5698        return Some(R::ForeignExitSignal(foreign));
5699    }
5700    // A signal numbered at or below `sig` is dequeued first or coalesces.
5701    let at_or_below = bit | (bit - 1);
5702    let undelivered = send.inputs.undelivered_scheduler_signals & unblocked & at_or_below;
5703    if undelivered != 0 {
5704        return Some(R::UndeliveredSchedulerSignal(undelivered));
5705    }
5706    if send.posthook_anchor {
5707        return Some(R::PosthookAnchor);
5708    }
5709    if send.signalfd {
5710        return Some(R::Signalfd);
5711    }
5712    if send.inputs.exec_in_flight {
5713        return Some(R::ExecInFlight);
5714    }
5715    if send.inputs.guest_marker_signal {
5716        return Some(R::GuestMarkerSignal);
5717    }
5718    None
5719}
5720
5721/// What a send names, as `Detcore::refuse_reserved_signal_send` reads it.
5722pub(crate) enum ReservedSendTarget {
5723    /// The process a positive pid names (`kill`, `rt_sigqueueinfo`): any of
5724    /// its live threads' ids names it, as in Linux's `kill_pid_info`. Not a
5725    /// group: a non-positive pid takes the ordinary path.
5726    Process(libc::pid_t),
5727    /// A thread (`tkill`), and for `tgkill` and `rt_tgsigqueueinfo` the
5728    /// thread group the call says it is in.
5729    Thread {
5730        tgid: Option<libc::pid_t>,
5731        tid: libc::pid_t,
5732    },
5733    /// What a `pidfd_send_signal` descriptor names, already Detcore's id
5734    /// (`None` for a descriptor with no recorded target), and whether the
5735    /// call is thread-scoped (`PIDFD_SIGNAL_THREAD`).
5736    Pidfd { named: Option<DetPid>, thread: bool },
5737}
5738
5739/// The `siginfo` a send carries, as `Detcore::refuse_reserved_signal_send`
5740/// checks it before a refusal: none (`kill`, `tkill`, `tgkill`); the
5741/// address `rt_sigqueueinfo` and `rt_tgsigqueueinfo` copy (0 for a null
5742/// pointer, which Linux's copy refuses too); or `pidfd_send_signal`'s,
5743/// which may be absent.
5744#[derive(Clone, Copy)]
5745pub(crate) enum ReservedSendSiginfo {
5746    None,
5747    Queued(usize),
5748    Pidfd(Option<usize>),
5749}
5750
5751/// The copy of a refused send's `siginfo` (`Detcore::refused_send_kernel_errno`).
5752enum RefusedSendSiginfo {
5753    /// The call carries none.
5754    NoCopy,
5755    /// Linux's answer to the copy: EFAULT, E2BIG, or for `pidfd_send_signal`
5756    /// EINVAL when `si_signo` is not the signal.
5757    Errno(Errno),
5758    /// The copy succeeds; `code` is its `si_code`.
5759    Copied { code: i32 },
5760    /// The caller cannot read bytes Linux's copy could read, or the caller's
5761    /// mappings cannot be read to tell: Linux's answer is not known.
5762    Unverifiable,
5763}
5764
5765/// A5 step 3c.1c: the copy of a refused send's `siginfo` from the caller's
5766/// memory, in Linux's order (`siginfo_copy_errno`: the head, then the
5767/// expansion only for a layout Linux does not know, then `si_signo` for
5768/// `pidfd_send_signal`). The bytes are read through `Guest::memory()`, which
5769/// on the in-guest backends refuses some memory x86's `copy_from_user` reads
5770/// (a write-only mapping; `reserved_send_siginfo_errno`). So a read that
5771/// fails is EFAULT only where Linux's copy faults too, as the caller's own
5772/// `/proc/<tid>/smaps`, read in its turn, shows: a byte in no mapping (and
5773/// not below a grow-down one) or in a mapping without read, write or execute
5774/// permission (`first_user_unreadable`). Any other failure, or mappings that
5775/// cannot be read, is `Unverifiable`. 3c.0a's reserved-send path
5776/// (`reserved_send_siginfo_errno`) keeps answering such a read EFAULT, a
5777/// known residual.
5778fn refused_send_siginfo<T: RecordOrReplay, G: Guest<Detcore<T>>>(
5779    guest: &mut G,
5780    signal: i32,
5781    siginfo: ReservedSendSiginfo,
5782) -> RefusedSendSiginfo {
5783    let (address, pidfd) = match siginfo {
5784        ReservedSendSiginfo::None | ReservedSendSiginfo::Pidfd(None) => {
5785            return RefusedSendSiginfo::NoCopy;
5786        }
5787        ReservedSendSiginfo::Queued(address) => (address, false),
5788        ReservedSendSiginfo::Pidfd(Some(address)) => (address, true),
5789    };
5790    let tid = guest.tid().as_raw();
5791    // Why `[start, start + len)` could not be read: EFAULT only where Linux
5792    // would fault.
5793    let unreadable = |start: usize, len: usize| {
5794        if start.checked_add(len).is_none() {
5795            return RefusedSendSiginfo::Errno(Errno::EFAULT);
5796        }
5797        let Ok(smaps) = std::fs::read_to_string(format!("/proc/{tid}/smaps")) else {
5798            return RefusedSendSiginfo::Unverifiable;
5799        };
5800        match first_user_unreadable(&parse_smaps(&smaps), start, len) {
5801            Some(Unreadable::NoPermission)
5802            | Some(Unreadable::Hole {
5803                below_growsdown: false,
5804            }) => RefusedSendSiginfo::Errno(Errno::EFAULT),
5805            _ => RefusedSendSiginfo::Unverifiable,
5806        }
5807    };
5808    let head = address
5809        .checked_add(KERNEL_SIGINFO_SIZE)
5810        .and_then(|_| AddrMut::<[u8; KERNEL_SIGINFO_SIZE]>::from_raw(address))
5811        .and_then(|head| guest.memory().read_value(head).ok());
5812    let Some(head) = head else {
5813        return unreadable(address, KERNEL_SIGINFO_SIZE);
5814    };
5815    let field = |offset: usize| {
5816        i32::from_ne_bytes(head[offset..offset + 4].try_into().expect("four bytes"))
5817    };
5818    let (si_signo, code) = (field(0), field(8));
5819    if !known_siginfo_layout(if pidfd { si_signo } else { signal }, code) {
5820        let start = address + KERNEL_SIGINFO_SIZE;
5821        let expansion = start
5822            .checked_add(SI_EXPANSION_SIZE)
5823            .and_then(|_| AddrMut::<[u8; SI_EXPANSION_SIZE]>::from_raw(start))
5824            .and_then(|expansion| guest.memory().read_value(expansion).ok());
5825        match expansion {
5826            None => return unreadable(start, SI_EXPANSION_SIZE),
5827            Some(bytes) if bytes.iter().any(|byte| *byte != 0) => {
5828                return RefusedSendSiginfo::Errno(Errno::E2BIG);
5829            }
5830            Some(_) => {}
5831        }
5832    }
5833    if pidfd && si_signo != signal {
5834        return RefusedSendSiginfo::Errno(Errno::EINVAL);
5835    }
5836    RefusedSendSiginfo::Copied { code }
5837}
5838
5839/// What a send `syscall` carries that Linux checks before sending
5840/// (`Detcore::refused_send_kernel_errno`): its `siginfo` (`rt_sigqueueinfo`,
5841/// `rt_tgsigqueueinfo`, `pidfd_send_signal`'s third argument) and the tgid a
5842/// `tgkill` or `rt_tgsigqueueinfo` names.
5843fn refused_send_arguments(
5844    syscall: syscalls::Syscall,
5845) -> (ReservedSendSiginfo, Option<libc::pid_t>) {
5846    let address = |info: Option<AddrMut<'_, libc::siginfo_t>>| info.map_or(0, |info| info.as_raw());
5847    match syscall {
5848        syscalls::Syscall::RtSigqueueinfo(call) => {
5849            (ReservedSendSiginfo::Queued(address(call.siginfo())), None)
5850        }
5851        syscalls::Syscall::RtTgsigqueueinfo(call) => (
5852            ReservedSendSiginfo::Queued(address(call.siginfo())),
5853            Some(call.tgid()),
5854        ),
5855        syscalls::Syscall::Tgkill(call) => (ReservedSendSiginfo::None, Some(call.tgid())),
5856        other => {
5857            let (sysno, args) = reverie::syscalls::SyscallInfo::into_parts(other);
5858            if sysno == Sysno::pidfd_send_signal {
5859                (
5860                    ReservedSendSiginfo::Pidfd((args.arg2 != 0).then_some(args.arg2)),
5861                    None,
5862                )
5863            } else {
5864                (ReservedSendSiginfo::None, None)
5865            }
5866        }
5867    }
5868}
5869
5870/// The size of Linux's `struct kernel_siginfo` on x86-64, the part of a
5871/// `siginfo_t` every copy from user memory reads, and of the expansion after
5872/// it (`SI_EXPANSION_SIZE`: `SI_MAX_SIZE`, 128, less that).
5873const KERNEL_SIGINFO_SIZE: usize = 48;
5874const SI_EXPANSION_SIZE: usize = 128 - KERNEL_SIGINFO_SIZE;
5875
5876/// Whether Linux knows the layout of a siginfo with signal `signo` and code
5877/// `code`, so that it need not check the expansion bytes
5878/// (`known_siginfo_layout`, kernel/signal.c:3320-3337 at v6.12, with
5879/// `sig_sicodes` at :3304-3318 and the limits of
5880/// include/uapi/asm-generic/siginfo.h).
5881fn known_siginfo_layout(signo: i32, code: i32) -> bool {
5882    const SI_KERNEL: i32 = 0x80;
5883    const SI_DETHREAD: i32 = -7;
5884    const SI_ASYNCNL: i32 = -60;
5885    const NSIGPOLL: i32 = 6;
5886    // `sig_specific_sicodes`: a signal below SIGRTMIN with its own codes.
5887    let specific_limit = match signo {
5888        libc::SIGILL => Some(11),
5889        libc::SIGFPE => Some(15),
5890        libc::SIGSEGV => Some(10),
5891        libc::SIGBUS => Some(5),
5892        libc::SIGTRAP => Some(6),
5893        libc::SIGCHLD => Some(6),
5894        libc::SIGPOLL => Some(NSIGPOLL),
5895        libc::SIGSYS => Some(2),
5896        _ => None,
5897    };
5898    if code == SI_KERNEL {
5899        true
5900    } else if code > 0 {
5901        code <= specific_limit.unwrap_or(NSIGPOLL)
5902    } else {
5903        code >= SI_DETHREAD || code == SI_ASYNCNL
5904    }
5905}
5906
5907/// Linux's answer to the copy of a send's `siginfo` from user memory, which
5908/// comes before any permission check (v6.12 kernel/signal.c): `head`, the
5909/// first `KERNEL_SIGINFO_SIZE` bytes, `None` when the caller cannot read
5910/// them, and `read_expansion`, which reads the rest (`None` when it cannot)
5911/// and is called only when Linux reads it too. The head must be readable
5912/// (EFAULT, `copy_siginfo_from_user`); only for a layout Linux does not know
5913/// is the expansion read at all, and then it must be readable (EFAULT) and
5914/// every byte of it 0 (E2BIG, `post_copy_siginfo_from_user`, :3394-3415), so
5915/// a known layout whose expansion is unmapped copies. `rt_sigqueueinfo` and
5916/// `rt_tgsigqueueinfo` judge the layout by the call's `signal`
5917/// (`__copy_siginfo_from_user`, :3417-3424); `pidfd_send_signal` (`pidfd`)
5918/// by the siginfo's own `si_signo`, which must then equal `signal` (EINVAL,
5919/// :3959-3966).
5920fn siginfo_copy_errno(
5921    signal: i32,
5922    pidfd: bool,
5923    head: Option<[u8; KERNEL_SIGINFO_SIZE]>,
5924    read_expansion: impl FnOnce() -> Option<[u8; SI_EXPANSION_SIZE]>,
5925) -> Option<Errno> {
5926    let Some(head) = head else {
5927        return Some(Errno::EFAULT);
5928    };
5929    let field = |offset: usize| {
5930        i32::from_ne_bytes(head[offset..offset + 4].try_into().expect("four bytes"))
5931    };
5932    let (si_signo, si_code) = (field(0), field(8));
5933    let layout_signo = if pidfd { si_signo } else { signal };
5934    if !known_siginfo_layout(layout_signo, si_code) {
5935        match read_expansion() {
5936            None => return Some(Errno::EFAULT),
5937            Some(bytes) if bytes.iter().any(|byte| *byte != 0) => return Some(Errno::E2BIG),
5938            Some(_) => {}
5939        }
5940    }
5941    (pidfd && si_signo != signal).then_some(Errno::EINVAL)
5942}
5943
5944/// Whether the thread group a `tgkill` or `rt_tgsigqueueinfo` names
5945/// (`group`) is the named thread's own process (`leader`), as Linux requires
5946/// (`task_tgid_vnr(p) == tgid` in `do_send_specific`, kernel/signal.c:3994 at
5947/// v6.12); a tgid naming another thread of that process, or another process,
5948/// is ESRCH there. Always true for a call that names no group.
5949fn reserved_send_names_its_group(group: Option<DetPid>, leader: Option<DetPid>) -> bool {
5950    group.is_none_or(|group| leader == Some(group))
5951}
5952
5953/// Whether a reserved send (`Detcore::refuse_reserved_signal_send`) names the
5954/// caller's own process by its ids alone, before any lookup: `named` is the
5955/// caller's thread or its process's id, or `group`, the thread group a
5956/// `tgkill` or `rt_tgsigqueueinfo` names, is the caller's process. Such a send
5957/// keeps its ordinary path; a `group` that does not hold `named` is the
5958/// kernel's ESRCH there.
5959fn reserved_send_names_own(
5960    own: DetPid,
5961    caller: DetTid,
5962    named: DetTid,
5963    group: Option<DetPid>,
5964) -> bool {
5965    named == caller || named == own || group == Some(own)
5966}
5967
5968/// Whether a reserved send is refused, given `targets`, the live threads of
5969/// the process the send names in Detcore's model (`process_signal_targets`),
5970/// and `thread`, the thread a thread-directed send names: only a process
5971/// with a live thread, which is not the caller's own (the caller is a live
5972/// thread of its own process), and for a thread-directed send only when the
5973/// named thread is one of those live threads. Anything else keeps Linux's
5974/// answer on the ordinary path: a zombie or a reaped process has no live
5975/// thread (0 or ESRCH), and an exited thread, or one outside the group a
5976/// `tgkill` names, is ESRCH.
5977fn reserved_send_refuses(caller: DetTid, thread: Option<DetTid>, targets: &[DetTid]) -> bool {
5978    !targets.is_empty()
5979        && !targets.contains(&caller)
5980        && thread.is_none_or(|thread| targets.contains(&thread))
5981}
5982
5983/// How a process-directed signal the calling thread sends goes on
5984/// (`Detcore::own_process_signal`).
5985/// What a positive pid the calling thread names refers to
5986/// (`Detcore::name_guest_pid`).
5987enum NamedGuestPid {
5988    /// This task, by its DetTid. `own_init_caught` is set when it belongs to
5989    /// the caller's own PID namespace's init, to the signals that init catches.
5990    Task {
5991        task: DetTid,
5992        own_init_caught: Option<u64>,
5993    },
5994    /// The call's answer, decided here: ESRCH for a pid no task of the
5995    /// caller's namespace has, or a refusal by name.
5996    Answer(Result<i64, Error>),
5997}
5998
5999/// How a process-directed signal reaches its process
6000/// (`Detcore::process_signal_target`).
6001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6002pub(crate) enum ProcessSignalTarget {
6003    /// The process has no live thread: a zombie, which Linux still finds and
6004    /// signals successfully, discarding the signal.
6005    Zombie,
6006    /// The thread that takes the signal: the process's only live thread, or,
6007    /// among several, the receiver Detcore chose by Linux's rule
6008    /// (`Scheduler::process_signal_receiver`). The call is forwarded
6009    /// unchanged, so the copy is the kernel's own; where other threads could
6010    /// take it from the shared queue first, the scheduler resumes this one
6011    /// before them (`Scheduler::first_process_signal_taker`).
6012    Thread(DetTid),
6013    /// Like `Thread`, but chosen among several live threads by the scheduler
6014    /// (`Scheduler::process_signal_receiver`): once the forwarded call
6015    /// succeeds, the caller reports it (`tool_global::process_signal_sent`),
6016    /// so that the walk's choice becomes the process's `curr_target`, as
6017    /// Linux moves it only for a send that reaches `complete_signal`.
6018    Chosen(DetTid),
6019    /// Several live threads and none wants the signal: the call is forwarded
6020    /// unchanged too, and the signal waits in the process's shared queue for
6021    /// the first thread that can take it, waking none.
6022    SharedQueue,
6023}
6024
6025enum OwnProcessSend {
6026    /// The ordinary path decides: the call does not name the sender's own
6027    /// multi-threaded process, or the schedule does not force the kernel's
6028    /// choice of receiver.
6029    NotOwn,
6030    /// Admitted: forward the call unchanged, holding the sender's turn.
6031    Admitted,
6032    /// Refused by name, with the policy's answer: the replay of a recorded
6033    /// schedule of a send that would be admitted.
6034    Refused(Result<i64, Error>),
6035}
6036
6037/// What `Detcore::rt_sigqueueinfo_precheck` found.
6038enum RtSigqueueinfoPrecheck {
6039    /// The kernel fails the call without sending (EPERM, or E2BIG, after a
6040    /// complete copy): forward it unchanged.
6041    KernelFails,
6042    /// The kernel's copy certainly faults: Hermit answers EFAULT itself.
6043    Efault,
6044    /// The call goes on to admission.
6045    Proceeds,
6046    /// Hermit cannot read the whole `siginfo_t`, and cannot prove that the
6047    /// kernel's copy faults: `[vvar]`, which neither tracer read reaches; a
6048    /// tail running into a gap; a first 48 bytes in the gap below a grow-down
6049    /// mapping, which the kernel's copy may grow into; or an unreadable
6050    /// `/proc/<tid>/smaps`.
6051    Unreadable,
6052}
6053
6054/// One mapping of a `/proc/<pid>/smaps` listing.
6055#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6056struct Mapping {
6057    start: usize,
6058    end: usize,
6059    /// Whether user mode can read it: on x86 any of read, write or execute
6060    /// permission allows a read; none (`---`) faults.
6061    user_readable: bool,
6062    /// Whether user mode, and so the kernel's copy to user memory, can
6063    /// write it: write permission.
6064    user_writable: bool,
6065    /// `VmFlags` `gd`: a fault just below it can grow it downwards.
6066    growsdown: bool,
6067}
6068
6069/// The mappings of a `/proc/<pid>/smaps` listing, in address order.
6070fn parse_smaps(smaps: &str) -> Vec<Mapping> {
6071    let mut mappings: Vec<Mapping> = Vec::new();
6072    for line in smaps.lines() {
6073        if let Some(flags) = line.strip_prefix("VmFlags:") {
6074            if let Some(last) = mappings.last_mut() {
6075                last.growsdown = flags.split_whitespace().any(|flag| flag == "gd");
6076            }
6077            continue;
6078        }
6079        let mut fields = line.split_whitespace();
6080        let Some((start, end)) = fields.next().and_then(|range| range.split_once('-')) else {
6081            continue;
6082        };
6083        let (Ok(start), Ok(end), Some(perms)) = (
6084            usize::from_str_radix(start, 16),
6085            usize::from_str_radix(end, 16),
6086            fields.next(),
6087        ) else {
6088            continue;
6089        };
6090        if perms.len() < 4 {
6091            continue;
6092        }
6093        mappings.push(Mapping {
6094            start,
6095            end,
6096            user_readable: perms.chars().take(3).any(|perm| perm != '-'),
6097            user_writable: perms.chars().nth(1) == Some('w'),
6098            growsdown: false,
6099        });
6100    }
6101    mappings.sort_by_key(|mapping| mapping.start);
6102    mappings
6103}
6104
6105/// A5 step 3c.1d (round-1 ruling 4, 3c.1c's rule): whether Linux's copy
6106/// from user memory of `[start, start + len)` by thread `tid` faults, as the
6107/// thread's `/proc/<tid>/smaps` shows in its own turn: `Some(true)` for a byte
6108/// in no mapping and not below a grow-down one, or in a mapping with no read,
6109/// write or execute permission (x86 user mode reads any page with one of
6110/// them; `first_user_unreadable`), or a range that overflows; `Some(false)`
6111/// when every byte is readable by user mode or lies below a grow-down mapping
6112/// the copy may grow; `None` when the mappings cannot be read.
6113pub(crate) fn user_copy_faults(tid: i32, start: usize, len: usize) -> Option<bool> {
6114    if start.checked_add(len).is_none() {
6115        return Some(true);
6116    }
6117    let smaps = std::fs::read_to_string(format!("/proc/{tid}/smaps")).ok()?;
6118    Some(matches!(
6119        first_user_unreadable(&parse_smaps(&smaps), start, len),
6120        Some(Unreadable::NoPermission)
6121            | Some(Unreadable::Hole {
6122                below_growsdown: false
6123            })
6124    ))
6125}
6126
6127/// A5 step 3c.1d (round-3 ruling 3): whether Linux's copy to user memory of
6128/// `[start, start + len)` by thread `tid` faults, as the thread's
6129/// `/proc/<tid>/smaps` shows in its own turn: `Some(true)` for a byte in no
6130/// mapping and not below a grow-down one, or in a mapping without write
6131/// permission, or a range that overflows; `Some(false)` when every byte is
6132/// writable or lies below a grow-down mapping the copy may grow; `None` when
6133/// the mappings cannot be read.
6134///
6135/// smaps' write permission is not always the kernel's whole answer: a write
6136/// can still fault on a page that smaps shows writable, under a protection
6137/// key that denies writes, a userfaultfd write-protected range, or a shared
6138/// file mapping beyond the file's end. There `Some(false)` is wrong, and a
6139/// caller probing in place gets the copy-out's EFAULT where Linux, with a
6140/// signal pending at entry, would make no copy-out (a named residual of A5
6141/// step 3c.1d, review round 4).
6142pub(crate) fn user_copy_out_faults(tid: i32, start: usize, len: usize) -> Option<bool> {
6143    if start.checked_add(len).is_none() {
6144        return Some(true);
6145    }
6146    let smaps = std::fs::read_to_string(format!("/proc/{tid}/smaps")).ok()?;
6147    Some(user_unwritable(&parse_smaps(&smaps), start, len))
6148}
6149
6150/// Whether a byte of `[address, address + len)` is one a copy to user memory
6151/// faults on: in a mapping without write permission, or in a hole not below a
6152/// grow-down mapping (`user_copy_out_faults`).
6153fn user_unwritable(mappings: &[Mapping], address: usize, len: usize) -> bool {
6154    let Some(end) = address.checked_add(len) else {
6155        return true;
6156    };
6157    let mut next = address;
6158    while next < end {
6159        match mappings.iter().find(|mapping| mapping.end > next) {
6160            Some(mapping) if mapping.start <= next => {
6161                if !mapping.user_writable {
6162                    return true;
6163                }
6164                next = mapping.end;
6165            }
6166            Some(above) if above.growsdown => next = above.start,
6167            _ => return true,
6168        }
6169    }
6170    false
6171}
6172
6173/// Why a byte is not readable by user mode (`first_user_unreadable`).
6174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6175enum Unreadable {
6176    /// In a mapping without read, write or execute permission.
6177    NoPermission,
6178    /// In no mapping; `below_growsdown` when the next mapping above it can
6179    /// grow down into it.
6180    Hole { below_growsdown: bool },
6181}
6182
6183/// Where the first byte of `[address, address + len)` that user mode cannot
6184/// read lies, or `None` when user mode can read them all. A range that
6185/// overflows the address space counts as a hole.
6186fn first_user_unreadable(mappings: &[Mapping], address: usize, len: usize) -> Option<Unreadable> {
6187    let Some(end) = address.checked_add(len) else {
6188        return Some(Unreadable::Hole {
6189            below_growsdown: false,
6190        });
6191    };
6192    let mut next = address;
6193    while next < end {
6194        match mappings.iter().find(|mapping| mapping.end > next) {
6195            Some(mapping) if mapping.start <= next => {
6196                if !mapping.user_readable {
6197                    return Some(Unreadable::NoPermission);
6198                }
6199                next = mapping.end;
6200            }
6201            above => {
6202                return Some(Unreadable::Hole {
6203                    below_growsdown: above.is_some_and(|mapping| mapping.growsdown),
6204                });
6205            }
6206        }
6207    }
6208    None
6209}
6210
6211/// The signals a send of `sig` lets Linux send later at a moment set by host
6212/// timing, as a kernel sigset; 0 for every signal but the four that stop by
6213/// default. A stopped member makes its process group one that an exit can
6214/// orphan with a stopped job, and Linux then sends that whole group SIGHUP and
6215/// SIGCONT at the exiting process's physical exit (`kill_orphaned_pgrp` in
6216/// kernel/exit.c). Apart from a guest's send, only a terminal stops a guest
6217/// process, and a terminal's signals, these two among them, are recorded with
6218/// `terminal_signals`.
6219fn orphaned_group_signals(sig: libc::c_int) -> u64 {
6220    match sig {
6221        libc::SIGSTOP | libc::SIGTSTP | libc::SIGTTIN | libc::SIGTTOU => {
6222            kernel_sigset_bit(libc::SIGHUP) | kernel_sigset_bit(libc::SIGCONT)
6223        }
6224        _ => 0,
6225    }
6226}
6227
6228/// Whether a held signal the scheduler woke a sleep for (`held_woken`, a
6229/// child-exit `SIGCHLD`) ends it: as Linux delivery would, only when this
6230/// thread's mask leaves it unblocked and it is caught or acted on by default
6231/// (`KernelSignalState::interrupting`). A blocked one stays pending, and a
6232/// default-ignored one (`SIGCHLD` without a handler) is discarded, and either
6233/// way the sleep goes on (https://github.com/rrnewton/hermit/issues/4126).
6234///
6235/// The caller has already removed a committed `SIGCHLD` that no longer counts:
6236/// the scheduler sends it to the process, so the kernel gives it to whichever
6237/// thread resumes first without blocking it, and a schedule may run a sibling
6238/// first (`--chaos-target-races`). Taken by that sibling, it is gone from the
6239/// child-exit ledger's send window (`ChildExitSigchldControl::PlacedPending`),
6240/// and the sleep goes on to its original deadline, as a futex wait woken for a
6241/// signal waits again (`futex_wait_held_after_wake`). Answering EINTR for it
6242/// anyway was a result Linux cannot give: the call interrupted, the handler run
6243/// in another thread (the Claude re-check of
6244/// https://github.com/rrnewton/hermit/pull/4131). A held signal a host-timed
6245/// source can also post is decided from the mask and dispositions alone.
6246fn held_wake_ends_sleep(
6247    state: &crate::syscalls::threads::KernelSignalState,
6248    held_woken: KernelSigset,
6249) -> bool {
6250    state.interrupting(state.blocked) & held_woken != 0
6251}
6252
6253#[cfg(test)]
6254mod tests {
6255    /// A first probe's not-run report restarts the call; any other result,
6256    /// errno or value, passes through unchanged.
6257    #[test]
6258    fn a_first_probe_not_run_restarts_the_call() {
6259        let restart = |result: Result<u64, Error>| restart_if_first_probe_not_run(result);
6260        assert!(matches!(
6261            restart(Err(Errno::ERESTARTSYS.into())),
6262            Err(Error::Errno(Errno::ERESTARTNOINTR))
6263        ));
6264        for errno in [
6265            Errno::EFAULT,
6266            Errno::EINVAL,
6267            Errno::EINTR,
6268            Errno::ERESTARTNOHAND,
6269        ] {
6270            assert!(
6271                matches!(restart(Err(errno.into())), Err(Error::Errno(e)) if e == errno),
6272                "{errno} passes through"
6273            );
6274        }
6275        assert!(matches!(restart(Ok(7)), Ok(7)));
6276    }
6277
6278    /// A reserved send names the caller's own process by its ids when it names
6279    /// the calling thread, the process's id, or the process as the thread
6280    /// group; any other id needs a lookup (A5 step 3c slice 0a).
6281    #[test]
6282    fn a_reserved_send_to_the_callers_own_process_is_decided_by_its_ids() {
6283        let own = DetPid::from_raw(3);
6284        let caller = DetTid::from_raw(4);
6285        let other = DetPid::from_raw(5);
6286        assert!(reserved_send_names_own(own, caller, caller, None));
6287        assert!(reserved_send_names_own(own, caller, own, None));
6288        // tgkill(own, other): the group is the caller's; the kernel answers.
6289        assert!(reserved_send_names_own(own, caller, other, Some(own)));
6290        assert!(!reserved_send_names_own(own, caller, other, None));
6291        assert!(!reserved_send_names_own(own, caller, other, Some(other)));
6292    }
6293
6294    /// A `tgkill` or `rt_tgsigqueueinfo` is refused only when the tgid it
6295    /// names is the thread's own process: a tgid naming another thread of
6296    /// that process, or another process, is Linux's ESRCH
6297    /// (`do_send_specific`), and a call that names no group always matches.
6298    #[test]
6299    fn a_reserved_thread_send_must_name_the_threads_own_group() {
6300        let leader = DetPid::from_raw(5);
6301        let sibling = DetPid::from_raw(6);
6302        assert!(reserved_send_names_its_group(None, Some(leader)));
6303        assert!(reserved_send_names_its_group(Some(leader), Some(leader)));
6304        assert!(!reserved_send_names_its_group(Some(sibling), Some(leader)));
6305        assert!(!reserved_send_names_its_group(Some(leader), None));
6306    }
6307
6308    /// Linux's siginfo copy, which comes before any permission check: an
6309    /// unreadable head is EFAULT; an unknown layout needs a readable, all-zero
6310    /// expansion (EFAULT, E2BIG); `rt_*sigqueueinfo` judge the layout by the
6311    /// call's signal and `pidfd_send_signal` by the siginfo's own, which must
6312    /// equal the call's (EINVAL).
6313    #[test]
6314    fn a_reserved_send_checks_its_siginfo_as_linux_copies_it() {
6315        let head = |signo: i32, code: i32| {
6316            let mut bytes = [0u8; KERNEL_SIGINFO_SIZE];
6317            bytes[0..4].copy_from_slice(&signo.to_ne_bytes());
6318            bytes[8..12].copy_from_slice(&code.to_ne_bytes());
6319            Some(bytes)
6320        };
6321        let zero = || Some([0u8; SI_EXPANSION_SIZE]);
6322        let dirty = || {
6323            let mut bytes = [0u8; SI_EXPANSION_SIZE];
6324            bytes[SI_EXPANSION_SIZE - 1] = 1;
6325            Some(bytes)
6326        };
6327        let unreadable = || None;
6328        // A known layout never reads its expansion, as Linux does not.
6329        let never = || -> Option<[u8; SI_EXPANSION_SIZE]> {
6330            panic!("the expansion of a known layout must not be read")
6331        };
6332        let sys = libc::SIGSYS;
6333        for pidfd in [false, true] {
6334            assert_eq!(
6335                siginfo_copy_errno(sys, pidfd, None, never),
6336                Some(Errno::EFAULT)
6337            );
6338            // SI_QUEUE is a known layout: the expansion is not read, so an
6339            // unreadable one copies.
6340            assert_eq!(siginfo_copy_errno(sys, pidfd, head(sys, -1), never), None);
6341            // -8 is below SI_DETHREAD and not SI_ASYNCNL: unknown.
6342            assert_eq!(siginfo_copy_errno(sys, pidfd, head(sys, -8), zero), None);
6343            assert_eq!(
6344                siginfo_copy_errno(sys, pidfd, head(sys, -8), dirty),
6345                Some(Errno::E2BIG)
6346            );
6347            assert_eq!(
6348                siginfo_copy_errno(sys, pidfd, head(sys, -8), unreadable),
6349                Some(Errno::EFAULT)
6350            );
6351        }
6352        // A mismatched si_signo is EINVAL for pidfd_send_signal only.
6353        let usr1 = head(libc::SIGUSR1, -1);
6354        assert_eq!(
6355            siginfo_copy_errno(sys, true, usr1, never),
6356            Some(Errno::EINVAL)
6357        );
6358        assert_eq!(siginfo_copy_errno(sys, false, usr1, never), None);
6359        // rt_*sigqueueinfo judge the layout by the call's signal: SIGSYS's
6360        // code 2 (SYS_USER_DISPATCH) is known; code 3 is not.
6361        assert_eq!(siginfo_copy_errno(sys, false, head(0, 2), never), None);
6362        assert_eq!(
6363            siginfo_copy_errno(sys, false, head(0, 3), dirty),
6364            Some(Errno::E2BIG)
6365        );
6366    }
6367
6368    /// `known_siginfo_layout`'s table: SI_KERNEL, the codes down to
6369    /// SI_DETHREAD and SI_ASYNCNL are known for every signal; a positive code
6370    /// up to the signal's own limit, or NSIGPOLL for a signal without one.
6371    #[test]
6372    fn known_siginfo_layout_matches_linuxs_table() {
6373        for signo in [
6374            libc::SIGSYS,
6375            libc::SIGSEGV,
6376            libc::SIGTRAP,
6377            libc::SIGUSR1,
6378            40,
6379        ] {
6380            for code in [0x80, 0, -1, -6, -7, -60] {
6381                assert!(known_siginfo_layout(signo, code), "{signo} {code}");
6382            }
6383            for code in [-8, -59, -61, 0x81] {
6384                assert!(!known_siginfo_layout(signo, code), "{signo} {code}");
6385            }
6386        }
6387        for (signo, limit) in [
6388            (libc::SIGILL, 11),
6389            (libc::SIGFPE, 15),
6390            (libc::SIGSEGV, 10),
6391            (libc::SIGBUS, 5),
6392            (libc::SIGTRAP, 6),
6393            (libc::SIGCHLD, 6),
6394            (libc::SIGPOLL, 6),
6395            (libc::SIGSYS, 2),
6396            (libc::SIGUSR1, 6),
6397        ] {
6398            assert!(known_siginfo_layout(signo, limit), "{signo}");
6399            assert!(!known_siginfo_layout(signo, limit + 1), "{signo}");
6400        }
6401    }
6402
6403    /// Only a send to a process with a live thread, other than the caller's
6404    /// own, is refused, and a thread-directed one only when the named thread
6405    /// is live in it: a zombie or reaped process (no live thread), an exited
6406    /// thread and a thread outside the named group keep Linux's answer.
6407    #[test]
6408    fn a_reserved_send_is_refused_only_to_another_live_process() {
6409        let caller = DetTid::from_raw(3);
6410        let other = DetTid::from_raw(5);
6411        let other_sibling = DetTid::from_raw(6);
6412        let live_other = [other, other_sibling];
6413        // kill(other): a live process that is not the caller's.
6414        assert!(reserved_send_refuses(caller, None, &live_other));
6415        // tkill/tgkill of a live thread of it.
6416        assert!(reserved_send_refuses(
6417            caller,
6418            Some(other_sibling),
6419            &live_other
6420        ));
6421        // The caller's own process.
6422        assert!(!reserved_send_refuses(caller, None, &[caller, other]));
6423        // A zombie or a reaped process: no live thread.
6424        assert!(!reserved_send_refuses(caller, None, &[]));
6425        assert!(!reserved_send_refuses(caller, Some(other), &[]));
6426        // An exited thread, or one outside the group tgkill names.
6427        assert!(!reserved_send_refuses(
6428            caller,
6429            Some(DetTid::from_raw(7)),
6430            &live_other
6431        ));
6432    }
6433
6434    use super::*;
6435
6436    fn timeval(seconds: libc::time_t, micros: libc::suseconds_t) -> libc::timeval {
6437        libc::timeval {
6438            tv_sec: seconds,
6439            tv_usec: micros,
6440        }
6441    }
6442
6443    /// Only KVM's `rt_sigsuspend` returns at once, so only there does the call
6444    /// run in the caller's own turn (https://github.com/rrnewton/hermit/issues/4360).
6445    /// Every other backend runs the kernel's sleeping call outside the
6446    /// runnable set.
6447    #[test]
6448    fn rt_sigsuspend_never_sleeps_per_backend() {
6449        use reverie::BackendCapabilities;
6450        for (name, backend, never_sleeps) in [
6451            ("ptrace", BackendCapabilities::PTRACE, false),
6452            ("e9patch", BackendCapabilities::E9PATCH, false),
6453            ("kvm", BackendCapabilities::KVM, true),
6454            ("sabre", BackendCapabilities::SABRE, false),
6455            ("dbt", BackendCapabilities::DBT, false),
6456            (
6457                "liteinst in-guest",
6458                BackendCapabilities::LITEINST_IN_GUEST,
6459                false,
6460            ),
6461        ] {
6462            assert_eq!(rt_sigsuspend_never_sleeps(&backend), never_sleeps, "{name}");
6463        }
6464    }
6465
6466    /// A send of each of the four default-stop signals arms the orphaned
6467    /// process group's SIGHUP and SIGCONT; no other signal arms anything.
6468    #[test]
6469    fn only_a_default_stop_signal_arms_the_orphaned_group_signals() {
6470        let hup_cont = kernel_sigset_bit(libc::SIGHUP) | kernel_sigset_bit(libc::SIGCONT);
6471        for sig in 0..=65 {
6472            let stops = matches!(
6473                sig,
6474                libc::SIGSTOP | libc::SIGTSTP | libc::SIGTTIN | libc::SIGTTOU
6475            );
6476            assert_eq!(
6477                orphaned_group_signals(sig),
6478                if stops { hup_cont } else { 0 },
6479                "signal {sig}"
6480            );
6481        }
6482    }
6483
6484    /// A child-exit `SIGCHLD` the scheduler woke a sleep for ends it only when
6485    /// it is unblocked and caught: blocked, or with its default (ignore)
6486    /// action, the sleep goes on, as on Linux
6487    /// (https://github.com/rrnewton/hermit/issues/4126). Taken by a sibling
6488    /// first, it is no longer among the held signals the wake counts, and the
6489    /// sleep goes on too (the Claude re-check of
6490    /// https://github.com/rrnewton/hermit/pull/4131). A default-fatal held
6491    /// signal (a host-timed `SIGALRM` without a handler) ends it.
6492    #[test]
6493    fn a_held_wake_ends_a_sleep_only_when_unblocked_and_caught_or_fatal() {
6494        use crate::syscalls::threads::KernelSignalState;
6495        let chld = kernel_sigset_bit(libc::SIGCHLD);
6496        let alrm = kernel_sigset_bit(libc::SIGALRM);
6497        let state = |caught: KernelSigset, blocked: KernelSigset| KernelSignalState {
6498            caught,
6499            blocked,
6500            ..Default::default()
6501        };
6502        assert!(held_wake_ends_sleep(&state(chld, 0), chld));
6503        assert!(!held_wake_ends_sleep(&state(chld, chld), chld));
6504        assert!(!held_wake_ends_sleep(&state(0, 0), chld));
6505        assert!(held_wake_ends_sleep(&state(0, 0), alrm));
6506        assert!(!held_wake_ends_sleep(&state(0, alrm), alrm));
6507        assert!(
6508            !held_wake_ends_sleep(&state(chld, 0), 0),
6509            "taken by a sibling"
6510        );
6511    }
6512
6513    /// In a process whose send window is unusable, a sleep woken for the
6514    /// scheduler's `SIGCHLD` ends for it when the copy is still pending, in its
6515    /// process's queue or its own, and unblocked and caught. It parks again
6516    /// when another thread took the copy, its mask blocks it, or it is left at
6517    /// its default action, which ignores it
6518    /// (https://github.com/rrnewton/hermit/issues/4229).
6519    #[test]
6520    fn a_woken_sleep_counts_a_pending_sigchld_in_an_unusable_window() {
6521        use crate::syscalls::threads::KernelSignalState;
6522        let chld = kernel_sigset_bit(libc::SIGCHLD);
6523        let state =
6524            |caught: KernelSigset, blocked: KernelSigset, shared: KernelSigset| KernelSignalState {
6525                caught,
6526                blocked,
6527                pending: shared,
6528                shared_pending: shared,
6529                ..Default::default()
6530            };
6531        assert!(woken_sigchld_pending(&state(chld, 0, chld), chld));
6532        assert!(
6533            !woken_sigchld_pending(&state(chld, 0, 0), chld),
6534            "another thread took it"
6535        );
6536        assert!(
6537            !woken_sigchld_pending(&state(chld, chld, chld), chld),
6538            "blocked"
6539        );
6540        assert!(
6541            !woken_sigchld_pending(&state(0, 0, chld), chld),
6542            "default, ignored"
6543        );
6544        assert!(
6545            !woken_sigchld_pending(&state(chld, 0, chld), 0),
6546            "host-timed SIGCHLD keeps it held"
6547        );
6548        let private = KernelSignalState {
6549            caught: chld,
6550            pending: chld,
6551            thread_pending: chld,
6552            ..Default::default()
6553        };
6554        assert!(woken_sigchld_pending(&private, chld));
6555    }
6556
6557    /// A claimed `SIGCHLD` stays claimed at a syscall while it is pending for
6558    /// the thread, in either queue: among them the syscall a held SIGUSR1's
6559    /// handler makes while its mask blocks SIGCHLD. It is forgotten once
6560    /// SIGCHLD is not pending, or when the state cannot be read, and an
6561    /// unclaimed thread reads nothing (the Codex review of the
6562    /// claimed-delivery fix; https://github.com/rrnewton/hermit/issues/4346).
6563    #[test]
6564    fn a_claimed_sigchld_stays_claimed_while_pending() {
6565        use crate::syscalls::threads::KernelSignalState;
6566        let chld = kernel_sigset_bit(libc::SIGCHLD);
6567        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
6568        let state = |blocked: KernelSigset, thread_pending: KernelSigset, shared: KernelSigset| {
6569            Ok::<_, Errno>(KernelSignalState {
6570                caught: chld | usr1,
6571                blocked,
6572                pending: thread_pending | shared,
6573                thread_pending,
6574                shared_pending: shared,
6575                ..Default::default()
6576            })
6577        };
6578        assert!(claim_still_pending(true, || state(0, 0, chld)));
6579        assert!(claim_still_pending(true, || state(0, chld, 0)));
6580        assert!(
6581            claim_still_pending(true, || state(chld | usr1, 0, chld)),
6582            "inside the held SIGUSR1's handler, whose mask holds SIGCHLD"
6583        );
6584        assert!(
6585            !claim_still_pending(true, || state(0, 0, usr1)),
6586            "taken elsewhere"
6587        );
6588        assert!(
6589            !claim_still_pending(true, || Err::<KernelSignalState, _>(Errno::ESRCH)),
6590            "unreadable"
6591        );
6592        assert!(
6593            !claim_still_pending(false, || -> Result<KernelSignalState, Errno> {
6594                panic!("an unclaimed thread reads no state")
6595            }),
6596            "unclaimed"
6597        );
6598    }
6599
6600    /// Before it parks, a sleep whose private queue already holds a guest's
6601    /// thread-directed `SIGCHLD` asks whether the copy is committed, and a
6602    /// committed one that is unblocked and caught ends the sleep as pending
6603    /// for it: the copy was sent before the sleeper filed its request, so the
6604    /// sender's turn had no sleep to wake (the Codex review of
6605    /// https://github.com/rrnewton/hermit/pull/4177). Without one, or with no
6606    /// wake and nothing private, it asks nothing; blocked, it does not end the
6607    /// sleep.
6608    #[test]
6609    fn a_private_sigchld_sent_before_the_sleep_is_filed_ends_it_before_it_parks() {
6610        use crate::syscalls::threads::KernelSignalState;
6611        let chld = kernel_sigset_bit(libc::SIGCHLD);
6612        let state = |blocked: KernelSigset, thread_pending: KernelSigset| KernelSignalState {
6613            caught: chld,
6614            blocked,
6615            pending: thread_pending,
6616            thread_pending,
6617            ..Default::default()
6618        };
6619        // Before park: no wake named a signal (`held_woken` 0).
6620        assert!(asks_committed_sigchld(0, &state(0, chld), chld));
6621        assert!(!asks_committed_sigchld(0, &state(0, 0), chld));
6622        assert!(
6623            !asks_committed_sigchld(0, &state(0, chld), 0),
6624            "a host-timed SIGCHLD source keeps it held"
6625        );
6626        assert_eq!(
6627            committed_sigchld_verdict(&state(0, chld), 0, chld),
6628            Some(SleepSignal::Pending)
6629        );
6630        assert_eq!(committed_sigchld_verdict(&state(chld, chld), 0, chld), None);
6631        assert_eq!(committed_sigchld_verdict(&state(0, chld), 0, 0), None);
6632        // After a wake for it, the same copy is the held signal it named.
6633        assert!(asks_committed_sigchld(chld, &state(0, 0), chld));
6634        assert_eq!(
6635            committed_sigchld_verdict(&state(0, chld), chld, chld),
6636            Some(SleepSignal::Held)
6637        );
6638    }
6639
6640    #[test]
6641    fn raw_kernel_signal_masks_are_exactly_one_u64() {
6642        assert_eq!(KERNEL_SIGSET_SIZE, 8);
6643        assert_eq!(std::mem::size_of::<KernelSigset>(), 8);
6644        assert!(std::mem::size_of::<libc::sigset_t>() > KERNEL_SIGSET_SIZE);
6645    }
6646
6647    /// The mask `rt_sigsuspend` (and the scheduler's record of it) sleeps
6648    /// under can never block SIGKILL or SIGSTOP; every other bit is kept.
6649    #[test]
6650    fn the_installed_mask_cannot_block_kill_or_stop() {
6651        let kill = 1_u64 << (libc::SIGKILL - 1);
6652        let stop = 1_u64 << (libc::SIGSTOP - 1);
6653        assert_eq!(kernel_installed_signal_mask(u64::MAX), !(kill | stop));
6654        assert_eq!(kernel_installed_signal_mask(kill | stop), 0);
6655        let alrm_and_rt64 = (1_u64 << (libc::SIGALRM - 1)) | (1_u64 << 63);
6656        assert_eq!(kernel_installed_signal_mask(alrm_and_rt64), alrm_and_rt64);
6657    }
6658
6659    #[test]
6660    fn raw_signal_size_validation_rejects_before_pointer_processing() {
6661        assert_eq!(validate_kernel_sigset_size(7), Err(Errno::EINVAL));
6662        assert_eq!(validate_kernel_sigset_size(8), Ok(()));
6663        assert_eq!(validate_kernel_sigset_size(16), Err(Errno::EINVAL));
6664    }
6665
6666    #[test]
6667    fn reserved_signal_is_removed_from_the_kernel_sized_mask_only() {
6668        let reserved = 1_u64 << (reverie::PERF_EVENT_SIGNAL as u32 - 1);
6669        let usr1 = 1_u64 << (libc::SIGUSR1 as u32 - 1);
6670        assert_eq!(without_perf_event_signal(reserved | usr1), usr1);
6671        assert_eq!(without_perf_event_signal(usr1), usr1);
6672    }
6673
6674    #[test]
6675    fn timeval_conversion_preserves_subsecond_precision() {
6676        let logical_time =
6677            timeval_to_logical_time(timeval(2, 345_678)).expect("valid timeval should convert");
6678        assert_eq!(
6679            logical_time,
6680            LogicalTime::from_nanos(2_345_678_000),
6681            "timeval conversion should preserve microsecond precision"
6682        );
6683
6684        let round_trip = logical_time_to_timeval(logical_time);
6685        assert_eq!(round_trip.tv_sec, 2, "round trip should preserve seconds");
6686        assert_eq!(
6687            round_trip.tv_usec, 345_678,
6688            "round trip should preserve microseconds"
6689        );
6690    }
6691
6692    #[test]
6693    fn timeval_conversion_rejects_invalid_values() {
6694        for invalid in [
6695            timeval(-1, 0),
6696            timeval(0, -1),
6697            timeval(0, 1_000_000),
6698            timeval(libc::time_t::MAX, 0),
6699        ] {
6700            assert_eq!(
6701                timeval_to_logical_time(invalid),
6702                Err(Errno::EINVAL),
6703                "invalid timeval should return EINVAL"
6704            );
6705        }
6706    }
6707
6708    #[test]
6709    fn alarm_seconds_round_as_linux_does() {
6710        assert_eq!(linux_alarm_seconds(LogicalTime::ZERO), 0);
6711        assert_eq!(linux_alarm_seconds(LogicalTime::from_nanos(1)), 1);
6712        assert_eq!(linux_alarm_seconds(LogicalTime::from_nanos(1_000)), 1);
6713        assert_eq!(
6714            linux_alarm_seconds(LogicalTime::from_nanos(1_000_000_000)),
6715            1
6716        );
6717        assert_eq!(
6718            linux_alarm_seconds(LogicalTime::from_nanos(1_499_999_999)),
6719            1
6720        );
6721        assert_eq!(
6722            linux_alarm_seconds(LogicalTime::from_nanos(1_500_000_000)),
6723            2
6724        );
6725        assert_eq!(
6726            linux_alarm_seconds(LogicalTime::from_nanos(2_000_000_001)),
6727            2
6728        );
6729    }
6730
6731    #[test]
6732    fn alarm_remaining_seconds_round_up() {
6733        assert_eq!(logical_time_to_alarm_seconds(LogicalTime::ZERO), 0);
6734        assert_eq!(logical_time_to_alarm_seconds(LogicalTime::from_nanos(1)), 1);
6735        assert_eq!(
6736            logical_time_to_alarm_seconds(LogicalTime::from_nanos(999_999_999)),
6737            1
6738        );
6739        assert_eq!(
6740            logical_time_to_alarm_seconds(LogicalTime::from_nanos(1_000_000_000)),
6741            1
6742        );
6743        assert_eq!(
6744            logical_time_to_alarm_seconds(LogicalTime::from_nanos(1_000_000_001)),
6745            2
6746        );
6747    }
6748
6749    #[test]
6750    fn kill_targets_only_unambiguous_process_delivery() {
6751        let first = DetTid::from_raw(42);
6752        let second = DetTid::from_raw(43);
6753        assert_eq!(
6754            deterministic_kill_target(&[], None, libc::SIGUSR1),
6755            Err(Errno::ESRCH)
6756        );
6757        assert_eq!(
6758            deterministic_kill_target(&[], Some(true), libc::SIGUSR1),
6759            Ok(None)
6760        );
6761        assert_eq!(
6762            deterministic_kill_target(&[], Some(false), libc::SIGUSR1),
6763            Err(Errno::ESRCH)
6764        );
6765        assert_eq!(
6766            deterministic_kill_target(&[first], None, libc::SIGUSR1),
6767            Ok(Some(first))
6768        );
6769        assert_eq!(
6770            deterministic_kill_target(&[first, second], None, libc::SIGUSR1),
6771            Err(Errno::ENOSYS)
6772        );
6773        assert_eq!(
6774            deterministic_kill_target(&[first, second], None, 0),
6775            Ok(Some(first))
6776        );
6777
6778        let process = DetPid::from_raw(41);
6779        assert!(self_sigkill_targets_current_task(
6780            libc::SIGKILL,
6781            Some(process),
6782            None,
6783            process,
6784            first,
6785        ));
6786        assert!(self_sigkill_targets_current_task(
6787            libc::SIGKILL,
6788            None,
6789            Some(first),
6790            process,
6791            first,
6792        ));
6793        assert!(self_sigkill_targets_current_task(
6794            libc::SIGKILL,
6795            Some(process),
6796            Some(first),
6797            process,
6798            first,
6799        ));
6800        for (signal, target_process, target_thread) in [
6801            (libc::SIGTERM, Some(process), Some(first)),
6802            (libc::SIGKILL, Some(DetPid::from_raw(40)), Some(first)),
6803            (libc::SIGKILL, Some(process), Some(second)),
6804            (libc::SIGKILL, None, None),
6805        ] {
6806            assert!(!self_sigkill_targets_current_task(
6807                signal,
6808                target_process,
6809                target_thread,
6810                process,
6811                first,
6812            ));
6813        }
6814    }
6815
6816    #[test]
6817    fn process_group_forwarding_is_limited_to_unmaskable_sigkill() {
6818        assert!(can_forward_process_group_signal(-42, libc::SIGKILL, false));
6819        assert!(!can_forward_process_group_signal(-42, libc::SIGTERM, false));
6820        assert!(!can_forward_process_group_signal(0, libc::SIGKILL, false));
6821        assert!(!can_forward_process_group_signal(-1, libc::SIGKILL, false));
6822        assert!(!can_forward_process_group_signal(-42, libc::SIGKILL, true));
6823    }
6824
6825    #[test]
6826    fn signal_zero_never_notifies_a_target() {
6827        let sender = DetTid::from_raw(42);
6828        let target = DetTid::from_raw(43);
6829        assert!(!should_notify_cross_task_signal(sender, target, 0));
6830        assert!(should_notify_cross_task_signal(
6831            sender,
6832            target,
6833            libc::SIGUSR1
6834        ));
6835        assert!(!should_notify_cross_task_signal(
6836            sender,
6837            sender,
6838            libc::SIGUSR1
6839        ));
6840    }
6841}
6842
6843#[cfg(test)]
6844mod appropriated_signal_tests {
6845    use super::*;
6846
6847    /// The set is closed, and it is closed BY MEASUREMENT: every signal 1..64 was
6848    /// run under two delivery paths against a native control, and exactly these
6849    /// two differ. If a third is ever appropriated it must be added here, because
6850    /// the diagnostic is the only thing that makes the loss visible.
6851    #[test]
6852    fn the_appropriated_set_is_exactly_sigtrap_and_sigstkflt() {
6853        let signums: Vec<i32> = APPROPRIATED_SIGNALS.iter().map(|(s, _)| *s).collect();
6854        assert_eq!(signums, vec![libc::SIGTRAP, libc::SIGSTKFLT]);
6855        // SIGSTKFLT is appropriated because reverie uses it as the PMU timer, so
6856        // the two must not drift apart.
6857        assert_eq!(libc::SIGSTKFLT, reverie::PERF_EVENT_SIGNAL as i32);
6858    }
6859
6860    /// ⚠️ SIG_DFL and SIG_IGN LOSE NOTHING. A guest that is not asking to be
6861    /// called back has no expectation to disappoint, and warning there would make
6862    /// the marker noise instead of signal -- Go registers SIGSTKFLT routinely.
6863    #[test]
6864    fn only_a_real_handler_is_reported() {
6865        for signum in [libc::SIGTRAP, libc::SIGSTKFLT] {
6866            assert!(!reports(signum, 0), "SIG_DFL must not warn");
6867            assert!(!reports(signum, 1), "SIG_IGN must not warn");
6868            assert!(reports(signum, 0x4000_1234), "a real handler must warn");
6869        }
6870    }
6871
6872    /// An ordinary signal is delivered normally and must never be reported.
6873    #[test]
6874    fn an_unappropriated_signal_is_never_reported() {
6875        for signum in [libc::SIGUSR1, libc::SIGTERM, libc::SIGINT, 10, 30] {
6876            assert!(
6877                !reports(signum, 0x4000_1234),
6878                "signal {signum} is not appropriated"
6879            );
6880        }
6881    }
6882
6883    /// Calls the REAL predicate. Deliberately not a re-implementation: a
6884    /// mirrored copy would keep passing if `appropriated_reason` were gutted.
6885    fn reports(signum: i32, handler: u64) -> bool {
6886        appropriated_reason(signum, handler).is_some()
6887    }
6888}
6889
6890#[cfg(test)]
6891mod rt_sigsuspend_tests {
6892    use reverie::GlobalRPC;
6893    use reverie::GlobalTool;
6894    use reverie::Pid;
6895    use reverie::Tool;
6896    use reverie::syscalls::LocalMemory;
6897    use reverie::syscalls::Syscall;
6898    use reverie::syscalls::SyscallInfo;
6899
6900    use super::*;
6901    use crate::Config;
6902    use crate::GlobalState;
6903    use crate::ThreadState;
6904    use crate::syscalls::threads::kernel_sigset_bit;
6905
6906    /// The guest's stack, in this process's memory: `regs` reports a stack
6907    /// pointer `STACK_WORD` words in, so the cells below its red zone are words
6908    /// `COPY_WORD - 1` (the pending set) and `COPY_WORD` (the copy).
6909    const ARENA_WORDS: usize = 64;
6910    const STACK_WORD: usize = 48;
6911    const COPY_WORD: usize = STACK_WORD - STACK_RED_ZONE / 8 - 1;
6912
6913    /// rt_sigsuspend writes its cells directly; it takes no scratch stack.
6914    struct NoStack;
6915
6916    struct NoStackGuard;
6917
6918    impl Drop for NoStackGuard {
6919        fn drop(&mut self) {}
6920    }
6921
6922    impl reverie::Stack for NoStack {
6923        type StackGuard = NoStackGuard;
6924
6925        fn size(&self) -> usize {
6926            panic!("rt_sigsuspend takes no scratch stack")
6927        }
6928        fn capacity(&self) -> usize {
6929            panic!("rt_sigsuspend takes no scratch stack")
6930        }
6931        fn push<'stack, T>(&mut self, _: T) -> Addr<'stack, T> {
6932            panic!("rt_sigsuspend takes no scratch stack")
6933        }
6934        fn reserve<'stack, T>(&mut self) -> AddrMut<'stack, T> {
6935            panic!("rt_sigsuspend takes no scratch stack")
6936        }
6937        fn commit(self) -> Result<Self::StackGuard, Errno> {
6938            panic!("rt_sigsuspend takes no scratch stack")
6939        }
6940    }
6941
6942    /// A guest whose `rt_sigsuspend` caller's mask lives at `caller_mask`. The
6943    /// call's `rt_sigpending` probe, which runs after Detcore read that mask
6944    /// and before the real call, rewrites it to `rewritten` if one is given,
6945    /// as another process sharing the buffer could while the call waits for
6946    /// its turn. Every mask a real `rt_sigsuspend` is given is kept in
6947    /// `suspended_under`. `stack_pointer`, when given, replaces the arena's
6948    /// stack pointer in `regs`.
6949    struct SuspendGuest {
6950        config: Config,
6951        thread: ThreadState<()>,
6952        arena: Box<[u64; ARENA_WORDS]>,
6953        stack_pointer: Option<usize>,
6954        caller_mask: usize,
6955        rewritten: Option<KernelSigset>,
6956        suspended_under: Vec<KernelSigset>,
6957    }
6958
6959    #[reverie::tool]
6960    impl GlobalRPC<GlobalState> for SuspendGuest {
6961        async fn send_rpc(
6962            &self,
6963            message: <GlobalState as GlobalTool>::Request,
6964        ) -> <GlobalState as GlobalTool>::Response {
6965            panic!(
6966                "an unsequentialized rt_sigsuspend sends no RPC: {:?}",
6967                message.2
6968            )
6969        }
6970        fn config(&self) -> &Config {
6971            &self.config
6972        }
6973    }
6974
6975    #[reverie::tool]
6976    impl Guest<Detcore> for SuspendGuest {
6977        type Memory = LocalMemory;
6978        type Stack = NoStack;
6979
6980        fn tid(&self) -> Pid {
6981            Pid::from_raw(1)
6982        }
6983        fn pid(&self) -> Pid {
6984            Pid::from_raw(1)
6985        }
6986        fn ppid(&self) -> Option<Pid> {
6987            None
6988        }
6989        fn memory(&self) -> Self::Memory {
6990            LocalMemory::new()
6991        }
6992        fn thread_state_mut(&mut self) -> &mut ThreadState<()> {
6993            &mut self.thread
6994        }
6995        fn thread_state(&self) -> &ThreadState<()> {
6996            &self.thread
6997        }
6998        async fn regs(&mut self) -> libc::user_regs_struct {
6999            // SAFETY: user_regs_struct is plain integers; all zeroes is valid.
7000            let mut regs: libc::user_regs_struct = unsafe { std::mem::zeroed() };
7001            regs.rsp = self
7002                .stack_pointer
7003                .unwrap_or(self.arena.as_ptr() as usize + STACK_WORD * 8)
7004                as u64;
7005            regs
7006        }
7007        async fn stack(&mut self) -> Self::Stack {
7008            NoStack
7009        }
7010        async fn daemonize(&mut self) {
7011            panic!("rt_sigsuspend must not daemonize")
7012        }
7013        async fn inject<S: SyscallInfo>(&mut self, syscall: S) -> Result<i64, Errno> {
7014            let (number, args) = syscall.into_parts();
7015            match Syscall::from_raw(number, args) {
7016                // read_kernel_sigset's validation probe.
7017                Syscall::RtSigprocmask(call) if call.how() == -1 => Err(Errno::EINVAL),
7018                Syscall::RtSigpending(call) => {
7019                    let set = call.set().expect("rt_sigpending without a set");
7020                    LocalMemory::new().write_value(set.cast::<KernelSigset>(), &0)?;
7021                    if let Some(rewritten) = self.rewritten {
7022                        // SAFETY: `caller_mask` is the test's live, aligned buffer.
7023                        unsafe {
7024                            std::ptr::write(self.caller_mask as *mut KernelSigset, rewritten)
7025                        };
7026                    }
7027                    Ok(0)
7028                }
7029                Syscall::RtSigsuspend(call) => {
7030                    let mask = call.mask().expect("rt_sigsuspend without a mask");
7031                    self.suspended_under
7032                        .push(LocalMemory::new().read_value(mask.cast::<KernelSigset>())?);
7033                    Err(Errno::EINTR)
7034                }
7035                other => panic!("rt_sigsuspend injected {other:?}"),
7036            }
7037        }
7038        async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
7039            panic!("rt_sigsuspend must not retire the guest")
7040        }
7041        fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7042            panic!("rt_sigsuspend must not set a timer")
7043        }
7044        fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7045            panic!("rt_sigsuspend must not set a timer")
7046        }
7047        fn read_clock(&mut self) -> Result<u64, Error> {
7048            panic!("rt_sigsuspend must not read a clock")
7049        }
7050    }
7051
7052    /// The real `rt_sigsuspend` sleeps under the mask Detcore read when it
7053    /// handled the call, the one the scheduler is given, even if the guest's
7054    /// buffer changes before the call runs. Linux copies the mask when the call
7055    /// starts, and from the guest's view it has started. Before, the call ran
7056    /// with the guest's pointer: another process sharing the buffer could make
7057    /// it block a signal the scheduler had counted on to end it, and the
7058    /// scheduler would hold every other thread waiting for that wake.
7059    #[tokio::test(flavor = "current_thread")]
7060    async fn rt_sigsuspend_sleeps_under_the_mask_detcore_read() {
7061        let config = Config::default();
7062        assert!(!config.sequentialize_threads);
7063        let tool = <Detcore as Tool>::new(Pid::from_raw(1), &config);
7064        let unblocks_sigchld: KernelSigset = kernel_sigset_bit(libc::SIGUSR2);
7065        let blocks_sigchld = unblocks_sigchld | kernel_sigset_bit(libc::SIGCHLD);
7066        let mut caller_mask: Box<KernelSigset> = Box::new(unblocks_sigchld);
7067        let caller_address = &mut *caller_mask as *mut KernelSigset as usize;
7068        let mut guest = SuspendGuest {
7069            thread: ThreadState::new(DetPid::from_raw(1), &config, ()),
7070            config: config.clone(),
7071            arena: Box::new([u64::MAX; ARENA_WORDS]),
7072            stack_pointer: None,
7073            caller_mask: caller_address,
7074            rewritten: Some(blocks_sigchld),
7075            suspended_under: Vec::new(),
7076        };
7077        let call = syscalls::RtSigsuspend::new()
7078            .with_mask(Addr::from_raw(caller_address))
7079            .with_sigsetsize(KERNEL_SIGSET_SIZE);
7080
7081        let result = tool.handle_rt_sigsuspend(&mut guest, call).await;
7082        assert!(
7083            matches!(result, Err(Error::Errno(Errno::EINTR))),
7084            "{result:?}"
7085        );
7086        assert_eq!(*caller_mask, blocks_sigchld, "the buffer was rewritten");
7087        assert_eq!(guest.suspended_under, [unblocks_sigchld]);
7088    }
7089
7090    /// A caller's mask that lies on the cells Detcore places below the red
7091    /// zone, as a raw syscall may pass one there in a shared stack: the copy
7092    /// must not be the caller's buffer, or a rewrite of the shared buffer
7093    /// reaches the real call again, and the rt_sigpending cell must not
7094    /// overwrite it. The cells move below the buffer, which keeps its own
7095    /// contents. Before, the copy slot was the caller's buffer, and the
7096    /// pending slot's buffer was overwritten with the pending set.
7097    #[tokio::test(flavor = "current_thread")]
7098    async fn rt_sigsuspend_scratch_never_lands_on_the_callers_mask() {
7099        let config = Config::default();
7100        let tool = <Detcore as Tool>::new(Pid::from_raw(1), &config);
7101        let original: KernelSigset = kernel_sigset_bit(libc::SIGUSR2);
7102        let rewritten = original | kernel_sigset_bit(libc::SIGCHLD);
7103        // The copy's slot, rewritten by a peer; then the pending cell's slot,
7104        // which nobody rewrites.
7105        for (slot, peer_rewrite) in [(COPY_WORD, Some(rewritten)), (COPY_WORD - 1, None)] {
7106            let mut arena = Box::new([u64::MAX; ARENA_WORDS]);
7107            arena[slot] = original;
7108            let caller_address = &mut arena[slot] as *mut u64 as usize;
7109            let mut guest = SuspendGuest {
7110                thread: ThreadState::new(DetPid::from_raw(1), &config, ()),
7111                config: config.clone(),
7112                arena,
7113                stack_pointer: None,
7114                caller_mask: caller_address,
7115                rewritten: peer_rewrite,
7116                suspended_under: Vec::new(),
7117            };
7118            let call = syscalls::RtSigsuspend::new()
7119                .with_mask(Addr::from_raw(caller_address))
7120                .with_sigsetsize(KERNEL_SIGSET_SIZE);
7121
7122            let result = tool.handle_rt_sigsuspend(&mut guest, call).await;
7123            assert!(
7124                matches!(result, Err(Error::Errno(Errno::EINTR))),
7125                "slot {slot}: {result:?}"
7126            );
7127            assert_eq!(guest.suspended_under, [original], "slot {slot}");
7128            assert_eq!(
7129                guest.arena[slot],
7130                peer_rewrite.unwrap_or(original),
7131                "slot {slot}: the caller's buffer holds only what the guest wrote"
7132            );
7133        }
7134    }
7135
7136    /// A stack pointer that leaves no room for the red zone and the two cells
7137    /// below it fails the call with EFAULT, as a cell at address zero or on
7138    /// unwritable memory does, and the real call never runs. Rows: a stack
7139    /// pointer of 0 and one inside the red zone's size (no room for the red
7140    /// zone on x86_64; aarch64 has none), the red zone's bottom 0, 8 and 15
7141    /// bytes above address zero (no room for the cells), and 16 bytes above it
7142    /// (the cells would start at address zero, which is refused as null).
7143    /// Mutation "an unchecked subtraction for the cells": the rows 0, 8 and 15
7144    /// bytes above the red zone's bottom overflow, which panics in a debug
7145    /// build.
7146    #[tokio::test(flavor = "current_thread")]
7147    async fn rt_sigsuspend_with_no_room_below_the_red_zone_fails_with_efault() {
7148        let config = Config::default();
7149        let tool = <Detcore as Tool>::new(Pid::from_raw(1), &config);
7150        let mut caller_mask: Box<KernelSigset> = Box::new(kernel_sigset_bit(libc::SIGUSR2));
7151        let caller_address = &mut *caller_mask as *mut KernelSigset as usize;
7152        let rows = [
7153            0,
7154            STACK_RED_ZONE / 2,
7155            STACK_RED_ZONE,
7156            STACK_RED_ZONE + 8,
7157            STACK_RED_ZONE + 15,
7158            STACK_RED_ZONE + 16,
7159        ];
7160        for stack_pointer in rows {
7161            let mut guest = SuspendGuest {
7162                thread: ThreadState::new(DetPid::from_raw(1), &config, ()),
7163                config: config.clone(),
7164                arena: Box::new([u64::MAX; ARENA_WORDS]),
7165                stack_pointer: Some(stack_pointer),
7166                caller_mask: caller_address,
7167                rewritten: None,
7168                suspended_under: Vec::new(),
7169            };
7170            let call = syscalls::RtSigsuspend::new()
7171                .with_mask(Addr::from_raw(caller_address))
7172                .with_sigsetsize(KERNEL_SIGSET_SIZE);
7173
7174            let result = tool.handle_rt_sigsuspend(&mut guest, call).await;
7175            assert!(
7176                matches!(result, Err(Error::Errno(Errno::EFAULT))),
7177                "stack pointer {stack_pointer}: {result:?}"
7178            );
7179            assert!(
7180                guest.suspended_under.is_empty(),
7181                "stack pointer {stack_pointer}"
7182            );
7183        }
7184    }
7185}
7186
7187#[cfg(test)]
7188mod before_park_tests {
7189    use reverie::GlobalRPC;
7190    use reverie::GlobalTool;
7191    use reverie::Pid;
7192    use reverie::syscalls::SyscallInfo;
7193
7194    use super::*;
7195    use crate::Config;
7196    use crate::GlobalState;
7197    use crate::ThreadState;
7198    use crate::scheduler::child_exit_sigchld::ChildExitSigchldControl;
7199
7200    /// The before-park check takes no scratch stack.
7201    struct NoStack;
7202
7203    struct NoStackGuard;
7204
7205    impl Drop for NoStackGuard {
7206        fn drop(&mut self) {}
7207    }
7208
7209    impl reverie::Stack for NoStack {
7210        type StackGuard = NoStackGuard;
7211
7212        fn size(&self) -> usize {
7213            panic!("the before-park check takes no stack")
7214        }
7215        fn capacity(&self) -> usize {
7216            panic!("the before-park check takes no stack")
7217        }
7218        fn push<'stack, T>(&mut self, _: T) -> Addr<'stack, T> {
7219            panic!("the before-park check takes no stack")
7220        }
7221        fn reserve<'stack, T>(&mut self) -> AddrMut<'stack, T> {
7222            panic!("the before-park check takes no stack")
7223        }
7224        fn commit(self) -> Result<Self::StackGuard, Errno> {
7225            panic!("the before-park check takes no stack")
7226        }
7227    }
7228
7229    /// A guest whose thread is a real stopped process, so `sleep_signal`
7230    /// reads the kernel's signal state for it from `/proc`, and whose RPCs go
7231    /// to a real `GlobalState`, so its questions reach the scheduler's ledger.
7232    struct SleeperGuest<'a> {
7233        state: &'a GlobalState,
7234        config: Config,
7235        thread: ThreadState<()>,
7236        pid: Pid,
7237    }
7238
7239    #[reverie::tool]
7240    impl GlobalRPC<GlobalState> for SleeperGuest<'_> {
7241        async fn send_rpc(
7242            &self,
7243            request: <GlobalState as GlobalTool>::Request,
7244        ) -> <GlobalState as GlobalTool>::Response {
7245            self.state.receive_rpc(self.pid, request).await
7246        }
7247        fn config(&self) -> &Config {
7248            &self.config
7249        }
7250    }
7251
7252    #[reverie::tool]
7253    impl Guest<Detcore> for SleeperGuest<'_> {
7254        type Memory = reverie::syscalls::LocalMemory;
7255        type Stack = NoStack;
7256
7257        fn tid(&self) -> Pid {
7258            self.pid
7259        }
7260        fn pid(&self) -> Pid {
7261            self.pid
7262        }
7263        fn ppid(&self) -> Option<Pid> {
7264            None
7265        }
7266        fn memory(&self) -> Self::Memory {
7267            panic!("the before-park check reads no guest memory")
7268        }
7269        fn thread_state_mut(&mut self) -> &mut ThreadState<()> {
7270            &mut self.thread
7271        }
7272        fn thread_state(&self) -> &ThreadState<()> {
7273            &self.thread
7274        }
7275        async fn regs(&mut self) -> libc::user_regs_struct {
7276            panic!("the before-park check reads no registers")
7277        }
7278        async fn stack(&mut self) -> Self::Stack {
7279            panic!("the before-park check takes no stack")
7280        }
7281        async fn daemonize(&mut self) {
7282            panic!("the before-park check must not daemonize")
7283        }
7284        async fn inject<S: SyscallInfo>(&mut self, _: S) -> Result<i64, Errno> {
7285            panic!("the before-park check injects no syscall")
7286        }
7287        async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
7288            panic!("the before-park check must not retire the guest")
7289        }
7290        fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7291            panic!("the before-park check sets no timer")
7292        }
7293        fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7294            panic!("the before-park check sets no timer")
7295        }
7296        fn read_clock(&mut self) -> Result<u64, Error> {
7297            panic!("the before-park check reads no clock")
7298        }
7299    }
7300
7301    extern "C" fn caught(_: libc::c_int) {}
7302
7303    /// `waitpid` for exactly `child`, retried on EINTR: the pid and status
7304    /// it reported, or the errno of its failure.
7305    fn wait_for(
7306        child: libc::pid_t,
7307        options: libc::c_int,
7308    ) -> Result<(libc::pid_t, libc::c_int), i32> {
7309        let mut status = 0;
7310        loop {
7311            // SAFETY: waiting for our own child.
7312            let waited = unsafe { libc::waitpid(child, &mut status, options) };
7313            if waited != -1 {
7314                return Ok((waited, status));
7315            }
7316            match std::io::Error::last_os_error().raw_os_error() {
7317                Some(libc::EINTR) => continue,
7318                errno => return Err(errno.unwrap_or(0)),
7319            }
7320        }
7321    }
7322
7323    /// The parent's ownership of the forked child: it is killed and reaped
7324    /// when the owner is dropped, so a panic or a cancelled await in the test
7325    /// leaves no process behind. `finish` does the same on the normal path
7326    /// and checks that the child was reaped.
7327    struct ChildOwner {
7328        pid: libc::pid_t,
7329        reaped: bool,
7330    }
7331
7332    impl ChildOwner {
7333        /// Wait for the child to stop. A wait that reports it exited or
7334        /// killed reaped it, so the owner is disarmed before anything can
7335        /// unwind: its pid may already belong to another process.
7336        /// An ECHILD means the child is no longer ours to wait for (it was
7337        /// reaped already), so it disarms the owner too.
7338        fn wait_stopped(&mut self) -> Result<(), Result<(libc::pid_t, libc::c_int), i32>> {
7339            let waited = wait_for(self.pid, libc::WUNTRACED);
7340            match waited {
7341                Ok((pid, status))
7342                    if pid == self.pid
7343                        && (libc::WIFEXITED(status) || libc::WIFSIGNALED(status)) =>
7344                {
7345                    self.reaped = true;
7346                }
7347                Err(libc::ECHILD) => self.reaped = true,
7348                _ => {}
7349            }
7350            match waited {
7351                Ok((pid, status)) if pid == self.pid && libc::WIFSTOPPED(status) => Ok(()),
7352                other => Err(other),
7353            }
7354        }
7355
7356        /// Whether the owner had disarmed itself, read once the child is
7357        /// known to be reaped. The owner is disarmed from here on whatever
7358        /// the answer, so a failed check of it unwinds without signalling a
7359        /// pid that may have been reused.
7360        fn disarmed_after_reap(&mut self) -> bool {
7361            std::mem::replace(&mut self.reaped, true)
7362        }
7363
7364        fn kill_and_reap(&mut self) -> Result<(libc::pid_t, libc::c_int), i32> {
7365            // SAFETY: killing our own child, which is not yet reaped.
7366            unsafe { libc::kill(self.pid, libc::SIGKILL) };
7367            let reaped = wait_for(self.pid, 0);
7368            self.reaped = true;
7369            reaped
7370        }
7371
7372        fn finish(mut self) {
7373            let reaped = self.kill_and_reap();
7374            assert!(
7375                matches!(reaped, Ok((pid, status)) if pid == self.pid
7376                    && libc::WIFSIGNALED(status)
7377                    && libc::WTERMSIG(status) == libc::SIGKILL),
7378                "the child was not reaped: {reaped:?}"
7379            );
7380        }
7381    }
7382
7383    impl Drop for ChildOwner {
7384        fn drop(&mut self) {
7385            if !self.reaped {
7386                // Unwinding already: the reap's outcome is not checked here.
7387                let _ = self.kill_and_reap();
7388            }
7389        }
7390    }
7391
7392    /// A forked child, owned by the parent from the moment `fork` returns,
7393    /// that exits with status 7 at once if `exits`.
7394    fn forked_child(exits: bool) -> ChildOwner {
7395        // SAFETY: the child calls only async-signal-safe functions, with no
7396        // allocation, lock or destructor, then stops until it is killed.
7397        let child = unsafe { libc::fork() };
7398        if child == 0 {
7399            unsafe {
7400                if exits {
7401                    libc::_exit(7);
7402                }
7403                let mut action: libc::sigaction = std::mem::zeroed();
7404                action.sa_sigaction = caught as extern "C" fn(libc::c_int) as usize;
7405                libc::sigaction(libc::SIGCHLD, &action, std::ptr::null_mut());
7406                let mut set: libc::sigset_t = std::mem::zeroed();
7407                libc::sigemptyset(&mut set);
7408                libc::sigaddset(&mut set, libc::SIGCHLD);
7409                libc::pthread_sigmask(libc::SIG_UNBLOCK, &set, std::ptr::null_mut());
7410                libc::raise(libc::SIGSTOP);
7411                libc::_exit(0);
7412            }
7413        }
7414        assert!(child > 0, "fork failed");
7415        ChildOwner {
7416            pid: child,
7417            reaped: false,
7418        }
7419    }
7420
7421    /// A child stopped as a sleeper is when Detcore handles its call before
7422    /// filing the sleep: SIGCHLD caught and unblocked, and SIGCHLD pending in
7423    /// its private queue, sent to the thread while it was stopped.
7424    fn stopped_child_with_a_private_sigchld() -> ChildOwner {
7425        let mut owner = forked_child(false);
7426        let child = owner.pid;
7427        if let Err(waited) = owner.wait_stopped() {
7428            panic!("the child did not stop: {waited:?}");
7429        }
7430        // SAFETY: the child is ours and stopped; SIGCHLD stays pending.
7431        let sent = unsafe { libc::syscall(libc::SYS_tgkill, child, child, libc::SIGCHLD) };
7432        assert_eq!(sent, 0, "tgkill failed");
7433        owner
7434    }
7435
7436    /// The before-park question through the production path: a guest's
7437    /// thread-directed SIGCHLD, committed (`PrivateSent`, as the sender's turn
7438    /// records it) while no sleep was filed, so that turn had no sleep to
7439    /// wake, is found by `sleep_signal(BeforePark)`'s own query of the ledger,
7440    /// and the sleep ends at once as pending for it. With no committed record
7441    /// the same kernel state, which `/proc` cannot tell from a host-timed copy,
7442    /// ends nothing (the Codex re-check of
7443    /// https://github.com/rrnewton/hermit/pull/4177).
7444    #[tokio::test(flavor = "current_thread")]
7445    async fn a_private_sigchld_committed_before_the_sleep_is_filed_ends_it_through_the_ledger() {
7446        // The fixture's owner: a child that exits instead of stopping is
7447        // reaped by the wait, which disarms the owner, so an unwind then
7448        // signals no pid that may have been reused.
7449        let mut exited = forked_child(true);
7450        let wait = exited.wait_stopped();
7451        let exit_reaped = matches!(wait, Err(Ok((pid, status))) if pid == exited.pid
7452            && libc::WIFEXITED(status)
7453            && libc::WEXITSTATUS(status) == 7);
7454        let disarmed = exit_reaped && exited.disarmed_after_reap();
7455        assert!(exit_reaped, "{wait:?}");
7456        assert!(disarmed, "a reaped child disarms its owner");
7457        drop(exited);
7458        // A child already reaped elsewhere answers ECHILD, which disarms the
7459        // owner as well. The raw wait that reaps it disarms the owner before
7460        // anything is checked; the owner is armed again only for the call
7461        // under test.
7462        let mut gone = forked_child(true);
7463        let reaped_elsewhere = wait_for(gone.pid, 0);
7464        gone.reaped = matches!(reaped_elsewhere, Ok((pid, _)) if pid == gone.pid)
7465            || reaped_elsewhere == Err(libc::ECHILD);
7466        assert!(
7467            matches!(reaped_elsewhere, Ok((pid, _)) if pid == gone.pid),
7468            "{reaped_elsewhere:?}"
7469        );
7470        gone.reaped = false;
7471        let wait = gone.wait_stopped();
7472        let disarmed = gone.disarmed_after_reap();
7473        assert_eq!(wait, Err(Err(libc::ECHILD)));
7474        assert!(disarmed, "an ECHILD disarms its owner");
7475        drop(gone);
7476
7477        let owner = stopped_child_with_a_private_sigchld();
7478        let child = owner.pid;
7479        let state_of_child = crate::syscalls::threads::read_thread_wait_signal_state(
7480            Pid::from_raw(child),
7481            Pid::from_raw(child),
7482        )
7483        .expect("the child's signal state");
7484        let chld = kernel_sigset_bit(libc::SIGCHLD);
7485        assert_eq!(
7486            state_of_child.thread_pending & chld,
7487            chld,
7488            "pending, private"
7489        );
7490        assert_eq!(state_of_child.blocked & chld, 0, "unblocked");
7491        assert_eq!(state_of_child.caught & chld, chld, "caught");
7492        let mut verdicts = Vec::new();
7493        for committed in [true, false] {
7494            let config = Config {
7495                sequentialize_threads: true,
7496                ..Config::default()
7497            }
7498            .with_backend(|backend| {
7499                detcore_model::config::set_kernel_signal_facts(backend, true);
7500                backend.delivers_preemption_timer_events = false;
7501                backend.runs_guest_code_between_injections = false;
7502            });
7503            let state = GlobalState::for_tests(&config);
7504            let mut guest = SleeperGuest {
7505                state: &state,
7506                thread: ThreadState::new(DetPid::from_raw(child), &config, ()),
7507                config,
7508                pid: Pid::from_raw(child),
7509            };
7510            if committed {
7511                let target = guest.thread_state().dettid;
7512                crate::tool_global::child_exit_sigchld(
7513                    &mut guest,
7514                    ChildExitSigchldControl::PrivateSent { target },
7515                )
7516                .await;
7517            }
7518            verdicts.push(sleep_signal(&mut guest, SleepCheck::BeforePark, false).await);
7519        }
7520        owner.finish();
7521        assert!(
7522            matches!(verdicts[0], Ok(SleepSignal::Pending)),
7523            "committed: {:?}",
7524            verdicts[0]
7525        );
7526        assert!(
7527            matches!(verdicts[1], Ok(SleepSignal::None)),
7528            "not committed: {:?}",
7529            verdicts[1]
7530        );
7531    }
7532
7533    /// The futex counterpart: a precise futex wait's own check before it
7534    /// parks (`futex_wait_pending_signals`) finds a guest's thread-directed
7535    /// SIGCHLD committed (`PrivateSent`) before the wait was filed, through
7536    /// the production query of the ledger, and the wait ends with its restart
7537    /// errno. With no record, or when a host-timed source can post SIGCHLD to
7538    /// the process, the same kernel state ends nothing and the wait parks
7539    /// (https://github.com/rrnewton/hermit/issues/4132).
7540    #[tokio::test(flavor = "current_thread")]
7541    async fn a_private_sigchld_committed_before_a_futex_wait_parks_ends_it_through_the_ledger() {
7542        let owner = stopped_child_with_a_private_sigchld();
7543        let child = owner.pid;
7544        let state = crate::syscalls::threads::read_thread_wait_signal_state(
7545            Pid::from_raw(child),
7546            Pid::from_raw(child),
7547        )
7548        .expect("the child's signal state");
7549        let chld = kernel_sigset_bit(libc::SIGCHLD);
7550        let interrupting = state.pending_interrupting(state.blocked, false);
7551        let mut pending = Vec::new();
7552        for (committed, host_timed) in [(true, 0), (false, 0), (true, chld)] {
7553            let config = Config {
7554                sequentialize_threads: true,
7555                ..Config::default()
7556            }
7557            .with_backend(|backend| {
7558                detcore_model::config::set_kernel_signal_facts(backend, true);
7559                backend.delivers_preemption_timer_events = false;
7560                backend.runs_guest_code_between_injections = false;
7561            });
7562            let global = GlobalState::for_tests(&config);
7563            let mut guest = SleeperGuest {
7564                state: &global,
7565                thread: ThreadState::new(DetPid::from_raw(child), &config, ()),
7566                config,
7567                pid: Pid::from_raw(child),
7568            };
7569            if committed {
7570                let target = guest.thread_state().dettid;
7571                crate::tool_global::child_exit_sigchld(
7572                    &mut guest,
7573                    ChildExitSigchldControl::PrivateSent { target },
7574                )
7575                .await;
7576            }
7577            let held = crate::syscalls::threads::futex_wait_held_after_wake(host_timed, 0);
7578            pending.push(
7579                crate::syscalls::threads::futex_wait_pending_signals(
7580                    &mut guest,
7581                    &state,
7582                    interrupting,
7583                    held,
7584                    host_timed,
7585                    0,
7586                )
7587                .await,
7588            );
7589        }
7590        owner.finish();
7591        assert_eq!(interrupting & chld, chld, "pending, unblocked and caught");
7592        assert_eq!(
7593            pending,
7594            vec![chld, 0, 0],
7595            "recorded, unrecorded, host-timed"
7596        );
7597    }
7598
7599    /// A futex wait's own check after a wake decides a held `SIGCHLD` from
7600    /// the private queue and the schedule's record together: after a flush
7601    /// cleared the record, a `SIGCHLD` only in the shared queue (which a
7602    /// host-timed source may have posted) ends nothing, and neither does a
7603    /// record whose copy is no longer in the private queue. Both waits park
7604    /// again (the design check of https://github.com/rrnewton/hermit/issues/4132).
7605    #[tokio::test(flavor = "current_thread")]
7606    async fn a_futex_waits_check_after_a_wake_needs_the_private_copy_and_its_record() {
7607        let chld = kernel_sigset_bit(libc::SIGCHLD);
7608        let state = |thread: KernelSigset, shared: KernelSigset| {
7609            crate::syscalls::threads::KernelSignalState {
7610                pending: thread | shared,
7611                thread_pending: thread,
7612                shared_pending: shared,
7613                caught: chld,
7614                ..Default::default()
7615            }
7616        };
7617        let mut answers = Vec::new();
7618        for (name, kernel, recorded, flushed) in [
7619            ("private, recorded", state(chld, 0), true, false),
7620            ("flushed, shared only", state(0, chld), true, true),
7621            ("recorded, shared only", state(0, chld), true, false),
7622        ] {
7623            let config = Config {
7624                sequentialize_threads: true,
7625                ..Config::default()
7626            }
7627            .with_backend(|backend| {
7628                detcore_model::config::set_kernel_signal_facts(backend, true);
7629                backend.delivers_preemption_timer_events = false;
7630                backend.runs_guest_code_between_injections = false;
7631            });
7632            let global = GlobalState::for_tests(&config);
7633            let mut guest = SleeperGuest {
7634                state: &global,
7635                thread: ThreadState::new(DetPid::from_raw(4242), &config, ()),
7636                config,
7637                pid: Pid::from_raw(4242),
7638            };
7639            let target = guest.thread_state().dettid;
7640            if recorded {
7641                crate::tool_global::child_exit_sigchld(
7642                    &mut guest,
7643                    ChildExitSigchldControl::PrivateSent { target },
7644                )
7645                .await;
7646            }
7647            if flushed {
7648                crate::tool_global::child_exit_sigchld(
7649                    &mut guest,
7650                    ChildExitSigchldControl::Flushed,
7651                )
7652                .await;
7653            }
7654            let held = crate::syscalls::threads::futex_wait_held_after_wake(0, 0);
7655            let interrupting = kernel.pending_interrupting(kernel.blocked, false);
7656            let pending = crate::syscalls::threads::futex_wait_pending_signals(
7657                &mut guest,
7658                &kernel,
7659                interrupting,
7660                held,
7661                0,
7662                0,
7663            )
7664            .await;
7665            answers.push((name, pending));
7666        }
7667        assert_eq!(
7668            answers,
7669            vec![
7670                ("private, recorded", chld),
7671                ("flushed, shared only", 0),
7672                ("recorded, shared only", 0),
7673            ]
7674        );
7675    }
7676
7677    /// After the drain woke a futex waiter for a guest's private `SIGCHLD`,
7678    /// a sibling flushed it (`SIG_IGN`, then the handler again) and sent
7679    /// `kill(getpid(), SIGCHLD)`, a copy in the shared queue. Here a forked
7680    /// child, stopped with `SIGCHLD` caught and unblocked, is sent that shared
7681    /// copy, so `/proc` shows it in `ShdPnd` only, and the ledger records the
7682    /// private send and the flush. The waiter's check, as
7683    /// `handle_futex_blocking` runs it with the drain's `SIGCHLD` left out of
7684    /// the committed set (`drain_woken_waiters`), ends nothing, and the
7685    /// wait parks again: a futex wait holds shared copies. Had the drain's
7686    /// `SIGCHLD` been committed, as before the design check of
7687    /// https://github.com/rrnewton/hermit/issues/4132, the shared copy, which
7688    /// a host-timed child exit could equally have posted, would have ended it.
7689    #[tokio::test(flavor = "current_thread")]
7690    async fn a_shared_sigchld_after_a_flush_does_not_end_a_drain_woken_futex_wait() {
7691        let mut owner = forked_child(false);
7692        let child = owner.pid;
7693        if let Err(waited) = owner.wait_stopped() {
7694            panic!("the child did not stop: {waited:?}");
7695        }
7696        // SAFETY: the child is ours and stopped; SIGCHLD stays pending.
7697        assert_eq!(
7698            unsafe { libc::kill(child, libc::SIGCHLD) },
7699            0,
7700            "kill failed"
7701        );
7702        let state = crate::syscalls::threads::read_thread_wait_signal_state(
7703            Pid::from_raw(child),
7704            Pid::from_raw(child),
7705        )
7706        .expect("the child's signal state");
7707        let chld = kernel_sigset_bit(libc::SIGCHLD);
7708        let mut pending = Vec::new();
7709        for committed_wake in [0, chld] {
7710            let config = Config {
7711                sequentialize_threads: true,
7712                ..Config::default()
7713            }
7714            .with_backend(|backend| {
7715                detcore_model::config::set_kernel_signal_facts(backend, true);
7716                backend.delivers_preemption_timer_events = false;
7717                backend.runs_guest_code_between_injections = false;
7718            });
7719            let global = GlobalState::for_tests(&config);
7720            let mut guest = SleeperGuest {
7721                state: &global,
7722                thread: ThreadState::new(DetPid::from_raw(child), &config, ()),
7723                config,
7724                pid: Pid::from_raw(child),
7725            };
7726            let target = guest.thread_state().dettid;
7727            crate::tool_global::child_exit_sigchld(
7728                &mut guest,
7729                ChildExitSigchldControl::PrivateSent { target },
7730            )
7731            .await;
7732            crate::tool_global::child_exit_sigchld(&mut guest, ChildExitSigchldControl::Flushed)
7733                .await;
7734            let held = crate::syscalls::threads::futex_wait_held_after_wake(0, committed_wake);
7735            pending.push(
7736                crate::syscalls::threads::futex_wait_pending_signals(
7737                    &mut guest,
7738                    &state,
7739                    state.pending_interrupting(state.blocked, false),
7740                    held,
7741                    0,
7742                    committed_wake,
7743                )
7744                .await,
7745            );
7746        }
7747        owner.finish();
7748        assert_eq!(
7749            (state.thread_pending & chld, state.shared_pending & chld),
7750            (0, chld),
7751            "SIGCHLD pending in the shared queue only"
7752        );
7753        assert_eq!(pending, vec![0, chld], "stripped, then committed as before");
7754    }
7755}
7756
7757#[cfg(test)]
7758mod own_process_signal_tests {
7759    use super::*;
7760
7761    const SENDER_SIBLING: i32 = 11;
7762
7763    /// The main thread sends SIGUSR1, which it catches, to its own process
7764    /// while one other thread is parked with an empty mask: admitted (S1).
7765    fn admitted_s1() -> OwnProcessSignal {
7766        OwnProcessSignal {
7767            sig: libc::SIGUSR1,
7768            names_sender: true,
7769            caught: kernel_sigset_bit(libc::SIGUSR1),
7770            inputs: OwnProcessSignalInputs {
7771                siblings: vec![(
7772                    DetTid::from_raw(SENDER_SIBLING),
7773                    SiblingSignalState::InSchedule(Some(0)),
7774                )],
7775                ..Default::default()
7776            },
7777            ..Default::default()
7778        }
7779    }
7780
7781    fn refusal(edit: impl FnOnce(&mut OwnProcessSignal)) -> Option<OwnProcessSignalRefusal> {
7782        let mut send = admitted_s1();
7783        edit(&mut send);
7784        own_process_signal_refusal(&send)
7785    }
7786
7787    /// Round-3 ruling 3: a copy to user memory faults on a byte without
7788    /// write permission (read-only, execute-only, `PROT_NONE`) or in a hole,
7789    /// and not across writable mappings or below a grow-down one.
7790    #[test]
7791    fn a_copy_to_user_memory_faults_where_no_mapping_is_writable() {
7792        let smaps = "1000-2000 rw-p 0 0:0 0\nVmFlags: rd wr mr mw\n\
7793                     2000-3000 -w-p 0 0:0 0\nVmFlags: wr mw\n\
7794                     3000-4000 r--p 0 0:0 0\nVmFlags: rd mr\n\
7795                     6000-7000 --xp 0 0:0 0 [vvar]\nVmFlags: ex\n\
7796                     9000-a000 rw-p 0 0:0 0 [stack]\nVmFlags: rd wr gd\n";
7797        let mappings = parse_smaps(smaps);
7798        assert!(!user_unwritable(&mappings, 0x1fc0, 128), "rw- into -w-");
7799        assert!(
7800            user_unwritable(&mappings, 0x2fc0, 128),
7801            "runs into read-only"
7802        );
7803        assert!(user_unwritable(&mappings, 0x6000, 8), "execute-only");
7804        assert!(
7805            user_unwritable(&mappings, 0x4000, 8),
7806            "a hole below a fixed mapping"
7807        );
7808        assert!(
7809            !user_unwritable(&mappings, 0x8ff8, 16),
7810            "a hole below the grow-down stack, into it"
7811        );
7812        assert!(user_unwritable(&mappings, usize::MAX - 4, 8), "overflow");
7813    }
7814
7815    /// The first byte user mode cannot read is found across contiguous
7816    /// readable, write-only and execute-only mappings, and is a `PROT_NONE`
7817    /// mapping, a hole, or a hole below a grow-down mapping.
7818    #[test]
7819    fn the_first_user_unreadable_byte_is_classified_by_its_mapping() {
7820        let smaps = "1000-2000 r--p 0 0:0 0\nVmFlags: rd mr\n\
7821                     2000-3000 -w-p 0 0:0 0\nVmFlags: wr mw\n\
7822                     3000-4000 ---p 0 0:0 0\nVmFlags: mr\n\
7823                     6000-7000 --xp 0 0:0 0 [vvar]\nVmFlags: ex\n\
7824                     9000-a000 rw-p 0 0:0 0 [stack]\nVmFlags: rd wr gd\n";
7825        let mappings = parse_smaps(smaps);
7826        assert_eq!(mappings.len(), 5);
7827        assert!(mappings[4].growsdown && !mappings[0].growsdown);
7828        assert_eq!(
7829            first_user_unreadable(&mappings, 0x1fc0, 128),
7830            None,
7831            "crosses from read-only into write-only"
7832        );
7833        assert_eq!(
7834            first_user_unreadable(&mappings, 0x6000, 128),
7835            None,
7836            "execute-only"
7837        );
7838        assert_eq!(
7839            first_user_unreadable(&mappings, 0x2fc0, 128),
7840            Some(Unreadable::NoPermission),
7841            "runs into PROT_NONE"
7842        );
7843        assert_eq!(
7844            first_user_unreadable(&mappings, 0x4000, 48),
7845            Some(Unreadable::Hole {
7846                below_growsdown: false
7847            }),
7848            "a hole below a fixed mapping"
7849        );
7850        assert_eq!(
7851            first_user_unreadable(&mappings, 0x8000, 48),
7852            Some(Unreadable::Hole {
7853                below_growsdown: true
7854            }),
7855            "a hole below the stack"
7856        );
7857        assert_eq!(
7858            first_user_unreadable(&mappings, 0xb000, 48),
7859            Some(Unreadable::Hole {
7860                below_growsdown: false
7861            }),
7862            "above every mapping"
7863        );
7864        assert_eq!(
7865            first_user_unreadable(&mappings, 0, 48),
7866            Some(Unreadable::Hole {
7867                below_growsdown: false
7868            })
7869        );
7870        assert_eq!(
7871            first_user_unreadable(&mappings, usize::MAX - 10, 48),
7872            Some(Unreadable::Hole {
7873                below_growsdown: false
7874            })
7875        );
7876    }
7877
7878    #[test]
7879    fn the_sender_named_by_its_call_is_admitted_whatever_its_siblings_masks() {
7880        assert_eq!(refusal(|_| {}), None);
7881    }
7882
7883    /// S2: the call names the leader but another thread sends; admitted only
7884    /// when every other live thread blocks the signal or has its exit granted.
7885    #[test]
7886    fn another_sender_is_admitted_only_when_every_live_sibling_blocks() {
7887        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
7888        let leader = DetTid::from_raw(SENDER_SIBLING);
7889        assert_eq!(
7890            refusal(|send| send.names_sender = false),
7891            Some(OwnProcessSignalRefusal::NotSoleReceiver(leader))
7892        );
7893        assert_eq!(
7894            refusal(|send| {
7895                send.names_sender = false;
7896                send.inputs.siblings = vec![(leader, SiblingSignalState::InSchedule(Some(usr1)))];
7897            }),
7898            None
7899        );
7900        assert_eq!(
7901            refusal(|send| {
7902                send.names_sender = false;
7903                send.inputs.siblings = vec![(leader, SiblingSignalState::ExitGranted)];
7904            }),
7905            None
7906        );
7907    }
7908
7909    /// Only the seven admitted signals pass; each other signal is refused
7910    /// whatever its disposition.
7911    #[test]
7912    fn only_the_seven_signals_below_sigchld_are_admitted() {
7913        for sig in 1..=64 {
7914            let admitted = OWN_PROCESS_SIGNALS.contains(&sig);
7915            let got = refusal(|send| {
7916                send.sig = sig;
7917                send.caught = kernel_sigset_bit(sig);
7918            });
7919            if admitted {
7920                assert!(sig < libc::SIGCHLD, "signal {sig}");
7921                assert_eq!(got, None, "signal {sig}");
7922            } else {
7923                assert_eq!(
7924                    got,
7925                    Some(OwnProcessSignalRefusal::SignalNotAdmitted),
7926                    "signal {sig}"
7927                );
7928            }
7929        }
7930        for sig in [
7931            libc::SIGCHLD,
7932            libc::SIGCONT,
7933            libc::SIGTSTP,
7934            libc::SIGRTMIN(),
7935            libc::SIGSEGV,
7936            libc::SIGTRAP,
7937            libc::SIGSTKFLT,
7938            libc::SIGKILL,
7939            libc::SIGALRM,
7940        ] {
7941            assert!(!OWN_PROCESS_SIGNALS.contains(&sig), "signal {sig}");
7942        }
7943    }
7944
7945    #[test]
7946    fn a_default_disposition_is_refused_and_sig_ign_is_admitted() {
7947        assert_eq!(
7948            refusal(|send| send.caught = 0),
7949            Some(OwnProcessSignalRefusal::DefaultDisposition)
7950        );
7951        assert_eq!(
7952            refusal(|send| {
7953                send.caught = 0;
7954                send.ignored = kernel_sigset_bit(libc::SIGUSR1);
7955            }),
7956            None
7957        );
7958    }
7959
7960    #[test]
7961    fn each_mode_and_sender_state_refuses_by_name() {
7962        use OwnProcessSignalRefusal as R;
7963        let sibling = DetTid::from_raw(SENDER_SIBLING);
7964        assert_eq!(
7965            refusal(|send| send.record_replay = true),
7966            Some(R::RecordReplay)
7967        );
7968        assert_eq!(
7969            refusal(|send| send.schedule_replay = true),
7970            Some(R::ScheduleReplay)
7971        );
7972        assert_eq!(
7973            refusal(|send| send.inputs.guest_marker_signal = true),
7974            Some(R::GuestMarkerSignal)
7975        );
7976        assert_eq!(
7977            refusal(|send| send.stacktrace_signal = true),
7978            Some(R::StacktraceSignal)
7979        );
7980        assert_eq!(
7981            refusal(|send| send.blocked = kernel_sigset_bit(libc::SIGUSR1)),
7982            Some(R::SenderBlocks)
7983        );
7984        assert_eq!(
7985            refusal(|send| send.inputs.siblings[0].1 = SiblingSignalState::OutOfSchedule),
7986            Some(R::OutOfSchedule(sibling))
7987        );
7988        assert_eq!(
7989            refusal(|send| send.inputs.siblings[0].1 = SiblingSignalState::InSchedule(None)),
7990            Some(R::UnknownMask(sibling))
7991        );
7992        assert_eq!(
7993            refusal(|send| send.posthook_anchor = true),
7994            Some(R::PosthookAnchor)
7995        );
7996        assert_eq!(refusal(|send| send.signalfd = true), Some(R::Signalfd));
7997        assert_eq!(
7998            refusal(|send| send.inputs.exec_in_flight = true),
7999            Some(R::ExecInFlight)
8000        );
8001    }
8002
8003    /// The private queue refuses on any signal the sender does not block,
8004    /// except Reverie's timer marker.
8005    #[test]
8006    fn a_private_pending_signal_refuses_unless_blocked_or_the_timer_marker() {
8007        let hup = kernel_sigset_bit(libc::SIGHUP);
8008        let marker = kernel_sigset_bit(reverie::PERF_EVENT_SIGNAL as i32);
8009        assert_eq!(
8010            refusal(|send| send.thread_pending = hup),
8011            Some(OwnProcessSignalRefusal::PrivatePending(hup))
8012        );
8013        assert_eq!(
8014            refusal(|send| {
8015                send.thread_pending = hup;
8016                send.blocked = hup;
8017            }),
8018            None
8019        );
8020        assert_eq!(refusal(|send| send.thread_pending = marker), None);
8021    }
8022
8023    /// An armed host-timed source or a foreign exit signal refuses whatever its
8024    /// number, unless the sender blocks it.
8025    #[test]
8026    fn armed_sources_refuse_unless_the_sender_blocks_them() {
8027        let io = kernel_sigset_bit(libc::SIGIO);
8028        let usr2 = kernel_sigset_bit(libc::SIGUSR2);
8029        assert_eq!(
8030            refusal(|send| send.inputs.host_timed = io),
8031            Some(OwnProcessSignalRefusal::HostTimedSource(io))
8032        );
8033        assert_eq!(
8034            refusal(|send| {
8035                send.inputs.host_timed = io;
8036                send.blocked = io;
8037            }),
8038            None
8039        );
8040        assert_eq!(
8041            refusal(|send| send.inputs.foreign_exit_signals = usr2),
8042            Some(OwnProcessSignalRefusal::ForeignExitSignal(usr2))
8043        );
8044        assert_eq!(
8045            refusal(|send| {
8046                send.inputs.foreign_exit_signals = usr2;
8047                send.blocked = usr2;
8048            }),
8049            None
8050        );
8051    }
8052
8053    /// An undelivered scheduler signal refuses when numbered at or below the
8054    /// one sent, which it would precede or coalesce with, and not above it.
8055    #[test]
8056    fn an_undelivered_scheduler_signal_refuses_only_at_or_below_the_sent_one() {
8057        let term = libc::SIGTERM;
8058        for (pending, refuses) in [
8059            (libc::SIGHUP, true),
8060            (libc::SIGALRM, true),
8061            (libc::SIGTERM, true),
8062            (libc::SIGSTKFLT, false),
8063            (libc::SIGRTMIN(), false),
8064        ] {
8065            let bit = kernel_sigset_bit(pending);
8066            let got = refusal(|send| {
8067                send.sig = term;
8068                send.caught = kernel_sigset_bit(term);
8069                send.inputs.undelivered_scheduler_signals = bit;
8070            });
8071            assert_eq!(
8072                got,
8073                refuses.then_some(OwnProcessSignalRefusal::UndeliveredSchedulerSignal(bit)),
8074                "pending {pending}"
8075            );
8076        }
8077    }
8078}