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}