1use std::collections::HashMap;
12use std::marker::PhantomData;
13use std::sync::Arc;
14use std::sync::Mutex;
15use std::time::Duration;
16
17use procfs::process::Process;
18use rand::Rng;
19use reverie::BackendCapabilities;
20use reverie::Error;
21use reverie::GlobalRPC;
22use reverie::Guest;
23use reverie::Pid;
24use reverie::Stack;
25use reverie::syscalls;
26use reverie::syscalls::Addr;
27use reverie::syscalls::AddrMut;
28use reverie::syscalls::CloneFlags;
29use reverie::syscalls::Errno;
30use reverie::syscalls::MemoryAccess;
31use reverie::syscalls::ReadAddr;
32use reverie::syscalls::Syscall;
33use reverie::syscalls::SyscallInfo;
34use reverie::syscalls::Timespec;
35use reverie::syscalls::WaitPidFlag;
36use tracing::debug;
37use tracing::error;
38use tracing::info;
39use tracing::trace;
40
41use crate::SharedMemoryObjectId;
42use crate::config::BlockingMode;
43use crate::config::Config;
44use crate::config::SchedHeuristic;
45use crate::memory::MemoryMetadata;
46use crate::record_or_replay::RecordOrReplay;
47use crate::resources::ExternalOpId;
48use crate::resources::Permission;
49use crate::resources::ResourceID;
50use crate::resources::Resources;
51use crate::scheduler::FutexSignalWatch;
52use crate::scheduler::HostTimedSignalScope;
53use crate::scheduler::SchedValue;
54use crate::scheduler::pid_namespaces::GuestPidAnswer;
55use crate::syscalls::guest_siginfo::GuestSiginfo;
56use crate::syscalls::helpers::NonblockableSyscall;
57use crate::syscalls::helpers::RestartCall;
58use crate::syscalls::helpers::keep_restart_block;
59use crate::syscalls::helpers::record_retry_event;
60use crate::syscalls::helpers::retry_nonblocking_syscall;
61use crate::syscalls::helpers::retry_nonblocking_syscall_with_timeout;
62use crate::syscalls::pi_futex;
63use crate::syscalls::pi_futex::LockPiStep;
64use crate::syscalls::pi_futex::PiFutexState;
65use crate::syscalls::pi_futex::PiHandOver;
66use crate::syscalls::pi_futex::UnlockPiStep;
67use crate::syscalls::robust_list;
68use crate::tool_global::FutexAction;
69use crate::tool_global::GlobalState;
70use crate::tool_global::ResumeStatus;
71use crate::tool_global::await_exact_child_physical_exit;
72use crate::tool_global::cancel_exec;
73use crate::tool_global::child_tid_clear_address;
74use crate::tool_global::consume_child_wait;
75use crate::tool_global::create_child_thread;
76use crate::tool_global::futex_action;
77use crate::tool_global::hold_turn_for_group_exit;
78use crate::tool_global::host_timed_signals;
79use crate::tool_global::prepare_exec;
80use crate::tool_global::process_group;
81use crate::tool_global::ready_child_wait;
82use crate::tool_global::record_host_timed_signals;
83use crate::tool_global::resource_request;
84use crate::tool_global::set_child_tid_address;
85use crate::tool_global::thread_is_live;
86use crate::tool_global::thread_observe_time;
87use crate::tool_global::wait_for_child_lifecycle;
88use crate::tool_global::yield_once;
89use crate::tool_local::Detcore;
90use crate::tool_local::PendingVfork;
91use crate::tool_local::RobustListExit;
92use crate::tool_local::RobustListOwner;
93use crate::tool_local::RobustListWake;
94use crate::tool_local::ThreadState;
95use crate::types::ChildWaitExitClass;
96use crate::types::ChildWaitSelector;
97use crate::types::ChildWaitSpec;
98use crate::types::DetPid;
99use crate::types::DetTid;
100use crate::types::ExactChildWaitState;
101use crate::types::FutexID;
102use crate::types::LogicalTime;
103use crate::types::SigWrapper;
104
105#[path = "affinity.rs"]
106mod affinity;
107#[path = "emulated_waitid.rs"]
108mod emulated_waitid;
109
110pub(crate) const VIRTUAL_CPUSET_BYTES: usize = 16;
113
114const IOPRIO_WHO_PROCESS: libc::c_int = 1;
115const IOPRIO_WHO_PGRP: libc::c_int = 2;
116const IOPRIO_WHO_USER: libc::c_int = 3;
117const IOPRIO_CLASS_SHIFT: libc::c_int = 13;
118const IOPRIO_CLASS_BE: libc::c_int = 2;
119const IOPRIO_BE_NORM: libc::c_int = 4;
120const IOPRIO_DEFAULT_EFFECTIVE: libc::c_int =
121 (IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT) | IOPRIO_BE_NORM;
122
123const SCHED_ATTR_SIZE_VER0: u32 = 48; const SCHED_ATTR_SIZE_VER1: u32 = 56; const SCHED_ATTR_KERNEL_SIZE: u32 = SCHED_ATTR_SIZE_VER1;
135fn sched_attr_max_size() -> u32 {
138 u32::try_from(crate::page::page_size()).expect("the page size fits in u32")
139}
140
141const VIRTUAL_CURRENT_POLICY: u32 = libc::SCHED_OTHER as u32;
147
148#[derive(Debug, Eq, PartialEq)]
150enum TailVerdict {
151 AllZero,
152 NotZeroed,
155 Faulted,
157}
158
159const MIN_PROTECTION_RESPECTING_READ: usize = std::mem::size_of::<u64>() + 1;
166
167fn scan_tail_is_zeroed<M: MemoryAccess>(
183 memory: &M,
184 base: AddrMut<u8>,
185 tail_off: usize,
186 tail_len: usize,
187) -> TailVerdict {
188 const CHUNK: usize = 256;
189 let mut done = 0usize;
190 let mut buf = [0u8; CHUNK];
191 while done < tail_len {
192 let remaining = tail_len - done;
193 let want = remaining.min(CHUNK);
194 let back = MIN_PROTECTION_RESPECTING_READ
201 .saturating_sub(want)
202 .min(done + tail_off);
203 let read_len = want + back;
204 let start = tail_off + done - back;
205 let Some(addr) = base
206 .as_raw()
207 .checked_add(start)
208 .and_then(AddrMut::<u8>::from_raw)
209 else {
210 return TailVerdict::Faulted;
211 };
212 let got = memory.read(addr, &mut buf[..read_len]).unwrap_or(0);
216 let new_from = back.min(got);
218 if buf[new_from..got].iter().any(|byte| *byte != 0) {
219 return TailVerdict::NotZeroed;
220 }
221 if got < read_len {
222 return TailVerdict::Faulted;
225 }
226 done += want;
227 }
228 TailVerdict::AllZero
229}
230
231const SCHED_FIFO: u32 = 1;
233const SCHED_RR: u32 = 2;
234const SCHED_DEADLINE: u32 = 6;
235const SCHED_EXT: u32 = 7;
236const MAX_RT_PRIO: u32 = 100;
238
239const SCHED_ATTR_OFF_POLICY: usize = 4;
242const SCHED_ATTR_OFF_FLAGS: usize = 8;
243const SCHED_ATTR_OFF_PRIORITY: usize = 20;
244const SCHED_ATTR_OFF_RUNTIME: usize = 24;
245const SCHED_ATTR_OFF_DEADLINE: usize = 32;
246const SCHED_ATTR_OFF_PERIOD: usize = 40;
247
248const SCHED_FLAG_RESET_ON_FORK: u64 = 0x01;
250const SCHED_FLAG_RECLAIM: u64 = 0x02;
251const SCHED_FLAG_DL_OVERRUN: u64 = 0x04;
252const SCHED_FLAG_KEEP_POLICY: u64 = 0x08;
253const SCHED_FLAG_KEEP_PARAMS: u64 = 0x10;
254const SCHED_FLAG_UTIL_CLAMP_MIN: u64 = 0x20;
255const SCHED_FLAG_UTIL_CLAMP_MAX: u64 = 0x40;
256const SCHED_FLAG_UTIL_CLAMP: u64 = SCHED_FLAG_UTIL_CLAMP_MIN | SCHED_FLAG_UTIL_CLAMP_MAX;
257const SCHED_FLAG_ALL: u64 = SCHED_FLAG_RESET_ON_FORK
258 | SCHED_FLAG_RECLAIM
259 | SCHED_FLAG_DL_OVERRUN
260 | SCHED_FLAG_KEEP_POLICY
261 | SCHED_FLAG_KEEP_PARAMS
262 | SCHED_FLAG_UTIL_CLAMP;
263
264fn is_valid_sched_policy(policy: u32) -> bool {
270 matches!(
271 policy,
272 0 | 1 | 2 | 3
274 | 5
276 | SCHED_DEADLINE
277 | SCHED_EXT
278 )
279}
280
281fn is_rt_policy(policy: u32) -> bool {
283 policy == SCHED_FIFO || policy == SCHED_RR
284}
285
286fn deadline_params_are_valid(runtime: u64, deadline: u64, period: u64) -> bool {
294 if deadline == 0 {
296 return false;
297 }
298 if runtime < (1u64 << 10) {
301 return false;
302 }
303 if deadline & (1u64 << 63) != 0 || period & (1u64 << 63) != 0 {
305 return false;
306 }
307 let period = if period == 0 { deadline } else { period };
309 runtime <= deadline && deadline <= period
311}
312
313fn sched_attr_effective_size(declared: u32) -> Result<u32, ()> {
323 let size = if declared == 0 {
324 SCHED_ATTR_SIZE_VER0
325 } else {
326 declared
327 };
328 if !(SCHED_ATTR_SIZE_VER0..=sched_attr_max_size()).contains(&size) {
329 return Err(());
330 }
331 Ok(size)
332}
333
334#[derive(Clone, Copy, Debug)]
336struct SchedAttrFields {
337 policy: u32,
338 sched_flags: u64,
339 priority: u32,
340 runtime: u64,
341 deadline: u64,
342 period: u64,
343}
344
345fn validate_sched_attr_before_lookup(size: u32, attr: &SchedAttrFields) -> Result<(), Errno> {
355 if attr.sched_flags & SCHED_FLAG_UTIL_CLAMP != 0 && size < SCHED_ATTR_SIZE_VER1 {
358 return Err(Errno::EINVAL);
359 }
360 if (attr.policy as i32) < 0 {
364 return Err(Errno::EINVAL);
365 }
366 Ok(())
367}
368
369fn validate_sched_attr_after_lookup(attr: &SchedAttrFields) -> Result<(), Errno> {
375 let policy = if attr.sched_flags & SCHED_FLAG_KEEP_POLICY != 0 {
393 Some(VIRTUAL_CURRENT_POLICY)
394 } else {
395 if !is_valid_sched_policy(attr.policy) {
396 return Err(Errno::EINVAL);
397 }
398 Some(attr.policy)
399 };
400 if attr.sched_flags & !SCHED_FLAG_ALL != 0 {
402 return Err(Errno::EINVAL);
403 }
404 if attr.priority > MAX_RT_PRIO - 1 {
408 return Err(Errno::EINVAL);
409 }
410 if let Some(policy) = policy {
411 if is_rt_policy(policy) != (attr.priority != 0) {
412 return Err(Errno::EINVAL);
413 }
414 if policy == SCHED_DEADLINE
415 && !deadline_params_are_valid(attr.runtime, attr.deadline, attr.period)
416 {
417 return Err(Errno::EINVAL);
418 }
419 }
420 Ok(())
421}
422
423fn virtual_ioprio(which: libc::c_int) -> Result<i64, Errno> {
426 match which {
427 IOPRIO_WHO_PROCESS => Ok(0),
428 IOPRIO_WHO_PGRP | IOPRIO_WHO_USER => Ok(i64::from(IOPRIO_DEFAULT_EFFECTIVE)),
429 _ => Err(Errno::EINVAL),
430 }
431}
432
433#[repr(C)]
434#[derive(Clone, Copy)]
435struct WaitidSigchldFields {
436 pid: libc::pid_t,
437 uid: libc::uid_t,
438 status: libc::c_int,
439 utime: libc::c_long,
440 stime: libc::c_long,
441}
442
443#[repr(C)]
444union WaitidSiginfoFields {
445 _alignment: *mut libc::c_void,
446 sigchld: WaitidSigchldFields,
447}
448
449#[repr(C)]
450struct WaitidSiginfoHead {
451 _base: [libc::c_int; 3],
452 fields: WaitidSiginfoFields,
453}
454
455fn wait_status_is_termination(status: libc::c_int) -> bool {
456 libc::WIFEXITED(status) || libc::WIFSIGNALED(status)
457}
458
459fn waitid_code_is_termination(code: libc::c_int) -> bool {
460 matches!(code, libc::CLD_EXITED | libc::CLD_KILLED | libc::CLD_DUMPED)
461}
462
463fn canonicalize_waitid_siginfo(info: &mut libc::siginfo_t) {
464 debug_assert!(
465 std::mem::size_of::<WaitidSiginfoHead>() <= std::mem::size_of::<libc::siginfo_t>()
466 );
467 let sigchld = unsafe {
472 &mut (*(info as *mut libc::siginfo_t).cast::<WaitidSiginfoHead>())
473 .fields
474 .sigchld
475 };
476 sigchld.utime = 0;
477 sigchld.stime = 0;
478}
479
480fn finish_waitid_result<T, G>(
482 guest: &mut G,
483 call: syscalls::Waitid,
484 value: i64,
485 mut info_value: libc::siginfo_t,
486 child: DetPid,
487) -> Result<i64, Error>
488where
489 T: RecordOrReplay,
490 G: Guest<Detcore<T>>,
491{
492 let child_pid = unsafe { info_value.si_pid() };
495 if child_pid != 0 {
496 canonicalize_waitid_siginfo(&mut info_value);
497 guest.memory().write_value(
498 call.info().expect("waitid infop checked before execution"),
499 &info_value,
500 )?;
501 if call.options() & libc::WNOWAIT == 0 && waitid_code_is_termination(info_value.si_code) {
502 guest.thread_state_mut().reap_child_process_cpu_time(child);
503 }
504 if let Some(rusage) = call.rusage() {
505 let usage: libc::rusage = unsafe { std::mem::zeroed() };
507 guest.memory().write_value(rusage, &usage)?;
508 }
509 }
510 Ok(value)
511}
512
513#[derive(Debug, Eq, PartialEq)]
514enum ExactWaitPollDecision {
515 ChildReady,
516 AwaitPhysicalExit,
517 ReapAfterLogicalExit,
518 Interrupted,
519 Retry,
520}
521
522fn exact_wait_poll_decision(
523 child_ready: bool,
524 signaled: bool,
525 lifecycle: Option<ExactChildWaitState>,
526) -> ExactWaitPollDecision {
527 if child_ready {
528 ExactWaitPollDecision::ChildReady
529 } else if lifecycle == Some(ExactChildWaitState::PhysicalExitPending) {
530 ExactWaitPollDecision::AwaitPhysicalExit
531 } else if matches!(
532 lifecycle,
533 Some(ExactChildWaitState::LogicallyExited | ExactChildWaitState::PhysicallyExited)
534 ) {
535 ExactWaitPollDecision::ReapAfterLogicalExit
536 } else if signaled {
537 ExactWaitPollDecision::Interrupted
538 } else {
539 ExactWaitPollDecision::Retry
540 }
541}
542
543fn stale_any_wait_must_interrupt(signaled: bool, next_ready: Option<DetPid>) -> bool {
544 signaled && next_ready.is_none()
545}
546
547fn terminal_child_wait_spec(
548 selector: ChildWaitSelector,
549 caller: DetTid,
550 options: libc::c_int,
551) -> ChildWaitSpec {
552 let exit_class = if options & libc::__WALL != 0 {
553 ChildWaitExitClass::Any
554 } else if options & libc::__WCLONE != 0 {
555 ChildWaitExitClass::Clone
556 } else {
557 ChildWaitExitClass::Sigchld
558 };
559 ChildWaitSpec {
560 selector,
561 owner: (options & libc::__WNOTHREAD != 0).then_some(caller),
562 exit_class,
563 }
564}
565
566fn validate_wait4_arguments(pid: libc::pid_t, options: WaitPidFlag) -> Result<(), Errno> {
567 let allowed_options = WaitPidFlag::WNOHANG
568 | WaitPidFlag::WUNTRACED
569 | WaitPidFlag::WCONTINUED
570 | WaitPidFlag::__WNOTHREAD
571 | WaitPidFlag::__WCLONE
572 | WaitPidFlag::__WALL;
573
574 if options.bits() & !allowed_options.bits() != 0 {
577 return Err(Errno::EINVAL);
578 }
579 if pid == libc::pid_t::MIN {
582 return Err(Errno::ESRCH);
583 }
584 Ok(())
585}
586
587pub(super) fn wait4_uses_terminal_selector(
594 options: WaitPidFlag,
595 serial_emulated_wait: bool,
596) -> bool {
597 if serial_emulated_wait {
598 options.bits() & !(libc::WNOHANG | libc::WUNTRACED | libc::__WNOTHREAD) == 0
599 } else {
600 !options.intersects(
601 WaitPidFlag::WUNTRACED
602 | WaitPidFlag::WCONTINUED
603 | WaitPidFlag::__WCLONE
604 | WaitPidFlag::__WALL,
605 )
606 }
607}
608
609pub(super) async fn await_selected_child_cpu_publication<G, T>(
618 guest: &mut G,
619 child: DetPid,
620 call: &str,
621) -> Result<(), Error>
622where
623 G: Guest<Detcore<T>>,
624 T: RecordOrReplay,
625{
626 if guest
627 .thread_state()
628 .wait_for_child_cpu_publication(child)
629 .await
630 {
631 Ok(())
632 } else {
633 Err(Error::Tool(anyhow::anyhow!(
634 "emulated {call} selected child has no owned final CPU publication"
635 )))
636 }
637}
638
639pub(super) async fn retire_selected_child<G, T>(
643 guest: &mut G,
644 child: DetPid,
645 call: &str,
646) -> Result<(), Error>
647where
648 G: Guest<Detcore<T>>,
649 T: RecordOrReplay,
650{
651 guest.thread_state_mut().reap_child_process_cpu_time(child);
652 if consume_child_wait(guest, child).await {
653 Ok(())
654 } else {
655 Err(Error::Tool(anyhow::anyhow!(
656 "emulated {call} selected child was not logically consumed"
657 )))
658 }
659}
660
661pub(super) async fn complete_selected_emulated_wait4<G, T>(
662 guest: &mut G,
663 call: syscalls::Wait4,
664 child: DetPid,
665) -> Result<i64, Error>
666where
667 G: Guest<Detcore<T>>,
668 T: RecordOrReplay,
669{
670 await_selected_child_cpu_publication(guest, child, "wait4").await?;
671 let exact = call
675 .with_pid(child.as_raw())
676 .with_options(WaitPidFlag::from_bits_retain(
677 call.options().bits() & (libc::WNOHANG | libc::WUNTRACED),
678 ));
679 let result = guest.inject(exact).await;
680 match result {
681 Ok(pid) if pid == i64::from(child.as_raw()) => {}
682 Err(Errno::EFAULT) => {}
685 unexpected => {
686 return Err(Error::Tool(anyhow::anyhow!(
687 "emulated wait4 selected-child invariant failed: {unexpected:?} for {child}"
688 )));
689 }
690 }
691 retire_selected_child(guest, child, "wait4").await?;
692 result.map_err(Error::from)
693}
694
695fn child_wait_can_retry_after_stale(spec: ChildWaitSpec) -> bool {
696 !matches!(spec.selector, ChildWaitSelector::Exact(_))
697}
698
699pub(super) type KernelSigset = u64;
700pub(super) const KERNEL_SIGSET_SIZE: usize = std::mem::size_of::<KernelSigset>();
701
702fn signal_is_blocked(mask: &KernelSigset, signal: SigWrapper) -> bool {
703 let raw_signal = signal.raw();
704 (1..=KernelSigset::BITS as i32).contains(&raw_signal)
705 && mask & (1_u64 << (raw_signal as u32 - 1)) != 0
706}
707
708#[repr(C)]
709#[derive(Clone, Copy)]
710pub(super) struct KernelSigaction {
711 pub(super) handler: u64,
712 pub(super) flags: u64,
713 pub(super) restorer: u64,
714 pub(super) mask: KernelSigset,
715}
716
717#[derive(Clone, Copy, Debug, Eq, PartialEq)]
718pub(super) enum WaitSignalDisposition {
719 Interrupt,
720 Restart,
721}
722
723fn signal_default_disposition_does_not_interrupt_child_wait(signal: SigWrapper) -> bool {
724 matches!(
725 signal.raw(),
726 libc::SIGCHLD | libc::SIGCONT | libc::SIGURG | libc::SIGWINCH
727 )
728}
729
730fn signal_default_action_ends_the_process(signal: SigWrapper) -> bool {
736 !matches!(
737 signal.raw(),
738 libc::SIGCHLD
739 | libc::SIGCONT
740 | libc::SIGURG
741 | libc::SIGWINCH
742 | libc::SIGSTOP
743 | libc::SIGTSTP
744 | libc::SIGTTIN
745 | libc::SIGTTOU
746 | libc::SIGKILL
747 )
748}
749
750pub(crate) fn retires_others_at_fatal_delivery(cfg: &Config) -> bool {
757 cfg.sequentialize_threads && cfg.backend.kernel_signal_state_is_guests && !cfg.gdbserver
758}
759
760pub(crate) fn fatal_default_delivery<G, T>(
769 guest: &G,
770 signal: i32,
771 may_retire_others: bool,
772) -> Option<bool>
773where
774 G: Guest<Detcore<T>>,
775 T: RecordOrReplay,
776{
777 match fatal_delivery_observation(guest, signal, may_retire_others) {
778 FatalDeliveryObservation::Fatal { may_retire_others } => Some(may_retire_others),
779 FatalDeliveryObservation::Nonfatal | FatalDeliveryObservation::Unknown => None,
780 }
781}
782
783#[derive(Clone, Copy, Debug, PartialEq, Eq)]
784pub(crate) enum FatalDeliveryObservation {
785 Nonfatal,
786 Fatal { may_retire_others: bool },
787 Unknown,
788}
789
790pub(crate) fn fatal_delivery_observation<G, T>(
793 guest: &G,
794 signal: i32,
795 may_retire_others: bool,
796) -> FatalDeliveryObservation
797where
798 G: Guest<Detcore<T>>,
799 T: RecordOrReplay,
800{
801 let copied = super::signal_shadow::copied_dispositions_for(guest, signal)
802 .map(|copied| default_action_ends_process(copied.ignored, copied.caught, signal));
803 if copied == Some(false) {
804 return FatalDeliveryObservation::Nonfatal;
805 }
806 let kernel = (copied.is_none() || may_retire_others)
807 .then(|| read_thread_signal_state(guest.pid(), guest.tid()).ok())
808 .flatten();
809 match copied.or_else(|| kernel.map(|state| state.default_action_ends_process(signal))) {
810 Some(true) => FatalDeliveryObservation::Fatal {
811 may_retire_others: may_retire_others
812 && kernel.is_some_and(|state| state.delivery_carries_out_a_fatal_default(signal)),
813 },
814 Some(false) => FatalDeliveryObservation::Nonfatal,
815 None => FatalDeliveryObservation::Unknown,
816 }
817}
818
819fn signal_has_uncatchable_default_disposition(signal: SigWrapper) -> bool {
820 matches!(signal.raw(), libc::SIGKILL | libc::SIGSTOP)
821}
822
823pub(super) async fn wait_signal_disposition<G, T>(
824 guest: &mut G,
825 status: ResumeStatus,
826 guest_signal_mask: &KernelSigset,
827 action_addr: AddrMut<'_, KernelSigaction>,
828 inspect_action: bool,
829) -> Result<Option<WaitSignalDisposition>, Error>
830where
831 G: Guest<Detcore<T>>,
832 T: RecordOrReplay,
833{
834 let ResumeStatus::Signaled(signals) = status else {
835 return Ok(None);
836 };
837 let Some(mut signals) = signals else {
838 return Ok(Some(WaitSignalDisposition::Interrupt));
839 };
840 signals.sort_by_key(|signal| signal.raw());
841 for signal in signals {
842 if signal_is_blocked(guest_signal_mask, signal) {
843 continue;
844 }
845 if signal_has_uncatchable_default_disposition(signal) {
846 return Ok(Some(WaitSignalDisposition::Interrupt));
847 }
848 if !inspect_action {
849 return Ok(Some(WaitSignalDisposition::Interrupt));
850 }
851 let call = syscalls::RtSigaction::new()
852 .with_signum(signal.raw())
853 .with_action(None)
854 .with_old_action(Some(action_addr.cast()))
855 .with_sigsetsize(std::mem::size_of::<u64>());
856 guest.inject_with_retry(call).await?;
857 let action: KernelSigaction = guest.memory().read_value(action_addr)?;
858 if action.handler == libc::SIG_IGN as u64
859 || action.handler == libc::SIG_DFL as u64
860 && signal_default_disposition_does_not_interrupt_child_wait(signal)
861 {
862 continue;
863 }
864 return Ok(Some(
865 if action.handler != libc::SIG_DFL as u64 && action.flags & libc::SA_RESTART as u64 != 0
866 {
867 WaitSignalDisposition::Restart
868 } else {
869 WaitSignalDisposition::Interrupt
870 },
871 ));
872 }
873 Ok(None)
874}
875
876pub(crate) fn futex_wait_held_after_wake(
882 host_timed: KernelSigset,
883 committed: KernelSigset,
884) -> KernelSigset {
885 (kernel_sigset_bit(libc::SIGCHLD) | host_timed) & !(committed & !host_timed)
886}
887
888pub(crate) async fn futex_wait_pending_signals<G, T>(
903 guest: &mut G,
904 state: &KernelSignalState,
905 interrupting: KernelSigset,
906 held: KernelSigset,
907 host_timed: KernelSigset,
908 committed: KernelSigset,
909) -> KernelSigset
910where
911 G: Guest<Detcore<T>>,
912 T: RecordOrReplay,
913{
914 let pending = without_discarded(guest, state, interrupting & !held, committed).await;
915 let sigchld = kernel_sigset_bit(libc::SIGCHLD) & !host_timed;
916 if pending == 0
917 && interrupting & state.thread_pending & held & sigchld != 0
918 && matches!(
919 crate::tool_global::child_exit_sigchld(
920 guest,
921 crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::PrivatePending,
922 )
923 .await,
924 crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer::Placed(true)
925 )
926 {
927 return sigchld;
928 }
929 pending
930}
931
932pub(crate) async fn without_discarded<G, T>(
945 guest: &mut G,
946 state: &KernelSignalState,
947 pending: KernelSigset,
948 committed: KernelSigset,
949) -> KernelSigset
950where
951 G: Guest<Detcore<T>>,
952 T: RecordOrReplay,
953{
954 use crate::scheduler::discarded_at_send::DiscardedSignalAnswer;
955 use crate::scheduler::discarded_at_send::DiscardedSignalControl;
956 use crate::scheduler::discarded_at_send::STANDARD_SIGNALS;
957 if pending & STANDARD_SIGNALS == 0
958 || !crate::scheduler::discarded_at_send::records(guest.config())
959 {
960 return pending;
961 }
962 let DiscardedSignalAnswer::Bits { thread, process } =
963 crate::tool_global::discarded_signal(guest, DiscardedSignalControl::Bits).await
964 else {
965 return pending;
966 };
967 with_kept_copies(state, thread, process, pending, committed)
968}
969
970pub(crate) fn with_kept_copies(
978 state: &KernelSignalState,
979 thread: u64,
980 process: u64,
981 pending: KernelSigset,
982 committed: KernelSigset,
983) -> KernelSigset {
984 let _ = committed;
985 let kept = (state.thread_pending & !thread) | (state.shared_pending & !process);
986 pending & kept
987}
988
989pub(crate) fn kernel_sigset_bit(raw_signal: i32) -> KernelSigset {
991 if (1..=KernelSigset::BITS as i32).contains(&raw_signal) {
992 1_u64 << (raw_signal as u32 - 1)
993 } else {
994 0
995 }
996}
997
998pub(crate) fn clone_host_timed_signals(
1007 flags: CloneFlags,
1008 exit_signal: libc::c_int,
1009) -> KernelSigset {
1010 if flags.intersects(CloneFlags::CLONE_THREAD | CloneFlags::CLONE_PARENT)
1011 || exit_signal == libc::SIGCHLD
1012 {
1013 0
1014 } else {
1015 kernel_sigset_bit(exit_signal)
1016 }
1017}
1018
1019#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1028pub(crate) struct KernelSignalState {
1029 pub(crate) pending: KernelSigset,
1031 pub(crate) thread_pending: KernelSigset,
1033 pub(crate) shared_pending: KernelSigset,
1035 pub(crate) blocked: KernelSigset,
1037 pub(crate) ignored: KernelSigset,
1039 pub(crate) caught: KernelSigset,
1041}
1042
1043pub(crate) fn default_action_ends_process(
1047 ignored: KernelSigset,
1048 caught: KernelSigset,
1049 signal: i32,
1050) -> bool {
1051 let bit = kernel_sigset_bit(signal);
1052 let stops_or_continues = [
1053 libc::SIGSTOP,
1054 libc::SIGTSTP,
1055 libc::SIGTTIN,
1056 libc::SIGTTOU,
1057 libc::SIGCONT,
1058 ]
1059 .into_iter()
1060 .fold(0, |set, signal| set | kernel_sigset_bit(signal));
1061 signal == libc::SIGKILL
1062 || ((ignored | caught | default_ignored_signals() | stops_or_continues) & bit) == 0
1063}
1064
1065pub(crate) fn ignored_at_generation(ignored: KernelSigset, caught: KernelSigset) -> KernelSigset {
1069 ignored | (default_ignored_signals() & !caught)
1070}
1071
1072impl KernelSignalState {
1073 pub(crate) fn parse(status: &str) -> Option<Self> {
1075 let mut thread_pending = None;
1076 let mut shared_pending = None;
1077 let mut state = KernelSignalState::default();
1078 let mut blocked = None;
1079 let mut ignored = None;
1080 let mut caught = None;
1081 for line in status.lines() {
1082 let Some((key, value)) = line.split_once(':') else {
1083 continue;
1084 };
1085 let slot = match key {
1086 "SigPnd" => &mut thread_pending,
1087 "ShdPnd" => &mut shared_pending,
1088 "SigBlk" => &mut blocked,
1089 "SigIgn" => &mut ignored,
1090 "SigCgt" => &mut caught,
1091 _ => continue,
1092 };
1093 *slot = Some(KernelSigset::from_str_radix(value.trim(), 16).ok()?);
1094 }
1095 state.thread_pending = thread_pending?;
1096 state.shared_pending = shared_pending?;
1097 state.pending = state.thread_pending | state.shared_pending;
1098 state.blocked = blocked?;
1099 state.ignored = ignored?;
1100 state.caught = caught?;
1101 Some(state)
1102 }
1103
1104 pub(crate) fn interrupting(&self, mask: KernelSigset) -> KernelSigset {
1110 let default_action = !(self.ignored | self.caught);
1111 (self.caught | (default_action & !default_ignored_signals()))
1112 & !mask
1113 & !kernel_sigset_bit(reverie::PERF_EVENT_SIGNAL as i32)
1114 }
1115
1116 pub(crate) fn discarded_at_generation(&self) -> KernelSigset {
1123 self.ignored_at_generation() & !self.blocked
1124 }
1125
1126 pub(crate) fn ignored_at_generation(&self) -> KernelSigset {
1131 ignored_at_generation(self.ignored, self.caught)
1132 }
1133
1134 pub(crate) fn default_action_ends_process(&self, signal: i32) -> bool {
1138 default_action_ends_process(self.ignored, self.caught, signal)
1139 }
1140
1141 pub(crate) fn delivery_carries_out_a_fatal_default(&self, signal: i32) -> bool {
1146 self.blocked & kernel_sigset_bit(signal) == 0 && self.default_action_ends_process(signal)
1147 }
1148
1149 pub(crate) fn default_job_control_stops(&self) -> KernelSigset {
1158 [libc::SIGTSTP, libc::SIGTTIN, libc::SIGTTOU]
1159 .into_iter()
1160 .fold(0, |set, signal| set | kernel_sigset_bit(signal))
1161 & !(self.ignored | self.caught)
1162 }
1163
1164 pub(crate) fn interrupting_wait(
1181 &self,
1182 mask: KernelSigset,
1183 defers_default_stops: bool,
1184 ) -> KernelSigset {
1185 let interrupting = self.interrupting(mask);
1186 if defers_default_stops {
1187 interrupting & !self.default_job_control_stops()
1188 } else {
1189 interrupting
1190 }
1191 }
1192
1193 pub(crate) fn pending_interrupting(
1196 &self,
1197 mask: KernelSigset,
1198 defers_default_stops: bool,
1199 ) -> KernelSigset {
1200 self.pending & self.interrupting_wait(mask, defers_default_stops)
1201 }
1202
1203 pub(crate) fn next_dequeued(&self, mask: KernelSigset) -> Option<(i32, SignalQueue)> {
1212 let synchronous = SYNCHRONOUS_SIGNALS
1213 .into_iter()
1214 .fold(0, |set, signal| set | kernel_sigset_bit(signal));
1215 let first = |queued: KernelSigset| {
1216 let deliverable = queued & !mask;
1217 let preferred = if deliverable & synchronous != 0 {
1218 deliverable & synchronous
1219 } else {
1220 deliverable
1221 };
1222 (preferred != 0).then(|| preferred.trailing_zeros() as i32 + 1)
1223 };
1224 first(self.thread_pending)
1225 .map(|signal| (signal, SignalQueue::Thread))
1226 .or_else(|| first(self.shared_pending).map(|signal| (signal, SignalQueue::Shared)))
1227 }
1228
1229 pub(crate) fn queued(&self, queue: SignalQueue) -> KernelSigset {
1231 match queue {
1232 SignalQueue::Thread => self.thread_pending,
1233 SignalQueue::Shared => self.shared_pending,
1234 }
1235 }
1236}
1237
1238pub(crate) fn default_ignored_signals() -> KernelSigset {
1240 [libc::SIGCHLD, libc::SIGCONT, libc::SIGURG, libc::SIGWINCH]
1241 .into_iter()
1242 .fold(0, |set, signal| set | kernel_sigset_bit(signal))
1243}
1244
1245const SYNCHRONOUS_SIGNALS: [i32; 6] = [
1248 libc::SIGSEGV,
1249 libc::SIGBUS,
1250 libc::SIGILL,
1251 libc::SIGTRAP,
1252 libc::SIGFPE,
1253 libc::SIGSYS,
1254];
1255
1256#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1258pub(crate) enum SignalQueue {
1259 Thread,
1261 Shared,
1263}
1264
1265#[cfg(not(test))]
1277static HOST_SIGNAL_STATE_READS_REFUSED: std::sync::atomic::AtomicBool =
1278 std::sync::atomic::AtomicBool::new(false);
1279
1280#[cfg(test)]
1281thread_local! {
1282 static HOST_SIGNAL_STATE_READS_REFUSED_FOR_TEST: std::cell::Cell<bool> =
1285 const { std::cell::Cell::new(false) };
1286}
1287
1288pub(crate) fn refuses_host_signal_state_reads(backend: &BackendCapabilities) -> bool {
1292 !crate::procfs_inode::guest_tids_name_host_tasks(backend, None)
1293}
1294
1295pub(crate) fn refuse_host_signal_state_reads(backend: &BackendCapabilities) {
1299 if !refuses_host_signal_state_reads(backend) {
1300 return;
1301 }
1302 #[cfg(not(test))]
1303 HOST_SIGNAL_STATE_READS_REFUSED.store(true, std::sync::atomic::Ordering::Relaxed);
1304 #[cfg(test)]
1305 HOST_SIGNAL_STATE_READS_REFUSED_FOR_TEST.with(|refused| refused.set(true));
1306}
1307
1308pub(crate) fn host_signal_state_reads_refused() -> bool {
1311 #[cfg(not(test))]
1312 {
1313 HOST_SIGNAL_STATE_READS_REFUSED.load(std::sync::atomic::Ordering::Relaxed)
1314 }
1315 #[cfg(test)]
1316 {
1317 HOST_SIGNAL_STATE_READS_REFUSED_FOR_TEST.with(|refused| refused.get())
1318 }
1319}
1320
1321pub(crate) fn read_kernel_signal_state(pid: Pid, tid: Pid) -> Result<KernelSignalState, Errno> {
1330 #[cfg(test)]
1331 if let Some(result) = signal_state_read_seam::scripted(pid, tid) {
1332 return result;
1333 }
1334 if host_signal_state_reads_refused() {
1335 static REPORTED: std::sync::Once = std::sync::Once::new();
1336 REPORTED.call_once(|| {
1337 error!(
1338 "refused a host /proc signal state read of guest thread {} (process {}): this \
1339 backend's guest thread IDs do not name host tasks",
1340 tid.as_raw(),
1341 pid.as_raw()
1342 );
1343 });
1344 return Err(Errno::ENOSYS);
1345 }
1346 let status = status_files::read(pid, tid)?;
1347 KernelSignalState::parse(&status).ok_or(Errno::EIO)
1348}
1349
1350pub(crate) fn forget_thread_status(tid: Pid) {
1353 status_files::forget(tid);
1354}
1355
1356pub(crate) fn keep_status_files_open() {
1362 status_files::enable();
1363}
1364
1365mod status_files {
1390 use std::collections::BTreeMap;
1391 use std::fs::File;
1392 use std::os::unix::fs::FileExt;
1393 use std::sync::Arc;
1394 use std::sync::LazyLock;
1395 use std::sync::Mutex;
1396
1397 use reverie::Pid;
1398 use reverie::syscalls::Errno;
1399
1400 pub(super) const CAPACITY: usize = 256;
1402
1403 type Kept = BTreeMap<(i32, i32), Arc<File>>;
1405
1406 static OPEN: LazyLock<Mutex<Kept>> = LazyLock::new(|| Mutex::new(BTreeMap::new()));
1407
1408 #[cfg(not(test))]
1410 static KEEPS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1411
1412 #[cfg(test)]
1413 thread_local! {
1414 static KEEPS_FOR_TEST: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1417 }
1418
1419 pub(super) fn enable() {
1421 #[cfg(not(test))]
1422 KEEPS.store(true, std::sync::atomic::Ordering::Relaxed);
1423 #[cfg(test)]
1424 KEEPS_FOR_TEST.with(|keeps| keeps.set(true));
1425 }
1426
1427 fn enabled() -> bool {
1428 #[cfg(not(test))]
1429 {
1430 KEEPS.load(std::sync::atomic::Ordering::Relaxed)
1431 }
1432 #[cfg(test)]
1433 {
1434 KEEPS_FOR_TEST.with(|keeps| keeps.get())
1435 }
1436 }
1437
1438 fn open() -> std::sync::MutexGuard<'static, Kept> {
1439 OPEN.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
1440 }
1441
1442 fn read_whole(file: &File) -> std::io::Result<String> {
1445 let mut bytes = Vec::with_capacity(4096);
1446 loop {
1447 let filled = bytes.len();
1448 bytes.resize(filled + 4096, 0);
1449 let read = file.read_at(&mut bytes[filled..], filled as u64)?;
1450 bytes.truncate(filled + read);
1451 if read == 0 {
1452 break;
1453 }
1454 }
1455 String::from_utf8(bytes)
1456 .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))
1457 }
1458
1459 pub(super) fn read(pid: Pid, tid: Pid) -> Result<String, Errno> {
1462 if !enabled() {
1463 let path = format!("/proc/{}/task/{}/status", pid.as_raw(), tid.as_raw());
1464 return std::fs::read_to_string(path).map_err(|_| Errno::ESRCH);
1465 }
1466 let key = (pid.as_raw(), tid.as_raw());
1467 let kept = open().get(&key).cloned();
1468 if let Some(file) = kept {
1469 if let Ok(text) = read_whole(&file)
1470 && !text.is_empty()
1471 {
1472 return Ok(text);
1473 }
1474 open().remove(&key);
1475 }
1476 let path = format!("/proc/{}/task/{}/status", key.0, key.1);
1477 let file = File::open(path).map_err(|_| Errno::ESRCH)?;
1478 let text = read_whole(&file).map_err(|_| Errno::ESRCH)?;
1479 let mut files = open();
1480 if files.len() < CAPACITY {
1481 files.insert(key, Arc::new(file));
1482 }
1483 Ok(text)
1484 }
1485
1486 pub(super) fn forget(tid: Pid) {
1488 if !enabled() {
1489 return;
1490 }
1491 open().retain(|&(_, kept_tid), _| kept_tid != tid.as_raw());
1492 }
1493
1494 #[cfg(test)]
1496 pub(super) fn kept() -> usize {
1497 open().len()
1498 }
1499
1500 #[cfg(test)]
1502 pub(super) fn keeps(pid: Pid, tid: Pid) -> bool {
1503 open().contains_key(&(pid.as_raw(), tid.as_raw()))
1504 }
1505
1506 #[cfg(test)]
1509 pub(super) fn kept_file(pid: Pid, tid: Pid) -> Option<Arc<File>> {
1510 open().get(&(pid.as_raw(), tid.as_raw())).cloned()
1511 }
1512
1513 #[cfg(test)]
1515 pub(super) fn read_kept(file: &File) -> std::io::Result<String> {
1516 read_whole(file)
1517 }
1518}
1519
1520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1525pub(crate) enum BlockedWaitSignalError {
1526 StateUnreadable { pid: Pid, tid: Pid, errno: Errno },
1530 MaskNotRestored {
1533 pid: Pid,
1534 tid: Pid,
1535 attempts: usize,
1536 last_error: Option<Errno>,
1537 },
1538}
1539
1540impl std::fmt::Display for BlockedWaitSignalError {
1541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1542 match self {
1543 Self::StateUnreadable { pid, tid, errno } => write!(
1544 f,
1545 "cannot read the signal state of guest thread {tid} (process {pid}) from \
1546 /proc while it waits: {errno}"
1547 ),
1548 Self::MaskNotRestored {
1549 pid,
1550 tid,
1551 attempts,
1552 last_error,
1553 } => {
1554 write!(
1555 f,
1556 "cannot restore the signal mask of guest thread {tid} (process {pid}) \
1557 after a blocked wait ({attempts} attempts"
1558 )?;
1559 if let Some(errno) = last_error {
1560 write!(f, ", last error {errno}")?;
1561 }
1562 f.write_str("); it would resume with every signal blocked")
1563 }
1564 }
1565 }
1566}
1567
1568impl std::error::Error for BlockedWaitSignalError {}
1569
1570pub(crate) fn guest_thread_is_gone(pid: Pid, tid: Pid) -> bool {
1572 let result = unsafe { libc::syscall(libc::SYS_tgkill, pid.as_raw(), tid.as_raw(), 0) };
1574 result == -1 && std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH)
1575}
1576
1577pub(crate) fn read_thread_signal_state(pid: Pid, tid: Pid) -> Result<KernelSignalState, Errno> {
1585 read_kernel_signal_state(pid, tid).or_else(|error| {
1586 if pid == tid {
1587 Err(error)
1588 } else {
1589 read_kernel_signal_state(tid, tid)
1590 }
1591 })
1592}
1593
1594pub(crate) fn thread_is_gone(pid: Pid, tid: Pid) -> bool {
1598 if !guest_thread_is_gone(pid, tid) {
1599 return false;
1600 }
1601 let result = unsafe { libc::syscall(libc::SYS_tkill, tid.as_raw(), 0) };
1603 result == -1 && std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH)
1604}
1605
1606pub(crate) fn read_thread_wait_signal_state(
1609 pid: Pid,
1610 tid: Pid,
1611) -> Result<KernelSignalState, Error> {
1612 match read_thread_signal_state(pid, tid) {
1613 Ok(state) => Ok(state),
1614 Err(_) if thread_is_gone(pid, tid) => Err(Errno::ERESTARTNOINTR.into()),
1615 Err(errno) => Err(Error::Tool(anyhow::Error::new(
1616 BlockedWaitSignalError::StateUnreadable { pid, tid, errno },
1617 ))),
1618 }
1619}
1620
1621pub(crate) fn read_wait_signal_state(pid: Pid, tid: Pid) -> Result<KernelSignalState, Error> {
1628 match read_kernel_signal_state(pid, tid) {
1629 Ok(state) => Ok(state),
1630 Err(_) if guest_thread_is_gone(pid, tid) => Err(Errno::ERESTARTNOINTR.into()),
1631 Err(errno) => Err(Error::Tool(anyhow::Error::new(
1632 BlockedWaitSignalError::StateUnreadable { pid, tid, errno },
1633 ))),
1634 }
1635}
1636
1637#[cfg(test)]
1639pub(crate) mod signal_state_read_seam {
1640 use std::cell::RefCell;
1641
1642 use reverie::Pid;
1643 use reverie::syscalls::Errno;
1644
1645 use super::KernelSignalState;
1646
1647 type Script = Box<dyn FnMut(Pid, Pid) -> Option<Result<KernelSignalState, Errno>>>;
1648
1649 thread_local! {
1650 static SCRIPT: RefCell<Option<Script>> = const { RefCell::new(None) };
1651 }
1652
1653 pub(crate) struct Installed(());
1655
1656 impl Drop for Installed {
1657 fn drop(&mut self) {
1658 SCRIPT.with(|script| script.borrow_mut().take());
1659 }
1660 }
1661
1662 pub(crate) fn install(
1664 script: impl FnMut(Pid, Pid) -> Option<Result<KernelSignalState, Errno>> + 'static,
1665 ) -> Installed {
1666 SCRIPT.with(|slot| *slot.borrow_mut() = Some(Box::new(script)));
1667 Installed(())
1668 }
1669
1670 pub(super) fn scripted(pid: Pid, tid: Pid) -> Option<Result<KernelSignalState, Errno>> {
1671 SCRIPT.with(|slot| {
1672 slot.borrow_mut()
1673 .as_mut()
1674 .and_then(|script| script(pid, tid))
1675 })
1676 }
1677}
1678
1679pub(super) fn blocked_signal_mask() -> KernelSigset {
1680 let mut libc_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
1683 unsafe {
1684 libc::sigfillset(&mut libc_mask);
1685 libc::sigdelset(&mut libc_mask, reverie::PERF_EVENT_SIGNAL as i32);
1686 }
1687 (1..=KernelSigset::BITS as i32).fold(0, |mask, raw_signal| {
1688 if unsafe { libc::sigismember(&libc_mask, raw_signal) } == 1 {
1689 mask | (1_u64 << (raw_signal as u32 - 1))
1690 } else {
1691 mask
1692 }
1693 })
1694}
1695
1696fn reads_mask_in_place(config: &Config) -> bool {
1708 config.backend.requires_thread_directed_process_signals || config.in_guest_signal_delivery
1709}
1710
1711pub(super) async fn block_signals_for_disposition<G, T>(
1712 guest: &mut G,
1713 blocked_mask_addr: Addr<'_, KernelSigset>,
1714 old_mask_addr: AddrMut<'_, KernelSigset>,
1715) -> Result<KernelSigset, Error>
1716where
1717 G: Guest<Detcore<T>>,
1718 T: RecordOrReplay,
1719{
1720 let block_signals = syscalls::RtSigprocmask::new()
1721 .with_how(libc::SIG_SETMASK)
1722 .with_set((!reads_mask_in_place(guest.config())).then_some(blocked_mask_addr.cast()))
1723 .with_oldset(Some(old_mask_addr.cast()))
1724 .with_sigsetsize(KERNEL_SIGSET_SIZE);
1725 guest.inject_with_retry(block_signals).await?;
1726 let guest_mask: KernelSigset = guest.memory().read_value(old_mask_addr)?;
1727 if guest.config().backend.reports_child_exit_publication
1728 || crate::scheduler::discarded_at_send::records(guest.config())
1729 {
1730 crate::tool_global::set_saved_guest_sigmask(guest, Some(guest_mask)).await;
1735 }
1736 Ok(guest_mask)
1737}
1738
1739pub(super) async fn restore_signals_after_disposition<G, T>(
1740 guest: &mut G,
1741 old_mask_addr: AddrMut<'_, KernelSigset>,
1742) -> Result<(), Error>
1743where
1744 G: Guest<Detcore<T>>,
1745 T: RecordOrReplay,
1746{
1747 if !reads_mask_in_place(guest.config()) {
1748 let old_mask: Addr<'_, KernelSigset> = old_mask_addr.into();
1749 let restore_signals = syscalls::RtSigprocmask::new()
1750 .with_how(libc::SIG_SETMASK)
1751 .with_set(Some(old_mask.cast()))
1752 .with_oldset(None)
1753 .with_sigsetsize(KERNEL_SIGSET_SIZE);
1754 guest.inject_with_retry(restore_signals).await?;
1755 }
1756 if guest.config().backend.reports_child_exit_publication
1757 || crate::scheduler::discarded_at_send::records(guest.config())
1758 {
1759 crate::tool_global::set_saved_guest_sigmask(guest, None).await;
1760 }
1761 Ok(())
1762}
1763
1764async fn interrupted_child_wait_result<G, T, S>(
1765 guest: &mut G,
1766 call: S,
1767 disposition: WaitSignalDisposition,
1768) -> Result<i64, Error>
1769where
1770 G: Guest<Detcore<T>>,
1771 T: RecordOrReplay,
1772 S: SyscallInfo,
1773{
1774 if !guest
1775 .config()
1776 .backend
1777 .requires_thread_directed_process_signals
1778 {
1779 return Err(Errno::ERESTARTSYS.into());
1780 }
1781 if disposition == WaitSignalDisposition::Interrupt {
1782 return Err(Errno::EINTR.into());
1783 }
1784
1785 guest.tail_inject(call).await
1786}
1787
1788fn snapshot_process_group(pid: Pid) -> Result<libc::pid_t, Errno> {
1789 let pgrp = Process::new(pid.as_raw())
1790 .and_then(|process| process.stat())
1791 .map(|stat| stat.pgrp)
1792 .map_err(|_| Errno::ESRCH)?;
1793 if pgrp == 0 {
1794 Err(Errno::EOPNOTSUPP)
1795 } else {
1796 Ok(pgrp)
1797 }
1798}
1799
1800fn guest_fd_status_flags(pid: Pid, fd: libc::c_int) -> Result<libc::c_int, Errno> {
1801 let path = format!("/proc/{}/fdinfo/{}", pid.as_raw(), fd);
1802 let contents = std::fs::read_to_string(path).map_err(|_| Errno::EBADF)?;
1803 let flags = contents
1804 .lines()
1805 .find_map(|line| line.strip_prefix("flags:"))
1806 .map(str::trim)
1807 .ok_or(Errno::EINVAL)?;
1808 libc::c_int::from_str_radix(flags, 8).map_err(|_| Errno::EINVAL)
1809}
1810
1811#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1812enum FutexTimeout {
1813 Relative(u64),
1814 Absolute(LogicalTime),
1815}
1816
1817fn futex_timespec_is_valid(timeout: Timespec) -> bool {
1821 timeout.tv_sec >= 0 && (0..1_000_000_000).contains(&timeout.tv_nsec)
1822}
1823
1824const FUTEX_KTIME_MAX_NANOS: u64 = i64::MAX as u64;
1827
1828fn parse_futex_timeout(futex_op: i32, timeout: Timespec) -> Result<FutexTimeout, Errno> {
1829 if !futex_timespec_is_valid(timeout) {
1830 return Err(Errno::EINVAL);
1831 }
1832 let seconds = timeout.tv_sec as u64;
1833 let nanoseconds = timeout.tv_nsec as u64;
1834 let timeout_nanos = if seconds >= FUTEX_KTIME_MAX_NANOS / 1_000_000_000 {
1836 FUTEX_KTIME_MAX_NANOS
1837 } else {
1838 seconds * 1_000_000_000 + nanoseconds
1839 };
1840 if matches!(
1850 futex_op & libc::FUTEX_CMD_MASK,
1851 libc::FUTEX_WAIT_BITSET | libc::FUTEX_LOCK_PI | libc::FUTEX_LOCK_PI2
1852 ) {
1853 Ok(FutexTimeout::Absolute(LogicalTime::from_nanos(
1854 timeout_nanos,
1855 )))
1856 } else {
1857 Ok(FutexTimeout::Relative(timeout_nanos))
1858 }
1859}
1860
1861fn futex_deadline_clock(futex_op: i32) -> syscalls::ClockId {
1866 if futex_op & libc::FUTEX_CLOCK_REALTIME != 0
1867 || futex_op & libc::FUTEX_CMD_MASK == libc::FUTEX_LOCK_PI
1868 {
1869 syscalls::ClockId::CLOCK_REALTIME
1870 } else {
1871 syscalls::ClockId::CLOCK_MONOTONIC
1872 }
1873}
1874
1875fn rebase_absolute_timeout(
1876 deadline: LogicalTime,
1877 clock_now: LogicalTime,
1878 logical_now: LogicalTime,
1879) -> LogicalTime {
1880 logical_now + Duration::from_nanos(deadline.as_nanos().saturating_sub(clock_now.as_nanos()))
1881}
1882
1883fn absolute_timeout_uses_host_clock(
1884 deadline: LogicalTime,
1885 host_clock_now: LogicalTime,
1886 logical_now: LogicalTime,
1887) -> bool {
1888 deadline.as_nanos().abs_diff(host_clock_now.as_nanos())
1889 < deadline.as_nanos().abs_diff(logical_now.as_nanos())
1890}
1891
1892#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1894pub(crate) enum FutexDeadline {
1895 FromCall,
1897 Restart(Option<LogicalTime>),
1900}
1901
1902#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1905enum SchedYieldRoute {
1906 ChaosReprioritize,
1908 ReplayStrongYield,
1910 ChaosYieldEpoch,
1912 WeakYield,
1914 EndTimeslice,
1916}
1917
1918fn sched_yield_route(cfg: &Config) -> SchedYieldRoute {
1950 if cfg.chaos && cfg.max_timeslice.is_none() {
1951 SchedYieldRoute::ChaosReprioritize
1952 } else if !cfg.chaos && cfg.replay_preemptions_from.is_some() {
1953 SchedYieldRoute::ReplayStrongYield
1954 } else if cfg.chaos
1955 && cfg.replay_schedule_from.is_none()
1956 && cfg.backend.delivers_preemption_timer_events
1957 && cfg.sched_heuristic != SchedHeuristic::Random
1958 {
1959 SchedYieldRoute::ChaosYieldEpoch
1960 } else if cfg.chaos || cfg.replay_schedule_from.is_some() {
1961 SchedYieldRoute::WeakYield
1962 } else {
1963 SchedYieldRoute::EndTimeslice
1964 }
1965}
1966
1967impl<T: RecordOrReplay> Detcore<T> {
1968 async fn futex_timeout_deadline<G: Guest<Self>>(
1969 &self,
1970 guest: &mut G,
1971 futex_flags: i32,
1972 timeout: Option<Addr<'_, Timespec>>,
1973 ) -> Result<Option<LogicalTime>, Error> {
1974 let Some(timeout) = timeout else {
1975 return Ok(None);
1976 };
1977 let timeout = parse_futex_timeout(futex_flags, guest.memory().read_value(timeout)?)?;
1978 match timeout {
1979 FutexTimeout::Relative(nanos) => {
1980 let now = thread_observe_time(guest).await;
1981 Ok(Some(now + Duration::from_nanos(nanos)))
1982 }
1983 FutexTimeout::Absolute(deadline)
1984 if self.cfg.virtualize_time
1985 && !self.cfg.backend.guest_clock_reads_bypass_backend =>
1986 {
1987 Ok(Some(deadline))
1988 }
1989 FutexTimeout::Absolute(deadline) => {
1990 let clockid = futex_deadline_clock(futex_flags);
1991
1992 let mut stack = guest.stack().await;
1993 let clock_output = syscalls::TimespecMutPtr(stack.reserve());
1994 let _stack_guard = stack.commit()?;
1995 let clock_call = syscalls::ClockGettime::new()
1996 .with_clockid(clockid)
1997 .with_tp(Some(clock_output));
1998 if self.cfg.virtualize_time && self.cfg.backend.guest_clock_reads_bypass_backend {
1999 guest.inject(Syscall::from(clock_call)).await?;
2003 } else {
2004 self.record_or_replay(guest, clock_call).await?;
2005 }
2006 let clock_now = match parse_futex_timeout(
2007 libc::FUTEX_WAIT_BITSET,
2008 guest.memory().read_value(clock_output.0)?,
2009 )? {
2010 FutexTimeout::Absolute(time) => time,
2011 FutexTimeout::Relative(_) => unreachable!(),
2012 };
2013 let logical_now = thread_observe_time(guest).await;
2014 if self.cfg.virtualize_time
2015 && !absolute_timeout_uses_host_clock(deadline, clock_now, logical_now)
2016 {
2017 return Ok(Some(deadline));
2018 }
2019 Ok(Some(rebase_absolute_timeout(
2020 deadline,
2021 clock_now,
2022 logical_now,
2023 )))
2024 }
2025 }
2026 }
2027
2028 async fn futex_deadline<G: Guest<Self>>(
2031 &self,
2032 guest: &mut G,
2033 call: syscalls::Futex,
2034 deadline: FutexDeadline,
2035 ) -> Result<Option<LogicalTime>, Error> {
2036 match deadline {
2037 FutexDeadline::FromCall => {
2038 self.futex_timeout_deadline(guest, call.futex_op(), call.timeout())
2039 .await
2040 }
2041 FutexDeadline::Restart(deadline) => Ok(deadline),
2042 }
2043 }
2044
2045 fn promote_before_native_lifecycle<G: Guest<Self>>(&self, guest: &G) {
2055 let backend = &guest.config().backend;
2056 if self.cfg.sequentialize_threads
2057 && backend.promotes_open_files_before_native_lifecycle
2058 && !crate::syscalls::timerfd::detcore_runs_outside_the_guest(backend)
2059 {
2060 guest.thread_state().promote_shared_open_files().expect(
2061 "could not publish shared open file descriptions before a native fork or exec",
2062 );
2063 }
2064 }
2065
2066 fn share_procfs_sequences_before_copy<G: Guest<Self>>(&self, guest: &G) {
2076 if self.cfg.sequentialize_threads
2077 && !crate::syscalls::timerfd::detcore_runs_outside_the_guest(&guest.config().backend)
2078 {
2079 guest.thread_state().share_procfs_sequences();
2080 }
2081 }
2082
2083 async fn user_namespace_owner_may_differ<G: Guest<Self>>(&self, guest: &mut G) -> bool {
2102 const OVERFLOW_ID: i64 = 65534;
2105 let uid = guest.inject(syscalls::Getuid::new()).await;
2106 let euid = guest.inject(syscalls::Geteuid::new()).await;
2107 !matches!((uid, euid), (Ok(uid), Ok(euid)) if uid == euid && uid != OVERFLOW_ID)
2108 }
2109
2110 pub async fn handle_clone_family<G: Guest<Self>>(
2112 &self,
2113 guest: &mut G,
2114 clone_family: syscalls::family::CloneFamily,
2115 ) -> Result<i64, Error> {
2116 if let syscalls::family::CloneFamily::Clone3(clone) = &clone_family
2117 && let Some(refusal) = self.clone3_set_tid_refusal(guest, clone).await
2118 {
2119 return refusal;
2120 }
2121 let user_limit = matches!(clone_family, syscalls::family::CloneFamily::Clone3(_))
2127 .then(|| guest.user_address_limit().ok().map(|limit| limit.max_end))
2128 .flatten();
2129 if linux_refuses_clone_flags(&guest.memory(), &clone_family, user_limit) {
2130 return Err(Errno::EINVAL.into());
2131 }
2132 let (flags, exit_signal, child_tid) = match &clone_family {
2137 syscalls::family::CloneFamily::Clone3(clone) => {
2138 let memory = guest.memory();
2139 (
2140 clone3_field(&memory, clone, 0)
2141 .map_or_else(CloneFlags::empty, CloneFlags::from_bits_truncate),
2142 clone3_field(&memory, clone, 32).map_or(0, |signal| signal as libc::c_int),
2143 clone3_field(&memory, clone, 16).map_or(0, |address| address as usize),
2144 )
2145 }
2146 syscalls::family::CloneFamily::Clone(clone) => (
2147 clone_family.flags(&guest.memory()),
2148 (clone.flags().bits() & 0xff) as libc::c_int,
2149 clone_family.child_tid(&guest.memory()),
2150 ),
2151 #[cfg(not(target_arch = "aarch64"))]
2152 _ => (
2153 clone_family.flags(&guest.memory()),
2154 libc::SIGCHLD,
2155 clone_family.child_tid(&guest.memory()),
2156 ),
2157 };
2158 let ctid = child_tid_clear_address(flags, child_tid);
2159 let pidfd_address = guest
2163 .config()
2164 .sequentialize_threads
2165 .then(|| clone_pidfd_address(&clone_family, &guest.memory(), flags))
2166 .flatten();
2167 let is_vfork = flags.contains(CloneFlags::CLONE_VFORK);
2171
2172 if is_vfork && let Some(refused) = super::helpers::kernel_run_entry_refusal(guest)? {
2177 return Err(refused);
2178 }
2179
2180 let user_namespace_owner_may_differ = flags.contains(CloneFlags::CLONE_NEWUSER)
2186 && !flags.contains(CloneFlags::CLONE_THREAD)
2187 && !self.cfg.recordreplay_modes
2188 && self.user_namespace_owner_may_differ(guest).await;
2189
2190 let clone_plan = self
2193 .plan_clone_id(guest, &Syscall::from(clone_family))
2194 .await?;
2195
2196 let ts = guest.thread_state_mut();
2197 assert_eq!(ts.clone_flags, None);
2198 assert!(ts.pending_vfork.is_none());
2199 ts.clone_flags = Some(flags);
2200
2201 let parent_dettid = ts.dettid;
2202 let child_priority_entropy = if is_vfork
2203 && self.cfg.chaos
2204 && self.cfg.replay_preemptions_from.is_none()
2205 && self.cfg.replay_schedule_from.is_none()
2206 {
2207 let mut parent_chaos_prng = ts.chaos_prng.clone();
2208 Some(parent_chaos_prng.next_u64())
2209 } else {
2210 None
2211 };
2212 let blocking_child_op_id = ExternalOpId::new(parent_dettid, ts.stats.syscall_count);
2213 if is_vfork {
2214 ts.pending_vfork = Some(PendingVfork {
2215 parent_dettid,
2216 op: blocking_child_op_id,
2217 parent_detpid: ts.detpid.expect("detpid unset"),
2218 child_tid_addr: ctid,
2219 flags,
2220 exit_signal,
2221 child_priority_entropy,
2222 user_namespace_owner_may_differ,
2223 });
2224 }
2225
2226 let exit_signals = clone_host_timed_signals(flags, exit_signal);
2231 if exit_signals != 0 && guest.config().backend.host_timed_signals_are_physical {
2232 record_host_timed_signals(guest, HostTimedSignalScope::Caller, exit_signals).await;
2233 }
2234
2235 if !flags.contains(CloneFlags::CLONE_THREAD) {
2236 self.promote_before_native_lifecycle(guest);
2237 self.share_procfs_sequences_before_copy(guest);
2238 }
2239
2240 super::signal_shadow::before_clone(guest, flags.bits()).await;
2243
2244 trace!("[detcore, dtid {}] parent invoking clone.", parent_dettid);
2245
2246 if is_vfork && self.cfg.sequentialize_threads {
2250 let mut resources = Resources::new(parent_dettid);
2251 resources.insert(
2252 ResourceID::BlockingVfork(blocking_child_op_id),
2253 Permission::RW,
2254 );
2255 resources.fyi("clone_vfork");
2256 resource_request(guest, resources).await;
2257 }
2258
2259 let call = Syscall::from(clone_family);
2260 let sysno = call.number();
2261 let (maybe_res, untraced_refusal) = match clone_plan {
2266 super::chosen_ids::ClonePlan::Kernel | super::chosen_ids::ClonePlan::GuestChosen => {
2267 match inject_traced_clone(guest, clone_family, flags).await {
2268 CloneInjection::Ran(result) => (result, None),
2269 CloneInjection::Refused(reason) => (Err(Errno::ENOMEM), Some(reason)),
2272 }
2273 }
2274 _ => (
2275 self.inject_clone_planned(guest, call, clone_plan, is_vfork)
2276 .await,
2277 None,
2278 ),
2279 };
2280 let refusal = if maybe_res.is_err() && untraced_refusal.is_none() {
2283 guest.take_unsupported_refusal()
2284 } else {
2285 None
2286 };
2287
2288 if is_vfork && self.cfg.sequentialize_threads {
2289 let mut resources = Resources::new(parent_dettid);
2290 if maybe_res.is_err() {
2291 resources.insert(
2297 ResourceID::VforkFailed(blocking_child_op_id),
2298 Permission::RW,
2299 );
2300 resources.fyi("clone_vfork_failed");
2301 } else {
2302 resources.insert(
2303 ResourceID::BlockedExternalContinue(blocking_child_op_id),
2304 Permission::RW,
2305 );
2306 resources.fyi("clone_vfork");
2307 }
2308 resource_request(guest, resources).await;
2309 }
2310
2311 let ts = guest.thread_state_mut();
2312 ts.clone_flags = None; ts.pending_vfork = None;
2314
2315 let source = if refusal.is_some() {
2323 "refused by the backend"
2324 } else {
2325 "refused"
2326 };
2327 let diagnostic = refusal
2328 .map(|refusal| refusal.diagnostic.to_string())
2329 .or(untraced_refusal);
2330 if let (Some(diagnostic), Err(errno)) = (diagnostic, &maybe_res) {
2331 let named = CloneFlags::from_bits_retain(flags.bits() & !0xff);
2332 crate::syscalls::helpers::report_refusal_reason(
2333 self.cfg.panic_on_unsupported_syscalls,
2334 &format!(
2335 "[dtid {parent_dettid}] {sysno} {source}: {diagnostic} (flags {named:?}, \
2336 exit signal {exit_signal}). No child was created."
2337 ),
2338 );
2339 if !self.cfg.panic_on_unsupported_syscalls {
2340 crate::tool_global::report_unsupported_syscall(guest, sysno).await;
2341 }
2342 return self
2343 .refuse_unserviceable_operation_because(guest, sysno, *errno, &diagnostic)
2344 .await;
2345 }
2346
2347 let res = maybe_res?;
2348
2349 if !flags.contains(CloneFlags::CLONE_THREAD) {
2350 guest.thread_state().forget_flock_modes();
2354 }
2355
2356 if is_vfork
2359 && self.cfg.chaos
2360 && self.cfg.replay_preemptions_from.is_none()
2361 && self.cfg.replay_schedule_from.is_none()
2362 {
2363 let _ = guest
2364 .thread_state_mut()
2365 .chaos_prng_next_u64("child_priority");
2366 }
2367
2368 let child_tid = Pid::from_raw(res as i32);
2369 let child_dettid = DetTid::from_raw(child_tid.into()); trace!(
2371 "[detcore] dtid {} cloned, continuing parent + register new thread.",
2372 child_dettid
2373 );
2374
2375 if !is_vfork {
2376 create_child_thread(guest, child_dettid, ctid, Some(flags), exit_signal, None).await;
2377 if user_namespace_owner_may_differ {
2381 crate::tool_global::note_user_namespace_owner_may_differ(guest, child_dettid).await;
2382 }
2383 }
2384
2385 if let Some(address) = pidfd_address {
2386 self.register_clone_pidfd(guest, address, child_dettid)
2387 .await;
2388 }
2389
2390 {
2391 let parent_pedigree = &mut guest.thread_state_mut().pedigree;
2393 let child_pedigree = parent_pedigree.fork_mut();
2394 debug!(
2395 "[dtid {}] after creating child thread (tid {}, pedigree {}) parents pedigree becomes {}",
2396 parent_dettid, child_dettid, child_pedigree, parent_pedigree,
2397 );
2398 }
2399
2400 if guest.thread_state().nested_pid_namespace {
2404 let refusal = match crate::tool_global::local_pid(guest, child_dettid).await {
2405 Ok(Some(local)) => return Ok(local as i64),
2406 Ok(None) => {
2407 format!("child {child_dettid} has no pid recorded in its creator's namespace")
2408 }
2409 Err(reason) => reason,
2410 };
2411 tracing::error!(
2412 "[detcore, dtid {}] the pid a clone in a nested PID namespace returns cannot be translated: {} (https://github.com/rrnewton/hermit/issues/4090)",
2413 parent_dettid,
2414 refusal,
2415 );
2416 return self
2417 .refuse_unserviceable_operation(
2418 guest,
2419 reverie::syscalls::Sysno::clone,
2420 Errno::ENOSYS,
2421 )
2422 .await;
2423 }
2424 Ok(child_dettid.as_raw() as i64)
2425 }
2426
2427 pub async fn handle_set_tid_address<G: Guest<Self>>(
2434 &self,
2435 guest: &mut G,
2436 call: syscalls::SetTidAddress,
2437 ) -> Result<i64, Error> {
2438 let address = call.tidptr().map_or(0, |pointer| pointer.as_raw());
2439 crate::reserved_input::expose_local_overlap(
2440 guest,
2441 address as u64,
2442 4,
2443 "clear-tid-reserved-reference",
2444 )?;
2445 let result = self
2446 .record_or_replay(guest, Syscall::SetTidAddress(call))
2447 .await?;
2448 if guest.config().sequentialize_threads {
2449 set_child_tid_address(guest, address).await;
2450 }
2451 trace!(
2452 "[detcore, dtid {}] child TID clear address registered: {address:#x}",
2453 guest.thread_state().dettid,
2454 );
2455 Ok(result)
2456 }
2457
2458 pub async fn handle_set_robust_list<G: Guest<Self>>(
2474 &self,
2475 guest: &mut G,
2476 call: syscalls::SetRobustList,
2477 ) -> Result<i64, Error> {
2478 let head = call.head().map(AddrMut::as_raw);
2479 let len = call.len();
2480 if let Some(head) = head {
2481 crate::reserved_input::expose_local_overlap(
2482 guest,
2483 head as u64,
2484 len as u64,
2485 "robust-list-reserved-reference",
2486 )?;
2487 }
2488 let res = self
2489 .record_or_replay(guest, Syscall::SetRobustList(call))
2490 .await?;
2491 let recorded = match head {
2494 None => None,
2496 Some(_) if len != robust_list::ROBUST_LIST_HEAD_LEN => None,
2502 Some(addr) => Some(addr),
2503 };
2504 guest.thread_state_mut().record_robust_list_head(recorded);
2505 trace!(
2506 "[detcore, dtid {}] robust-list head registered: {:?}",
2507 guest.thread_state().dettid,
2508 recorded,
2509 );
2510 Ok(res)
2511 }
2512
2513 async fn run_robust_list_owner_death<G: Guest<Self>>(&self, guest: &mut G) {
2532 let Some(path) = self.robust_owner_death_path() else {
2533 return;
2534 };
2535 let Some(head) = guest.thread_state().robust_list_head else {
2536 return;
2537 };
2538 let dettid = guest.thread_state().dettid;
2539 let owner_tid = dettid.as_raw() as u32;
2552
2553 let mut marked = HashMap::new();
2554 let mut effects = GuestRobustEffects::<'_, G, T> {
2555 guest,
2556 dettid,
2557 staged_wakes: None,
2558 marked: &mut marked,
2559 tool: PhantomData,
2560 };
2561 let outcome = robust_list::exit_robust_list(&mut effects, head, owner_tid).await;
2562 if path == OwnerDeathPath::Barrier {
2563 guest.thread_state().mark_robust_list_walked_by_tool();
2564 }
2565 if outcome.head_unreadable {
2566 trace!(
2567 "[detcore, dtid {}] unreadable robust-list head at {:#x}; no owner-death wakeups",
2568 dettid, head,
2569 );
2570 } else if outcome.aborted || outcome.next_faulted || outcome.truncated {
2571 trace!(
2572 "[detcore, dtid {}] robust-list walk stopped early after {} entr(ies): {:?}",
2573 dettid, outcome.entries_visited, outcome,
2574 );
2575 }
2576 }
2577
2578 pub(crate) async fn stage_thread_group_robust_list_wakes<G: Guest<Self>>(
2591 &self,
2592 guest: &mut G,
2593 reason: RobustListExit,
2594 ) {
2595 match self.robust_owner_death_path() {
2596 None | Some(OwnerDeathPath::Barrier) => {}
2597 Some(OwnerDeathPath::Kernel) => {
2598 let staged = self
2599 .read_thread_group_robust_list_wakes(guest, OwnerDeath::Exit)
2600 .await;
2601 guest.thread_state().stage_robust_list_wakes(reason, staged);
2602 }
2603 }
2604 }
2605
2606 async fn run_thread_group_robust_list_owner_death<G: Guest<Self>>(
2612 &self,
2613 guest: &mut G,
2614 heads: Vec<RobustListOwner>,
2615 ) {
2616 let mut marked = HashMap::new();
2619 for RobustListOwner {
2620 dettid: owner,
2621 head,
2622 } in heads
2623 {
2624 let mut effects = GuestRobustEffects::<'_, G, T> {
2625 guest,
2626 dettid: owner,
2627 staged_wakes: None,
2628 marked: &mut marked,
2629 tool: PhantomData,
2630 };
2631 let outcome =
2632 robust_list::exit_robust_list(&mut effects, head, owner.as_raw() as u32).await;
2633 if outcome.head_unreadable || outcome.aborted {
2634 trace!(
2635 "[detcore, dtid {}] robust-list owner death stopped early: {:?}",
2636 owner, outcome,
2637 );
2638 }
2639 }
2640 }
2641
2642 fn robust_owner_death_path(&self) -> Option<OwnerDeathPath> {
2650 owner_death_path(&self.cfg)
2651 }
2652
2653 pub(crate) async fn record_sigkill_robust_lists<G: Guest<Self>>(
2668 &self,
2669 guest: &mut G,
2670 token: u64,
2671 ) {
2672 if self.robust_owner_death_path() != Some(OwnerDeathPath::Kernel) {
2673 return;
2674 }
2675 let mut lists = Vec::new();
2676 for (tid, mm) in crate::tool_global::sigkill_dying_threads(guest, token).await {
2677 if let Some(list) = robust_list::record_robust_list(tid.as_raw()).await {
2678 lists.push((tid, mm, list));
2679 }
2680 }
2681 if !lists.is_empty() {
2682 crate::tool_global::record_sigkill_robust_lists(guest, token, lists).await;
2683 }
2684 }
2685
2686 pub(crate) async fn sigkill_robust_list_wakes<R: GlobalRPC<GlobalState>>(
2697 &self,
2698 global_state: &R,
2699 thread_state: &ThreadState<T::ThreadState>,
2700 exit_signal: Option<i32>,
2701 ) {
2702 if exit_signal != Some(libc::SIGKILL)
2703 || self.robust_owner_death_path() != Some(OwnerDeathPath::Kernel)
2704 || thread_state.has_matching_robust_list_exit(exit_signal)
2706 {
2707 return;
2708 }
2709 let time = thread_state.thread_logical_time.clone();
2710 let mm = thread_state.mm_id;
2711 let Some(list) =
2712 crate::tool_global::take_sigkill_robust_list(time.clone(), global_state, mm).await
2713 else {
2714 return;
2715 };
2716 let dettid = thread_state.dettid;
2717 let (futexes, outcome) =
2720 robust_list::owner_death_wakes_in_snapshot(&list, dettid.as_raw() as u32, |address| {
2721 thread_state.futex_id(address, false)
2722 })
2723 .await;
2724 if outcome.head_unreadable || outcome.aborted || outcome.next_faulted || outcome.truncated {
2725 trace!(
2726 "[detcore, dtid {}] SIGKILLed thread's robust-list walk stopped early: {:?}",
2727 dettid, outcome,
2728 );
2729 }
2730 let wakes: Vec<(DetTid, RobustListWake)> = futexes
2731 .into_iter()
2732 .map(|futex| (dettid, RobustListWake { futex }))
2733 .collect();
2734 let identities: Vec<FutexID> = wakes.iter().map(|(_, wake)| wake.futex).collect();
2735 let counts =
2736 crate::tool_global::robust_list_wakes_after_exit(time, global_state, mm, wakes).await;
2737 for (futex, count) in identities.into_iter().zip(counts) {
2739 info!(
2740 "[detcore, dtid {}] robust-list owner death woke {} waiter(s) on futex {:?} after physical exit",
2741 dettid, count, futex,
2742 );
2743 }
2744 }
2745
2746 pub async fn exec_robust_list_wakes<G: Guest<Self>>(
2753 &self,
2754 guest: &mut G,
2755 ) -> Vec<(DetTid, FutexID)> {
2756 if self.robust_owner_death_path().is_none() {
2757 return Vec::new();
2758 }
2759 let wakes = self
2760 .read_thread_group_robust_list_wakes(guest, OwnerDeath::Exec)
2761 .await;
2762 crate::tool_local::flatten_exec_robust_list_wakes(wakes)
2763 .into_iter()
2764 .map(|(owner, wake)| (owner, wake.futex))
2765 .collect()
2766 }
2767
2768 async fn read_thread_group_robust_list_wakes<G: Guest<Self>>(
2781 &self,
2782 guest: &mut G,
2783 death: OwnerDeath,
2784 ) -> Vec<(DetTid, Vec<RobustListWake>)> {
2785 let caller = guest.thread_state().dettid;
2786 let tgid = guest.thread_state().detpid.expect("detpid unset");
2787 let mut heads: Vec<(DetTid, usize)> = guest
2792 .thread_state()
2793 .robust_list_owners_for_exit_group()
2794 .into_iter()
2795 .map(|owner| (owner.dettid, owner.head))
2796 .collect();
2797 if death == OwnerDeath::Exec {
2798 heads.sort_by_key(|(owner, _)| *owner == caller);
2804 }
2805 let mut staged = Vec::with_capacity(heads.len());
2806 let mut marked = HashMap::new();
2807 for (owner, head) in heads {
2808 let walked_as = if death == OwnerDeath::Exec && owner == caller {
2809 tgid.as_raw()
2810 } else {
2811 owner.as_raw()
2812 };
2813 let mut wakes = Vec::new();
2814 let mut effects = GuestRobustEffects::<'_, G, T> {
2815 guest,
2816 dettid: owner,
2817 staged_wakes: Some(&mut wakes),
2818 marked: &mut marked,
2819 tool: PhantomData,
2820 };
2821 let outcome = robust_list::exit_robust_list(&mut effects, head, walked_as as u32).await;
2822 if outcome.head_unreadable || outcome.aborted {
2823 trace!(
2824 "[detcore, dtid {}] could not stage complete robust-list owner-death effects: {:?}",
2825 owner, outcome,
2826 );
2827 }
2828 staged.push((owner, wakes));
2829 }
2830 staged
2831 }
2832
2833 pub async fn handle_exit<G: Guest<Self>>(
2835 &self,
2836 guest: &mut G,
2837 call: syscalls::Exit,
2838 ) -> Result<i64, Error> {
2839 crate::guest_log::flush_unsent();
2841 if self.cfg.backend.reports_kernel_child_exit_signals {
2847 let uid = real_uid(guest.tid().as_raw()).unwrap_or_else(|| unsafe { libc::getuid() });
2848 crate::tool_global::child_exit_sigchld(
2849 guest,
2850 crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::RawExitStatus {
2851 code: libc::CLD_EXITED,
2852 status: call.status() & 0xff,
2853 uid,
2854 },
2855 )
2856 .await;
2857 }
2858 let request = guest.thread_state().mk_request(
2859 ResourceID::Exit {
2860 group: false,
2861 process: guest.thread_state().detpid.expect("detpid unset"),
2862 mm: guest.thread_state().mm_id,
2863 },
2864 Permission::RW,
2865 );
2866 resource_request(guest, request).await;
2867 self.run_robust_list_owner_death(guest).await;
2868 let state = guest.thread_state();
2873 if self.cfg.sequentialize_threads
2874 && state
2875 .detpid
2876 .is_some_and(|pid| pid.as_raw() == state.dettid.as_raw())
2877 && crate::tool_global::retire_exited_leader(guest).await
2878 && self.cfg.replay_data.is_some()
2879 {
2880 let message = format!(
2885 "hermit: thread-group leader {} exited while its threads run on, which record \
2886 and replay cannot yet capture, so the run is refused \
2887 (https://github.com/rrnewton/hermit/issues/4244)",
2888 guest.thread_state().dettid
2889 );
2890 tracing::error!("{message}");
2891 let _ = std::io::Write::write_fmt(
2892 &mut crate::util::RetryingStderr,
2893 format_args!("{message}\n"),
2894 );
2895 crate::tool_global::unrecoverable_shutdown(
2896 guest,
2897 detcore_model::HERMIT_POLICY_REFUSAL_EXIT,
2898 )
2899 .await;
2900 }
2901 self.release_table_at_exit(guest, false).await;
2905 guest.tail_inject(call).await
2907 }
2908
2909 pub async fn handle_exit_group<G: Guest<Self>>(
2911 &self,
2912 guest: &mut G,
2913 call: syscalls::ExitGroup,
2914 ) -> Result<i64, Error> {
2915 crate::reserved_input::before_exit_group(guest)?;
2916 crate::guest_log::flush_unsent();
2919 if self.cfg.backend.reports_kernel_child_exit_signals {
2923 let uid = real_uid(guest.tid().as_raw()).unwrap_or_else(|| unsafe { libc::getuid() });
2924 crate::tool_global::child_exit_sigchld(
2925 guest,
2926 crate::scheduler::child_exit_sigchld::ChildExitSigchldControl::ExitStatus {
2927 code: libc::CLD_EXITED,
2928 status: call.status() & 0xff,
2929 uid,
2930 },
2931 )
2932 .await;
2933 }
2934 let barrier = self.robust_owner_death_path() == Some(OwnerDeathPath::Barrier);
2952 if barrier {
2953 guest.thread_state().file_exit_group_for_robust_lists();
2954 }
2955 let request = guest.thread_state().mk_request(
2956 ResourceID::Exit {
2957 group: true,
2958 process: guest.thread_state().detpid.expect("detpid unset"),
2959 mm: guest.thread_state().mm_id,
2960 },
2961 Permission::RW,
2962 );
2963 resource_request(guest, request).await;
2964 if barrier {
2965 guest.thread_state().begin_exit_group_robust_walk();
2966 let owners = guest.thread_state().robust_list_owners_for_exit_group();
2967 self.run_thread_group_robust_list_owner_death(guest, owners)
2968 .await;
2969 guest.thread_state().finish_exit_group_robust_walk();
2970 } else {
2971 self.stage_thread_group_robust_list_wakes(guest, RobustListExit::ExitGroup)
2972 .await
2973 }
2974 self.release_table_at_exit(guest, true).await;
2980 guest.tail_inject(call).await
2982 }
2983
2984 pub(crate) async fn end_thread_group_before_fatal_delivery<G: Guest<Self>>(
3020 &self,
3021 guest: &mut G,
3022 signal: SigWrapper,
3023 ) -> bool {
3024 if !(self.cfg.sequentialize_threads
3025 && self.cfg.backend.requires_thread_directed_process_signals)
3026 || !signal_default_action_ends_the_process(signal)
3027 {
3028 return false;
3029 }
3030 match self.signal_is_left_at_default(guest, signal).await {
3031 Ok(true) => {}
3032 Ok(false) => return false,
3033 Err(error) => {
3034 tracing::warn!(
3035 "[detcore, dtid {}] cannot read the disposition of {}: {}",
3036 guest.thread_state().dettid,
3037 signal,
3038 error
3039 );
3040 return false;
3041 }
3042 }
3043 info!(
3044 "[detcore, dtid {}] {} ends its thread group by default",
3045 guest.thread_state().dettid,
3046 signal
3047 );
3048 let barrier = self.robust_owner_death_path() == Some(OwnerDeathPath::Barrier);
3049 if barrier {
3050 guest.thread_state().file_exit_group_for_robust_lists();
3051 }
3052 let dettid = guest.thread_state().dettid;
3053 if !hold_turn_for_group_exit(guest, dettid).await {
3054 tracing::warn!(
3055 "[detcore, dtid {}] the scheduler did not hold the turn for the group exit of {}",
3056 dettid,
3057 signal
3058 );
3059 }
3060 let request = guest.thread_state().mk_request(
3061 ResourceID::Exit {
3062 group: true,
3063 process: guest.thread_state().detpid.expect("detpid unset"),
3064 mm: guest.thread_state().mm_id,
3065 },
3066 Permission::RW,
3067 );
3068 resource_request(guest, request).await;
3069 match self.signal_is_left_at_default(guest, signal).await {
3070 Ok(true) => {}
3071 changed => {
3072 info!(
3073 "[detcore, dtid {}] the action for {} changed after the delivery decision \
3074 ({:?}); restoring SIG_DFL in the dying process",
3075 guest.thread_state().dettid,
3076 signal,
3077 changed.map(|default| !default)
3078 );
3079 if let Err(error) = self.restore_default_action(guest, signal).await {
3080 tracing::warn!(
3081 "[detcore, dtid {}] cannot restore SIG_DFL for {}: {}",
3082 guest.thread_state().dettid,
3083 signal,
3084 error
3085 );
3086 }
3087 }
3088 }
3089 if barrier {
3090 guest.thread_state().begin_exit_group_robust_walk();
3091 let owners = guest.thread_state().robust_list_owners_for_exit_group();
3092 self.run_thread_group_robust_list_owner_death(guest, owners)
3093 .await;
3094 guest.thread_state().finish_exit_group_robust_walk();
3095 }
3096 true
3097 }
3098
3099 async fn restore_default_action<G: Guest<Self>>(
3101 &self,
3102 guest: &mut G,
3103 signal: SigWrapper,
3104 ) -> Result<(), Error> {
3105 let default = KernelSigaction {
3106 handler: libc::SIG_DFL as u64,
3107 flags: 0,
3108 restorer: 0,
3109 mask: 0,
3110 };
3111 let mut stack = guest.stack().await;
3112 let action_addr = stack.push(default);
3113 let _guard = stack.commit()?;
3114 let call = syscalls::RtSigaction::new()
3115 .with_signum(signal.raw())
3116 .with_action(Some(action_addr.cast()))
3117 .with_old_action(None)
3118 .with_sigsetsize(std::mem::size_of::<u64>());
3119 guest.inject_with_retry(call).await?;
3120 super::signal_shadow::give_up(
3123 guest,
3124 "Detcore reset an action to the default before a fatal delivery",
3125 );
3126 Ok(())
3127 }
3128
3129 async fn signal_is_left_at_default<G: Guest<Self>>(
3132 &self,
3133 guest: &mut G,
3134 signal: SigWrapper,
3135 ) -> Result<bool, Error> {
3136 let mut stack = guest.stack().await;
3137 let action_addr = stack.reserve::<KernelSigaction>();
3138 let _guard = stack.commit()?;
3139 let call = syscalls::RtSigaction::new()
3140 .with_signum(signal.raw())
3141 .with_action(None)
3142 .with_old_action(Some(action_addr.cast()))
3143 .with_sigsetsize(std::mem::size_of::<u64>());
3144 guest.inject_with_retry(call).await?;
3145 let action: KernelSigaction = guest.memory().read_value(action_addr)?;
3146 Ok(action.handler == libc::SIG_DFL as u64)
3147 }
3148
3149 pub async fn handle_futex<G: Guest<Self>>(
3151 &self,
3152 guest: &mut G,
3153 call: syscalls::Futex,
3154 ) -> Result<i64, Error> {
3155 self.handle_futex_from(guest, call, FutexDeadline::FromCall)
3156 .await
3157 }
3158
3159 pub(crate) async fn handle_futex_from<G: Guest<Self>>(
3164 &self,
3165 guest: &mut G,
3166 call: syscalls::Futex,
3167 deadline: FutexDeadline,
3168 ) -> Result<i64, Error> {
3169 let dettid = guest.thread_state().dettid;
3170 if self.cfg.sequentialize_threads
3174 && !matches!(self.cfg.debug_futex_mode, BlockingMode::External)
3175 {
3176 match classify_futex_command(call.futex_op()) {
3177 FutexCommand::Served => {}
3178 FutexCommand::NoSys => return Err(Error::Errno(Errno::ENOSYS)),
3179 FutexCommand::RequeuePi(name) => {
3180 return self
3181 .refuse_futex_command(
3182 guest,
3183 name,
3184 "Detcore does not emulate requeue-PI futexes (PI condition variables)",
3185 )
3186 .await;
3187 }
3188 }
3189 }
3190 if self.cfg.sequentialize_threads
3195 && !matches!(self.cfg.debug_futex_mode, BlockingMode::External)
3196 {
3197 let command = call.futex_op() & libc::FUTEX_CMD_MASK;
3198 if let Some(name) = pi_lock_command_name(command) {
3202 if matches!(self.cfg.debug_futex_mode, BlockingMode::Polling) {
3203 return self
3204 .refuse_futex_command(guest, name, POLLING_REFUSAL)
3205 .await;
3206 }
3207 return if command == libc::FUTEX_UNLOCK_PI {
3209 self.handle_futex_unlock_pi(guest, call).await
3210 } else {
3211 self.handle_futex_lock_pi(guest, call, command).await
3212 };
3213 }
3214 if matches!(command, libc::FUTEX_WAKE | libc::FUTEX_WAKE_BITSET)
3218 && matches!(self.cfg.debug_futex_mode, BlockingMode::Precise)
3219 {
3220 return self.handle_futex_wake(guest, call, command).await;
3221 }
3222 let name = match command {
3223 libc::FUTEX_REQUEUE => Some("FUTEX_REQUEUE"),
3224 libc::FUTEX_CMP_REQUEUE => Some("FUTEX_CMP_REQUEUE"),
3225 libc::FUTEX_WAKE_OP => Some("FUTEX_WAKE_OP"),
3226 _ => None,
3227 };
3228 if let Some(name) = name {
3229 if matches!(self.cfg.debug_futex_mode, BlockingMode::Polling) {
3232 return self
3233 .refuse_futex_command(guest, name, POLLING_REFUSAL)
3234 .await;
3235 }
3236 return if command == libc::FUTEX_WAKE_OP {
3237 self.handle_futex_wake_op(guest, call).await
3239 } else {
3240 self.handle_futex_requeue(guest, call, command).await
3242 };
3243 }
3244 }
3245 let ptr = match call.uaddr() {
3246 None => {
3247 return Ok(guest.inject(call).await?);
3249 }
3250 Some(x) => x,
3251 };
3252 if self.cfg.sequentialize_threads
3253 && !matches!(self.cfg.debug_futex_mode, BlockingMode::External)
3254 && !AddrMut::as_raw(ptr).is_multiple_of(std::mem::size_of::<u32>())
3255 {
3256 return Err(Error::Errno(Errno::EINVAL));
3258 }
3259 if !self.cfg.sequentialize_threads {
3260 return crate::syscalls::helpers::inject_keeping_kernel_restart(guest, call).await;
3261 }
3262 let polling = match self.cfg.debug_futex_mode {
3263 BlockingMode::External => {
3264 return self.record_or_replay_blocking(guest, call.into()).await;
3265 }
3266 BlockingMode::Precise => false,
3267 BlockingMode::Polling => true,
3268 };
3269 let init_val = match call.futex_op() & libc::FUTEX_CMD_MASK {
3280 command @ (libc::FUTEX_WAIT | libc::FUTEX_WAIT_BITSET) => {
3281 if matches!(deadline, FutexDeadline::FromCall)
3282 && let Some(timeout) = call.timeout()
3283 {
3284 let timeout = read_user_timespec(guest, timeout.as_raw())?;
3285 if !futex_timespec_is_valid(timeout) {
3286 return Err(Error::Errno(Errno::EINVAL));
3287 }
3288 }
3289 if command == libc::FUTEX_WAIT_BITSET && call.val3() == 0 {
3290 return Err(Error::Errno(Errno::EINVAL));
3291 }
3292 read_futex_word(guest, AddrMut::as_raw(ptr))?
3293 }
3294 _ => 0,
3295 };
3296 trace!(
3297 "[detcore, dtid {}] futex op with memory address containing value {}",
3298 &dettid, init_val
3299 );
3300 if polling {
3301 self.handle_futex_polling(guest, call, init_val, deadline)
3302 .await
3303 } else {
3304 self.handle_futex_blocking(guest, call, init_val, deadline)
3305 .await
3306 }
3307 }
3308
3309 pub(crate) async fn handle_futex_blocking<G: Guest<Self>>(
3313 &self,
3314 guest: &mut G,
3315 call: syscalls::Futex,
3316 init_val: i32,
3317 deadline: FutexDeadline,
3318 ) -> Result<i64, Error> {
3319 let ptr = call.uaddr().unwrap();
3320 let futexid = guest.thread_state().futex_id(
3321 AddrMut::as_raw(ptr),
3322 call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0,
3323 );
3324 let futex_op = call.futex_op() & libc::FUTEX_CMD_MASK;
3325 let bitset = match futex_op {
3326 libc::FUTEX_WAKE_BITSET | libc::FUTEX_WAIT_BITSET => call.val3() as u32,
3327 _ => u32::MAX,
3328 };
3329 if bitset == 0 {
3330 return Err(Error::Errno(Errno::EINVAL));
3331 }
3332 let dettid = guest.thread_state().dettid;
3333 match futex_op {
3334 libc::FUTEX_WAIT | libc::FUTEX_WAIT_BITSET => {
3335 if init_val != call.val() {
3336 info!(
3337 "[detcore, dtid {}] Futex wait running immediately because it will fizzle ({} != {}).",
3338 &dettid,
3339 init_val,
3340 call.val()
3341 );
3342 Err(Error::Errno(Errno::EAGAIN))
3343 } else {
3344 let maybe_timeout_lt = self.futex_deadline(guest, call, deadline).await?;
3345 let gated = guest.config().in_guest_signal_delivery;
3349 let signal_interruption = guest
3350 .config()
3351 .backend
3352 .supports_blocked_wait_signal_interruption
3353 || gated;
3354 let mut rewait = false;
3356 let mut committed_wake: KernelSigset = 0;
3360 let mut requested = false;
3364 let res = loop {
3365 let signal_watch = match self
3407 .futex_wait_signal_watch(
3408 guest,
3409 call,
3410 gated,
3411 signal_interruption,
3412 &mut committed_wake,
3413 )
3414 .await
3415 {
3416 Ok(watch) => watch,
3417 Err(error) => break Err(error),
3418 };
3419 if rewait {
3420 let observed = match read_futex_word(guest, AddrMut::as_raw(ptr)) {
3429 Ok(observed) => observed,
3430 Err(error) => break Err(error),
3431 };
3432 if observed != call.val() {
3433 trace!(
3434 "[detcore, dtid {}] futex value changed while its wait was woken for a signal ({} != {}); EAGAIN",
3435 &dettid,
3436 observed,
3437 call.val()
3438 );
3439 break Err(Error::Errno(Errno::EAGAIN));
3440 }
3441 debug!(
3442 "[detcore, dtid {}] futex wait woken for a signal that is not pending for it; waiting again until {:?}",
3443 &dettid, maybe_timeout_lt
3444 );
3445 }
3446 requested = true;
3447 let ans = futex_action(
3448 guest,
3449 FutexAction::WaitRequest(maybe_timeout_lt, signal_watch),
3450 &futexid,
3451 init_val,
3452 bitset,
3453 )
3454 .await;
3455 if signal_interruption && let Some(SchedValue::Signaled(committed)) = ans {
3456 committed_wake = committed;
3457 rewait = true;
3463 continue;
3464 }
3465 break if ans != Some(SchedValue::TimeOut) {
3466 let expected = call.val();
3467 match guest.memory().read_value(ptr) {
3470 Ok(observed) => {
3471 trace!(
3472 "[detcore, dtid {}] after (emulated) futex wait, memory value is {}, expected {}",
3473 &dettid, observed, expected,
3474 );
3475 if expected == observed {
3476 debug!(
3477 "WARNING: fishy that the futex value did not change before wakeup. Weird application-level protocol.\n"
3478 );
3479 }
3480 }
3481 Err(error) => trace!(
3482 "[detcore, dtid {}] skipped post-wait futex memory diagnostic: {}",
3483 &dettid, error,
3484 ),
3485 }
3486 Ok(0)
3487 } else {
3488 trace!("[detcore, dtid {}] futex wait timed out", &dettid);
3489 Err(Error::Errno(Errno::ETIMEDOUT))
3490 };
3491 };
3492 if requested {
3493 futex_action(guest, FutexAction::WaitFinished, &futexid, init_val, bitset)
3494 .await;
3495 }
3496 keep_restart_block(guest, &res, RestartCall::Futex(call), maybe_timeout_lt)
3497 .await;
3498 res
3499 }
3500 }
3501 _ => Err(Error::Errno(Errno::ENOSYS)),
3503 }
3504 }
3505
3506 async fn futex_wait_signal_watch<G: Guest<Self>>(
3514 &self,
3515 guest: &mut G,
3516 call: syscalls::Futex,
3517 gated: bool,
3518 signal_interruption: bool,
3519 committed_wake: &mut KernelSigset,
3520 ) -> Result<Option<FutexSignalWatch>, Error> {
3521 let dettid = guest.thread_state().dettid;
3522 if gated {
3523 let state = read_wait_signal_state(guest.pid(), guest.tid())?;
3528 let mask = super::helpers::gated_own_mask(guest, state.blocked);
3529 if let Some(end) = crate::wait_signal_end::wait_end(
3530 &state,
3531 mask,
3532 guest.config().in_guest_reserved_signals,
3533 ) {
3534 let errno = call.kernel_restart_errno();
3535 trace!(
3536 "[detcore, dtid {}] futex wait ended under the in-guest delivery gate by {:?}: {:?}",
3537 &dettid, end, errno
3538 );
3539 guest.thread_state_mut().wait_signal_end = Some(end);
3540 return Err(Error::Errno(errno));
3541 }
3542 let published =
3549 crate::tool_global::publish_request_guest_mask(guest, &Resources::new(dettid));
3550 crate::tool_global::set_saved_guest_sigmask(guest, published).await;
3551 Ok(Some(FutexSignalWatch {
3552 unblocked: !mask & !kernel_sigset_bit(reverie::PERF_EVENT_SIGNAL as i32),
3553 defers_default_stops: call.restart_keeps_deadline(),
3554 pid: guest.pid().as_raw(),
3555 tid: guest.tid().as_raw(),
3556 }))
3557 } else if signal_interruption {
3558 let state = read_wait_signal_state(guest.pid(), guest.tid())?;
3561 let defers_default_stops = call.restart_keeps_deadline();
3564 let host_timed = host_timed_signals(guest).await;
3565 let committed = std::mem::take(committed_wake);
3566 let held = futex_wait_held_after_wake(host_timed, committed);
3567 let pending = futex_wait_pending_signals(
3568 guest,
3569 &state,
3570 state.pending_interrupting(state.blocked, defers_default_stops),
3571 held,
3572 host_timed,
3573 committed,
3574 )
3575 .await;
3576 if pending != 0 {
3577 let errno = call.kernel_restart_errno();
3578 trace!(
3579 "[detcore, dtid {}] futex wait interrupted by pending signals {:#x}: {:?}",
3580 &dettid, pending, errno
3581 );
3582 return Err(Error::Errno(errno));
3583 }
3584 Ok(Some(FutexSignalWatch {
3585 unblocked: !state.blocked & !kernel_sigset_bit(reverie::PERF_EVENT_SIGNAL as i32),
3586 defers_default_stops,
3587 pid: guest.pid().as_raw(),
3588 tid: guest.tid().as_raw(),
3589 }))
3590 } else {
3591 Ok(None)
3592 }
3593 }
3594
3595 async fn pi_futex_state<G: Guest<Self>>(
3600 &self,
3601 guest: &mut G,
3602 futexid: &FutexID,
3603 word: u32,
3604 ) -> Option<PiFutexState> {
3605 match futex_action(
3606 guest,
3607 FutexAction::PiState(pi_futex::word_owner(word)),
3608 futexid,
3609 word as i32,
3610 u32::MAX,
3611 )
3612 .await
3613 {
3614 Some(SchedValue::PiFutex(state)) => Some(state),
3615 _ => None,
3616 }
3617 }
3618
3619 async fn handle_futex_lock_pi<G: Guest<Self>>(
3655 &self,
3656 guest: &mut G,
3657 call: syscalls::Futex,
3658 command: i32,
3659 ) -> Result<i64, Error> {
3660 let trylock = command == libc::FUTEX_TRYLOCK_PI;
3661 let name = pi_lock_command_name(command).expect("a PI lock command");
3662 if guest.thread_state().nested_pid_namespace {
3663 return self
3664 .refuse_futex_command(guest, name, NESTED_PID_NAMESPACE_REFUSAL)
3665 .await;
3666 }
3667 if !trylock && let Some(timeout) = call.timeout() {
3668 let timeout = read_user_timespec(guest, timeout.as_raw())?;
3669 if !futex_timespec_is_valid(timeout) {
3670 return Err(Error::Errno(Errno::EINVAL));
3671 }
3672 }
3673 let private = call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0;
3674 let uaddr = futex_uaddr(&call);
3675 let futexid = futex_key(guest, uaddr, private, KeyAccess::Write).await?;
3676 if futex_word_is_file_backed(guest, uaddr) {
3677 return self
3678 .refuse_futex_command(guest, name, FILE_BACKED_WORD_REFUSAL)
3679 .await;
3680 }
3681 let dettid = guest.thread_state().dettid;
3682 let caller = pi_futex::guest_tid(dettid);
3683 let gated = guest.config().in_guest_signal_delivery;
3684 let signal_interruption = guest
3685 .config()
3686 .backend
3687 .supports_blocked_wait_signal_interruption
3688 || gated;
3689 let mut deadline: Option<Option<LogicalTime>> = None;
3691 let mut committed_wake: KernelSigset = 0;
3692 let mut rewait_watch: Option<Option<FutexSignalWatch>> = None;
3695 let mut requested = false;
3696 let res = loop {
3697 let word = match read_futex_word(guest, uaddr) {
3698 Ok(word) => word as u32,
3699 Err(error) => break Err(error),
3700 };
3701 let Some(state) = self.pi_futex_state(guest, &futexid, word).await else {
3702 break Err(Error::Errno(Errno::ERESTARTNOINTR));
3703 };
3704 let step = pi_futex::lock_pi_step(word, caller, state, trylock);
3705 trace!(
3706 "[detcore, dtid {}] {:?} on the word at {:#x}, holding {:#x}, with {:?}: {:?}",
3707 dettid,
3708 pi_lock_command_name(command),
3709 uaddr,
3710 word,
3711 state,
3712 step
3713 );
3714 let (owner, write) = match step {
3715 LockPiStep::Fail { errno, write } => {
3716 break write
3717 .map_or(Ok(()), |value| write_futex_word(guest, uaddr, value))
3718 .and(Err(Error::Errno(errno)));
3719 }
3720 LockPiStep::Acquire(value) => {
3721 break write_futex_word(guest, uaddr, value).map(|()| 0);
3722 }
3723 LockPiStep::Block { owner, write } => (owner, write),
3724 };
3725 if let Some(value) = write
3726 && let Err(error) = write_futex_word(guest, uaddr, value)
3727 {
3728 break Err(error);
3729 }
3730 let signal_watch = match rewait_watch.take() {
3731 Some(watch) => watch,
3732 None => match self
3733 .futex_wait_signal_watch(
3734 guest,
3735 call,
3736 gated,
3737 signal_interruption,
3738 &mut committed_wake,
3739 )
3740 .await
3741 {
3742 Ok(watch) => watch,
3743 Err(error) => break Err(error),
3744 },
3745 };
3746 let timeout = match deadline {
3747 Some(timeout) => timeout,
3748 None => {
3749 let timeout = if trylock {
3750 None
3751 } else {
3752 match self
3753 .futex_timeout_deadline(guest, call.futex_op(), call.timeout())
3754 .await
3755 {
3756 Ok(timeout) => timeout,
3757 Err(error) => break Err(error),
3758 }
3759 };
3760 *deadline.insert(timeout)
3761 }
3762 };
3763 requested = true;
3764 let answer = futex_action(
3765 guest,
3766 FutexAction::PiLockWait(timeout, signal_watch, owner),
3767 &futexid,
3768 word as i32,
3769 u32::MAX,
3770 )
3771 .await;
3772 match futex_action(guest, FutexAction::PiTake, &futexid, 0, u32::MAX).await {
3773 Some(SchedValue::PiTaken(Some(PiHandOver::Unlock))) => break Ok(0),
3774 Some(SchedValue::PiTaken(Some(PiHandOver::OwnerDeath))) => {
3775 break write_futex_word(guest, uaddr, pi_futex::owner_death_word(caller))
3776 .map(|()| 0);
3777 }
3778 _ => {}
3779 }
3780 match answer {
3781 Some(SchedValue::TimeOut) => break Err(Error::Errno(Errno::ETIMEDOUT)),
3782 Some(SchedValue::Signaled(committed)) if signal_interruption => {
3783 committed_wake = committed;
3787 match self
3788 .futex_wait_signal_watch(
3789 guest,
3790 call,
3791 gated,
3792 signal_interruption,
3793 &mut committed_wake,
3794 )
3795 .await
3796 {
3797 Ok(watch) => rewait_watch = Some(watch),
3798 Err(error) => break Err(error),
3799 }
3800 }
3801 _ => break Err(Error::Errno(Errno::ERESTARTNOINTR)),
3805 }
3806 };
3807 if requested {
3808 futex_action(guest, FutexAction::WaitFinished, &futexid, 0, u32::MAX).await;
3809 }
3810 res
3811 }
3812
3813 async fn handle_futex_unlock_pi<G: Guest<Self>>(
3825 &self,
3826 guest: &mut G,
3827 call: syscalls::Futex,
3828 ) -> Result<i64, Error> {
3829 if guest.thread_state().nested_pid_namespace {
3830 return self
3831 .refuse_futex_command(guest, "FUTEX_UNLOCK_PI", NESTED_PID_NAMESPACE_REFUSAL)
3832 .await;
3833 }
3834 let uaddr = futex_uaddr(&call);
3835 let word = read_futex_word(guest, uaddr)? as u32;
3836 let dettid = guest.thread_state().dettid;
3837 if pi_futex::word_owner(word) != pi_futex::guest_tid(dettid) {
3838 return Err(Error::Errno(Errno::EPERM));
3839 }
3840 let private = call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0;
3841 let futexid = futex_key(guest, uaddr, private, KeyAccess::Write).await?;
3842 if futex_word_is_file_backed(guest, uaddr) {
3843 return self
3844 .refuse_futex_command(guest, "FUTEX_UNLOCK_PI", FILE_BACKED_WORD_REFUSAL)
3845 .await;
3846 }
3847 let Some(state) = self.pi_futex_state(guest, &futexid, word).await else {
3848 return Err(Error::Errno(Errno::ERESTARTNOINTR));
3849 };
3850 let step = pi_futex::unlock_pi_step(dettid, state);
3851 trace!(
3852 "[detcore, dtid {}] FUTEX_UNLOCK_PI on the word at {:#x}, holding {:#x}, with {:?}: {:?}",
3853 dettid, uaddr, word, state, step
3854 );
3855 match step {
3856 UnlockPiStep::Fail(errno) => Err(Error::Errno(errno)),
3857 UnlockPiStep::Release => write_futex_word(guest, uaddr, 0).map(|()| 0),
3858 UnlockPiStep::HandOff { write, .. } => {
3859 write_futex_word(guest, uaddr, write)?;
3860 let woken = match futex_action(guest, FutexAction::PiHandOff, &futexid, 0, u32::MAX)
3863 .await
3864 {
3865 Some(SchedValue::Value(1)) => 1,
3866 Some(SchedValue::Value(e)) if e == nix::errno::Errno::EINTR as u64 => 0,
3867 other => panic!("impossible answer to a PI futex hand-over: {:?}", other),
3868 };
3869 if self.cfg.yields_after_futex_wake(woken) {
3870 guest.thread_state_mut().yield_after_futex_wake = true;
3871 }
3872 Ok(0)
3873 }
3874 }
3875 }
3876
3877 async fn handle_futex_wake<G: Guest<Self>>(
3882 &self,
3883 guest: &mut G,
3884 call: syscalls::Futex,
3885 command: i32,
3886 ) -> Result<i64, Error> {
3887 let bitset = if command == libc::FUTEX_WAKE_BITSET {
3888 call.val3() as u32
3889 } else {
3890 u32::MAX
3891 };
3892 if bitset == 0 {
3893 return Err(Error::Errno(Errno::EINVAL));
3894 }
3895 let private = call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0;
3896 let futexid = futex_key(guest, futex_uaddr(&call), private, KeyAccess::Read).await?;
3897 let num = match futex_action(
3898 guest,
3899 FutexAction::WakeRequest(call.val()),
3900 &futexid,
3901 0,
3902 bitset,
3903 )
3904 .await
3905 .expect("futex wake must return value")
3906 {
3907 SchedValue::Value(num) => num,
3908 SchedValue::PiWaiterReached { woken } => {
3911 if self.cfg.yields_after_futex_wake(woken) {
3912 guest.thread_state_mut().yield_after_futex_wake = true;
3913 }
3914 return Err(Error::Errno(Errno::EINVAL));
3915 }
3916 other => panic!("impossible answer to a futex wake: {:?}", other),
3917 };
3918 let _ = futex_action(guest, FutexAction::WakeFinished(0), &futexid, 0, bitset).await;
3919 if self.cfg.yields_after_futex_wake(num) {
3920 guest.thread_state_mut().yield_after_futex_wake = true;
3921 }
3922 Ok(num as i64)
3923 }
3924
3925 async fn handle_futex_requeue<G: Guest<Self>>(
3941 &self,
3942 guest: &mut G,
3943 call: syscalls::Futex,
3944 command: i32,
3945 ) -> Result<i64, Error> {
3946 let nr_wake = call.val();
3947 let nr_requeue = futex_val2(&call) as i32;
3948 if nr_wake < 0 || nr_requeue < 0 {
3949 return Err(Error::Errno(Errno::EINVAL));
3950 }
3951 let private = call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0;
3952 let uaddr = futex_uaddr(&call);
3953 let futexid = futex_key(guest, uaddr, private, KeyAccess::Read).await?;
3954 let futexid2 = futex_key(guest, futex_uaddr2(&call), private, KeyAccess::Read).await?;
3955 let mut current = 0;
3956 if command == libc::FUTEX_CMP_REQUEUE {
3957 current = read_futex_word(guest, uaddr)?;
3958 if current != call.val3() {
3959 return Err(Error::Errno(Errno::EAGAIN));
3960 }
3961 }
3962 let woken_and_moved = match futex_action(
3963 guest,
3964 FutexAction::RequeueRequest(futexid2, nr_wake as u32, nr_requeue as u32),
3965 &futexid,
3966 current,
3967 u32::MAX,
3968 )
3969 .await
3970 {
3971 Some(SchedValue::Requeued { woken, moved }) => {
3972 if self.cfg.yields_after_futex_wake(woken) {
3973 guest.thread_state_mut().yield_after_futex_wake = true;
3974 }
3975 woken + moved
3976 }
3977 Some(SchedValue::Value(num)) => num,
3980 Some(SchedValue::PiWaiterReached { woken }) => {
3983 if self.cfg.yields_after_futex_wake(woken) {
3984 guest.thread_state_mut().yield_after_futex_wake = true;
3985 }
3986 return Err(Error::Errno(Errno::EINVAL));
3987 }
3988 other => panic!("impossible answer to a futex requeue: {:?}", other),
3989 };
3990 let _ = futex_action(
3991 guest,
3992 FutexAction::WakeFinished(0),
3993 &futexid,
3994 current,
3995 u32::MAX,
3996 )
3997 .await;
3998 Ok(woken_and_moved as i64)
3999 }
4000
4001 async fn handle_futex_wake_op<G: Guest<Self>>(
4027 &self,
4028 guest: &mut G,
4029 call: syscalls::Futex,
4030 ) -> Result<i64, Error> {
4031 let nr_wake = call.val();
4032 let nr_wake2 = futex_val2(&call) as i32;
4033 let private = call.futex_op() & libc::FUTEX_PRIVATE_FLAG != 0;
4034 let uaddr2 = futex_uaddr2(&call);
4035 let futexid = futex_key(guest, futex_uaddr(&call), private, KeyAccess::Read).await?;
4036 let futexid2 = futex_key(guest, uaddr2, private, KeyAccess::Write).await?;
4037 let op = FutexWakeOp::decode(call.val3() as u32);
4038 if !op.has_known_operation() {
4039 return Err(Error::Errno(Errno::ENOSYS));
4040 }
4041 if futex_word_is_file_backed(guest, uaddr2) {
4042 return self
4043 .refuse_futex_command(guest, "FUTEX_WAKE_OP", FILE_BACKED_WORD_REFUSAL)
4044 .await;
4045 }
4046 let old = read_futex_word(guest, uaddr2)?;
4047 let new = op.apply(old).expect("operation was checked to be known");
4048 write_futex_word(guest, uaddr2, new as u32)?;
4049 let Some(wake2) = op.compare(old) else {
4050 return Err(Error::Errno(Errno::ENOSYS));
4051 };
4052 let mut woken = 0;
4053 for (futex, count, wake) in [(futexid, nr_wake, true), (futexid2, nr_wake2, wake2)] {
4054 if !wake {
4055 continue;
4056 }
4057 woken += match futex_action(guest, FutexAction::WakeRequest(count), &futex, 0, u32::MAX)
4058 .await
4059 {
4060 Some(SchedValue::Value(num)) => num,
4061 Some(SchedValue::PiWaiterReached { woken: num }) => {
4064 if self.cfg.yields_after_futex_wake(woken + num) {
4065 guest.thread_state_mut().yield_after_futex_wake = true;
4066 }
4067 return Err(Error::Errno(Errno::EINVAL));
4068 }
4069 other => panic!("impossible answer to a futex wake: {:?}", other),
4070 };
4071 let _ = futex_action(guest, FutexAction::WakeFinished(0), &futex, 0, u32::MAX).await;
4072 }
4073 if self.cfg.yields_after_futex_wake(woken) {
4074 guest.thread_state_mut().yield_after_futex_wake = true;
4075 }
4076 Ok(woken as i64)
4077 }
4078
4079 async fn refuse_futex_command<G: Guest<Self>>(
4085 &self,
4086 guest: &mut G,
4087 name: &'static str,
4088 reason: &'static str,
4089 ) -> Result<i64, Error> {
4090 tracing::error!(
4091 "[detcore, dtid {}] futex command {} is not supported: {}",
4092 guest.thread_state().dettid,
4093 name,
4094 reason,
4095 );
4096 self.refuse_unserviceable_operation(guest, reverie::syscalls::Sysno::futex, Errno::ENOSYS)
4097 .await
4098 }
4099
4100 pub(crate) async fn handle_futex_polling<G: Guest<Self>>(
4103 &self,
4104 guest: &mut G,
4105 call: syscalls::Futex,
4106 init_val: i32,
4107 deadline: FutexDeadline,
4108 ) -> Result<i64, Error> {
4109 fn make_futex_wake_request(dettid: DetTid) -> Resources {
4110 let mut rsrc = Resources::new(dettid);
4111 rsrc.fyi("futex_wake");
4112 rsrc
4113 }
4114
4115 fn make_futex_wait_request(dettid: DetTid) -> Resources {
4116 let mut rsrc = Resources::new(dettid);
4117 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4118 rsrc.fyi("futex_wait");
4119 rsrc
4120 }
4121
4122 let dettid = guest.thread_state().dettid;
4123 let futex_op = call.futex_op() & libc::FUTEX_CMD_MASK;
4124 match futex_op {
4125 libc::FUTEX_WAKE | libc::FUTEX_WAKE_BITSET => {
4126 let rsrc = make_futex_wake_request(dettid);
4127 resource_request(guest, rsrc.clone()).await; let res = guest.inject(call).await;
4129 Ok(res?)
4133 }
4134 libc::FUTEX_WAIT | libc::FUTEX_WAIT_BITSET => {
4135 if init_val != call.val() {
4136 info!(
4137 "[detcore, dtid {}] Futex wait running immediately because it will fizzle ({} != {}).",
4138 dettid,
4139 init_val,
4140 call.val()
4141 );
4142 let res = guest.inject(call).await;
4143 Ok(res?)
4144 } else {
4145 let rsrc = make_futex_wait_request(dettid);
4146 let deadline = self.futex_deadline(guest, call, deadline).await?;
4147 let res =
4148 retry_nonblocking_syscall_with_timeout(guest, call, rsrc, deadline).await;
4149 keep_restart_block(guest, &res, RestartCall::Futex(call), deadline).await;
4150 let res = res?;
4151 trace!(
4152 "[detcore, dtid {}] after futex wait, memory value is {}",
4153 &dettid,
4154 guest.memory().read_value(call.uaddr().unwrap()).unwrap()
4155 );
4156 Ok(res)
4157 }
4158 }
4159 _ => Err(Error::Errno(Errno::ENOSYS)),
4161 }
4162 }
4163
4164 fn exec_image<G: Guest<Self>>(
4173 &self,
4174 guest: &G,
4175 call: &syscalls::Execveat,
4176 ) -> Option<crate::exec_record::ExecRecord> {
4177 let path: std::path::PathBuf = call.path()?.read(&guest.memory()).ok()?;
4178 self.exec_image_at(guest, call.dirfd(), &path)
4179 }
4180
4181 pub(crate) fn exec_image_from_execfn<G: Guest<Self>>(
4188 &self,
4189 guest: &G,
4190 ) -> Option<crate::exec_record::ExecRecord> {
4191 use reverie::syscalls::FromToRaw;
4192 let address = guest.auxv().at_execfn()?.as_raw();
4193 let path: std::path::PathBuf = Option::<syscalls::PathPtr>::from_raw(address)?
4194 .read(&guest.memory())
4195 .ok()?;
4196 self.exec_image_at(guest, libc::AT_FDCWD, &path)
4197 }
4198
4199 pub(crate) fn execfn_comm<G: Guest<Self>>(
4206 &self,
4207 guest: &G,
4208 ) -> Option<crate::procfs::task_stat::TaskComm> {
4209 use std::os::unix::ffi::OsStrExt;
4210
4211 use reverie::syscalls::FromToRaw;
4212 let address = guest.auxv().at_execfn()?.as_raw();
4213 let path: std::path::PathBuf = Option::<syscalls::PathPtr>::from_raw(address)?
4214 .read(&guest.memory())
4215 .ok()?;
4216 Some(crate::procfs::task_stat::TaskComm::of_exec_filename(
4217 path.as_os_str().as_bytes(),
4218 ))
4219 }
4220
4221 pub fn exec_image_at<G: Guest<Self>>(
4226 &self,
4227 guest: &G,
4228 dirfd: i32,
4229 path: &std::path::Path,
4230 ) -> Option<crate::exec_record::ExecRecord> {
4231 let task = self.guest_task_directory(guest)?;
4232 let root = std::fs::read_link(task.join("root")).ok()?;
4233 let inside = |link: std::path::PathBuf| {
4234 Some(std::path::Path::new("/").join(link.strip_prefix(&root).ok()?))
4235 };
4236 let cwd = inside(std::fs::read_link(task.join("cwd")).ok()?)?;
4237 let spelled = if path.is_absolute() || dirfd == libc::AT_FDCWD {
4238 cwd.join(path)
4239 } else {
4240 let descriptor = task.join("fd").join(dirfd.to_string());
4241 let directory = inside(std::fs::read_link(descriptor).ok()?)?;
4242 if path.as_os_str().is_empty() {
4245 directory
4246 } else {
4247 directory.join(path)
4248 }
4249 };
4250 match self.resolve_exe_subject(guest, &spelled, true) {
4254 Some(crate::syscalls::namespace::ExeLinkSubject::Own) => {
4255 return guest.thread_state().exe.as_deref().cloned();
4256 }
4257 Some(crate::syscalls::namespace::ExeLinkSubject::Other(_)) => return None,
4259 None => {}
4260 }
4261 crate::exec_record::ExecRecord::resolve(&root, &cwd, &spelled)
4262 }
4263
4264 async fn exec_image_without_procfs<G: Guest<Self>>(
4284 &self,
4285 guest: &mut G,
4286 call: &syscalls::Execveat,
4287 ) -> Option<crate::exec_record::ExecRecord> {
4288 use std::os::unix::ffi::OsStrExt;
4289 use std::os::unix::fs::MetadataExt;
4290 if crate::procfs_inode::tool_can_name_guest_descriptors(&self.cfg.backend)
4291 || guest.thread_state().nested_pid_namespace
4292 {
4293 return None;
4294 }
4295 let root = self.exec_record_root(guest)?;
4296 let path: std::path::PathBuf = call.path()?.read(&guest.memory()).ok()?;
4297 if path.as_os_str().is_empty()
4298 || (!path.is_absolute() && call.dirfd() != libc::AT_FDCWD)
4299 || (!path.is_absolute() && path.components().count() < 2)
4300 {
4301 return None;
4302 }
4303 let cwd = if path.is_absolute() {
4304 None
4305 } else {
4306 let directory = self.injected_working_directory(guest, None, 0).await.ok()?;
4307 Some(std::path::PathBuf::from(
4308 <std::ffi::OsString as std::os::unix::ffi::OsStringExt>::from_vec(directory),
4309 ))
4310 .filter(|directory| directory.is_absolute())
4311 };
4312 let spelled = match &cwd {
4313 Some(cwd) => cwd.join(&path),
4314 None => path.clone(),
4315 };
4316 let own_pid = self.own_detpid(guest).as_raw();
4317 if spelled.starts_with("/proc") {
4318 return match crate::syscalls::namespace::exe_link_subject(
4319 spelled.as_os_str().as_bytes(),
4320 own_pid,
4321 ) {
4322 Some(crate::syscalls::namespace::ExeLinkSubject::Own) => {
4323 guest.thread_state().exe.as_deref().cloned()
4324 }
4325 _ => None,
4326 };
4327 }
4328 let record = crate::exec_record::ExecRecord::resolve_from(&root, cwd.as_deref(), &path)?;
4329 let named = std::fs::metadata(root.join(spelled.strip_prefix("/").ok()?))
4332 .ok()
4333 .map(|metadata| (metadata.dev(), metadata.ino()));
4334 let tool_image = std::fs::metadata("/proc/self/exe")
4335 .ok()
4336 .map(|metadata| (metadata.dev(), metadata.ino()));
4337 (Some(record.identity()) == named && Some(record.identity()) != tool_image)
4338 .then_some(record)
4339 }
4340
4341 pub async fn handle_execveat<G: Guest<Self>>(
4343 &self,
4344 guest: &mut G,
4345 call: syscalls::Execveat,
4346 ) -> Result<i64, Error> {
4347 self.promote_before_native_lifecycle(guest);
4348 let (old_metadata, old_memory_metadata, table_is_shared, dettid, detpid, old_mm_id) = {
4349 let thread_state = guest.thread_state();
4350 (
4351 Arc::clone(&thread_state.file_metadata),
4352 Arc::clone(&thread_state.memory_metadata),
4353 Arc::strong_count(&thread_state.file_metadata) > 1,
4354 thread_state.dettid,
4355 thread_state.detpid.expect("detpid unset"),
4356 thread_state.mm_id,
4357 )
4358 };
4359 let (mut new_metadata, closed_open_files, exec_fd_blocking) = {
4360 let metadata = old_metadata.lock().unwrap();
4361 let mut new_metadata = metadata.for_exec(dettid);
4363 new_metadata.hold_alone(detpid);
4364 (
4365 new_metadata.clone(),
4366 metadata.open_files_closed_on_exec(table_is_shared),
4367 new_metadata.exec_blocking_overrides(),
4368 )
4369 };
4370 self.keep_table_for_exec(guest, &old_metadata, &mut new_metadata, detpid);
4374 let preserve_exec_fd_status = guest.config().backend.rediscovers_descriptors_after_exec;
4375 let exe = match self.exec_image(guest, &call) {
4376 Some(exe) => Some(exe),
4377 None => self.exec_image_without_procfs(guest, &call).await,
4378 };
4379
4380 prepare_exec(
4381 guest,
4382 old_mm_id,
4383 if preserve_exec_fd_status {
4384 exec_fd_blocking
4385 } else {
4386 Default::default()
4387 },
4388 Vec::new(),
4390 exe,
4391 )
4392 .await;
4393
4394 let mut released_ports = Vec::new();
4395 for open_file_id in closed_open_files {
4396 if let Some(port) = self.release_port_for_open_file(guest, open_file_id).await {
4397 released_ports.push((open_file_id, port));
4398 }
4399 }
4400
4401 let exec_robust_wakes = match self.robust_owner_death_path() {
4413 Some(OwnerDeathPath::Kernel) => Some(
4414 self.read_thread_group_robust_list_wakes(guest, OwnerDeath::Exec)
4415 .await,
4416 ),
4417 Some(OwnerDeathPath::Barrier) | None => None,
4418 };
4419 let old_robust_list_head;
4420 let old_process_files = guest.thread_state().process_files;
4424
4425 {
4426 let thread_state = guest.thread_state_mut();
4427 thread_state.file_metadata = Arc::new(Mutex::new(new_metadata));
4428 thread_state.adopt_process_files();
4429 thread_state.memory_metadata = Arc::new(Mutex::new(MemoryMetadata::new()));
4430 thread_state.mm_id = old_mm_id.for_exec(detpid);
4431 old_robust_list_head = thread_state.take_robust_list_for_exec();
4432 if let Some(wakes) = exec_robust_wakes {
4433 thread_state.stage_robust_list_wakes_for_exec(wakes);
4434 }
4435 }
4436
4437 let errno = self.record_or_replay(guest, call).await.unwrap_err();
4439 let refusal = guest.take_unsupported_refusal();
4442
4443 {
4444 let thread_state = guest.thread_state_mut();
4445 thread_state.file_metadata = old_metadata;
4446 thread_state.process_files = old_process_files;
4447 thread_state.memory_metadata = old_memory_metadata;
4448 thread_state.mm_id = old_mm_id;
4449 thread_state.restore_robust_list_after_failed_exec(old_robust_list_head);
4450 thread_state.take_robust_list_wakes_for_exec();
4452 }
4453
4454 cancel_exec(guest).await;
4455 for (open_file_id, port) in released_ports {
4456 self.restore_port_for_open_file(guest, open_file_id, port)
4457 .await;
4458 }
4459
4460 if let Some(refusal) = refusal {
4469 tracing::error!(
4470 "[detcore, dtid {dettid}] {}; the replacement image did not run",
4471 refusal.diagnostic
4472 );
4473 if !self.cfg.panic_on_unsupported_syscalls {
4474 crate::tool_global::report_unsupported_syscall(guest, call.number()).await;
4475 }
4476 return self
4477 .refuse_unserviceable_operation_because(
4478 guest,
4479 call.number(),
4480 errno,
4481 refusal.diagnostic,
4482 )
4483 .await;
4484 }
4485
4486 Err(errno.into())
4487 }
4488
4489 pub async fn handle_sched_yield<G: Guest<Self>>(
4493 &self,
4494 guest: &mut G,
4495 call: syscalls::SchedYield,
4496 ) -> Result<i64, Error> {
4497 if self.cfg.sequentialize_threads {
4498 match sched_yield_route(&self.cfg) {
4499 SchedYieldRoute::ChaosReprioritize => {
4500 let change_time = guest.thread_state().thread_logical_time.as_nanos();
4501 let request = Self::random_priority_changepoint_request(guest, change_time);
4502 resource_request(guest, request).await;
4503 }
4504 SchedYieldRoute::ReplayStrongYield => {
4505 if self.cfg.max_timeslice.is_some() {
4506 guest
4507 .thread_state_mut()
4508 .reset_timeslice_for_explicit_yield();
4509 }
4510 let request = Self::sched_yield_request(guest);
4511 resource_request(guest, request).await;
4512 }
4513 SchedYieldRoute::ChaosYieldEpoch => {
4514 let request = Self::sched_yield_request(guest);
4515 resource_request(guest, request).await;
4516 }
4517 SchedYieldRoute::WeakYield
4518 if !self.cfg.chaos && guest.thread_state().slice_boundary.is_some() =>
4519 {
4520 self.end_timeslice_for_sched_yield(guest).await;
4524 }
4525 SchedYieldRoute::WeakYield => {
4526 let request = Self::yield_request(guest);
4527 resource_request(guest, request).await;
4528 }
4529 SchedYieldRoute::EndTimeslice => self.end_timeslice_for_sched_yield(guest).await,
4530 }
4531 trace!("sched_yield yielded to the scheduler; NOT performing actual syscall");
4532 Ok(0)
4533 } else {
4534 Ok(self.record_or_replay(guest, call).await?)
4535 }
4536 }
4537
4538 async fn guest_visible_pid<G: Guest<Self>>(
4544 &self,
4545 guest: &mut G,
4546 sysno: reverie::syscalls::Sysno,
4547 child: DetPid,
4548 ) -> Result<i32, Error> {
4549 if !guest.thread_state().nested_pid_namespace {
4550 return Ok(child.as_raw());
4551 }
4552 let refusal = match crate::tool_global::local_pid(guest, child).await {
4553 Ok(Some(local)) => return Ok(local),
4554 Ok(None) => format!("child {child} is not visible in the caller's namespace"),
4555 Err(reason) => reason,
4556 };
4557 Err(self.refuse_nested_pid(guest, sysno, &refusal).await)
4558 }
4559
4560 async fn task_of_guest_pid<G: Guest<Self>>(
4565 &self,
4566 guest: &mut G,
4567 sysno: reverie::syscalls::Sysno,
4568 pid: i32,
4569 ) -> Result<DetPid, Error> {
4570 if !guest.thread_state().nested_pid_namespace || pid <= 0 {
4571 return Ok(DetPid::from_raw(pid));
4572 }
4573 let refusal = match crate::tool_global::guest_pid(guest, pid).await {
4574 GuestPidAnswer::Task { task, .. } => return Ok(task),
4575 GuestPidAnswer::NoSuchPid => {
4576 format!("the kernel answered pid {pid}, which no recorded task has")
4577 }
4578 GuestPidAnswer::Untranslatable(reason) => reason,
4579 };
4580 Err(self.refuse_nested_pid(guest, sysno, &refusal).await)
4581 }
4582
4583 async fn register_clone_pidfd<G: Guest<Self>>(
4603 &self,
4604 guest: &mut G,
4605 address: usize,
4606 child: DetTid,
4607 ) {
4608 let dettid = guest.thread_state().dettid;
4609 let skip = |reason: String| {
4610 tracing::error!(
4611 "[detcore, dtid {}] the pidfd of CLONE_PIDFD child {} is not tracked: {}; pidfd \
4612 calls through it answer EBADF (https://github.com/rrnewton/hermit/issues/4430)",
4613 dettid,
4614 child,
4615 reason,
4616 );
4617 };
4618 let fd: libc::c_int = match Addr::<libc::c_int>::from_raw(address)
4619 .map(|address| guest.memory().read_value(address))
4620 {
4621 Some(Ok(fd)) => fd,
4622 _ => return skip(format!("its word at {address:#x} cannot be read back")),
4625 };
4626 if guest.thread_state().with_detfd(fd, |_| ()).is_ok() {
4629 return skip(format!(
4630 "its word names descriptor {fd}, which Detcore already tracks"
4631 ));
4632 }
4633 if let Err(errno) = self
4634 .track_held_fd(
4635 guest,
4636 fd,
4637 nix::fcntl::OFlag::O_CLOEXEC,
4638 crate::fd::FdType::Pidfd,
4639 )
4640 .await
4641 {
4642 return skip(format!("tracking descriptor {fd} failed with {errno}"));
4643 }
4644 if let Err(error) = guest.thread_state().with_detfd(fd, |detfd| {
4645 detfd.set_pidfd_target(DetPid::from_raw(child.as_raw()))
4646 }) {
4647 skip(format!("descriptor {fd} has no Detcore entry: {error}"));
4648 }
4649 }
4650
4651 async fn clone3_set_tid_refusal<G: Guest<Self>>(
4679 &self,
4680 guest: &mut G,
4681 clone: &syscalls::Clone3,
4682 ) -> Option<Result<i64, Error>> {
4683 const SET_TID_SIZE_OFFSET: usize = 72;
4694 const MAX_SET_TID: u64 = 32;
4696 let size = clone.size();
4697 let page = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).ok()?;
4698 if size <= SET_TID_SIZE_OFFSET || size > page {
4699 return None;
4700 }
4701 let witness = super::chosen_ids::FaultWitness::for_backend(
4711 &guest.config().backend,
4712 guest.tid().as_raw(),
4713 );
4714 let memory = guest.memory();
4715 let mut raw = match super::chosen_ids::read_clone_args(&memory, witness, clone) {
4716 super::chosen_ids::Clone3Read::Bytes(bytes) => bytes,
4717 super::chosen_ids::Clone3Read::Refused => return None,
4718 super::chosen_ids::Clone3Read::Unreadable { .. } => {
4719 return Some(
4720 self.refuse_unchecked_set_tid(
4721 guest,
4722 "clone3's struct clone_args cannot be read with the guest's own access",
4723 )
4724 .await,
4725 );
4726 }
4727 };
4728 if raw.len() < super::chosen_ids::CLONE_ARGS_SIZE_VER2 {
4730 raw.resize(super::chosen_ids::CLONE_ARGS_SIZE_VER2, 0);
4731 }
4732 let field = |offset: usize| u64::from_ne_bytes(raw[offset..offset + 8].try_into().unwrap());
4733 let flags = field(0);
4734 let exit_signal = field(32);
4735 let set_tid = field(64);
4736 let set_tid_size = field(72);
4737 if !clone3_signal_fields_valid(flags, exit_signal) {
4738 return None;
4739 }
4740 if set_tid_size == 0 || set_tid_size > MAX_SET_TID {
4741 return None;
4742 }
4743 let mut tids = vec![0 as libc::pid_t; set_tid_size as usize];
4746 if !super::chosen_ids::within_user_memory(
4747 set_tid as usize,
4748 tids.len() * std::mem::size_of::<libc::pid_t>(),
4749 ) {
4750 return None;
4751 }
4752 if set_tid == 0 {
4753 return None;
4755 }
4756 let read =
4761 super::chosen_ids::read_pid_array(&memory, set_tid as usize, &mut tids).is_some();
4762 if !read
4763 && super::chosen_ids::range_faults(
4764 witness,
4765 set_tid as usize,
4766 tids.len() * std::mem::size_of::<libc::pid_t>(),
4767 )
4768 {
4769 return None;
4770 }
4771 let newpid = flags & libc::CLONE_NEWPID as u64 != 0;
4774 if read && (tids.iter().any(|tid| *tid < 1) || (newpid && tids[0] != 1)) {
4775 return None;
4776 }
4777 const CAP_SYS_ADMIN: u32 = 21;
4782 const CAP_CHECKPOINT_RESTORE: u32 = 40;
4783 let thread = guest.thread_state();
4784 let status = format!(
4785 "/proc/{}/task/{}/status",
4786 thread.detpid.expect("detpid unset"),
4787 thread.dettid
4788 );
4789 let host_readable = matches!(witness, super::chosen_ids::FaultWitness::HostMaps(_));
4792 let capabilities = host_readable
4793 .then(|| std::fs::read_to_string(status).ok())
4794 .flatten()
4795 .and_then(|status| {
4796 status
4797 .lines()
4798 .find_map(|line| line.strip_prefix("CapEff:"))
4799 .and_then(|mask| u64::from_str_radix(mask.trim(), 16).ok())
4800 });
4801 let pid_capabilities = (1u64 << CAP_SYS_ADMIN) | (1u64 << CAP_CHECKPOINT_RESTORE);
4802 if capabilities.is_some_and(|mask| mask & pid_capabilities == 0) {
4803 return None;
4804 }
4805 if guest.thread_state().nested_pid_namespace {
4808 return Some(Err(self
4809 .refuse_nested_pid(
4810 guest,
4811 reverie::syscalls::Sysno::clone3,
4812 "clone3 set_tid names pids in a namespace Detcore does not translate",
4813 )
4814 .await));
4815 }
4816 if set_tid_size <= u64::from(newpid) {
4821 return None;
4822 }
4823 if !read {
4824 return Some(
4825 self.refuse_unchecked_set_tid(
4826 guest,
4827 "clone3 set_tid's array cannot be read with the guest's own access",
4828 )
4829 .await,
4830 );
4831 }
4832 let tid = tids[usize::from(newpid)];
4833 if !crate::tool_global::thread_id_refused(guest, DetTid::from_raw(tid)).await {
4834 return None;
4835 }
4836 let message = format!(
4837 "hermit: clone3 set_tid asks for id {tid}, which an earlier task of this run had; \
4838 Detcore keeps per-task state by that id and cannot give it to a new task \
4839 (https://github.com/rrnewton/hermit/issues/4370)"
4840 );
4841 tracing::error!(
4842 "[detcore, dtid {}] {}",
4843 guest.thread_state().dettid,
4844 message
4845 );
4846 if guest.config().panic_on_unsupported_syscalls {
4847 use std::io::Write as _;
4848 let _ = writeln!(crate::util::RetryingStderr, "{message}");
4849 }
4850 Some(
4851 self.refuse_unserviceable_operation(
4852 guest,
4853 reverie::syscalls::Sysno::clone3,
4854 Errno::EEXIST,
4855 )
4856 .await,
4857 )
4858 }
4859
4860 async fn refuse_unchecked_set_tid<G: Guest<Self>>(
4866 &self,
4867 guest: &mut G,
4868 what: &str,
4869 ) -> Result<i64, Error> {
4870 let message = format!(
4871 "hermit: {what}, so Detcore cannot check its set_tid against the run's earlier tasks \
4872 (https://github.com/rrnewton/hermit/issues/4370)"
4873 );
4874 tracing::error!(
4875 "[detcore, dtid {}] {}",
4876 guest.thread_state().dettid,
4877 message
4878 );
4879 if guest.config().panic_on_unsupported_syscalls {
4880 use std::io::Write as _;
4881 let _ = writeln!(crate::util::RetryingStderr, "{message}");
4882 }
4883 self.refuse_unserviceable_operation(guest, reverie::syscalls::Sysno::clone3, Errno::EFAULT)
4884 .await
4885 }
4886
4887 async fn refuse_nested_pid<G: Guest<Self>>(
4890 &self,
4891 guest: &mut G,
4892 sysno: reverie::syscalls::Sysno,
4893 reason: &str,
4894 ) -> Error {
4895 tracing::error!(
4896 "[detcore, dtid {}] {} from a nested PID namespace is not supported: {} (https://github.com/rrnewton/hermit/issues/4155)",
4897 guest.thread_state().dettid,
4898 sysno,
4899 reason,
4900 );
4901 match self
4902 .refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
4903 .await
4904 {
4905 Err(error) => error,
4906 Ok(_) => Errno::ENOSYS.into(),
4907 }
4908 }
4909
4910 pub async fn handle_wait4<G: Guest<Self>>(
4914 &self,
4915 guest: &mut G,
4916 call: syscalls::Wait4,
4917 ) -> Result<i64, Error> {
4918 let serial_emulated_wait =
4919 guest.config().backend.emulates_child_waits && guest.config().sequentialize_threads;
4920 let mut emulated_wait_consumed = false;
4921 let dettid = guest.thread_state().dettid;
4922 let mut rsrc = Resources::new(dettid);
4923 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4924 rsrc.fyi("wait4");
4925
4926 validate_wait4_arguments(call.pid(), call.options())?;
4927
4928 let parent = guest.thread_state().detpid.expect("detpid unset");
4929 let nested = guest.thread_state().nested_pid_namespace;
4933 if nested && (call.pid() == 0 || call.pid() < -1 || serial_emulated_wait) {
4934 return Err(self
4935 .refuse_nested_pid(
4936 guest,
4937 reverie::syscalls::Sysno::wait4,
4938 "a process-group wait, or a wait on a backend that emulates child waits",
4939 )
4940 .await);
4941 }
4942 let named_child = if call.pid() > 0 && nested {
4943 match crate::tool_global::guest_pid(guest, call.pid()).await {
4944 GuestPidAnswer::Task { task, .. } => task,
4945 GuestPidAnswer::NoSuchPid => return Err(Errno::ECHILD.into()),
4948 GuestPidAnswer::Untranslatable(reason) => {
4949 return Err(self
4950 .refuse_nested_pid(guest, reverie::syscalls::Sysno::wait4, &reason)
4951 .await);
4952 }
4953 }
4954 } else {
4955 DetPid::from_raw(call.pid())
4956 };
4957 let selector = if !wait4_uses_terminal_selector(call.options(), serial_emulated_wait) {
4962 None
4963 } else {
4964 match call.pid() {
4965 pid if pid > 0 => Some(ChildWaitSelector::Exact(named_child)),
4966 -1 => Some(ChildWaitSelector::Any),
4967 0 => process_group(guest, parent)
4968 .await
4969 .map(ChildWaitSelector::ProcessGroup),
4970 pid if pid < -1 => Some(ChildWaitSelector::ProcessGroup(DetPid::from_raw(-pid))),
4971 _ => unreachable!(),
4972 }
4973 };
4974 let spec = selector
4975 .map(|selector| terminal_child_wait_spec(selector, dettid, call.options().bits()));
4976 let complete_lineage = guest.config().backend.tracks_process_children;
4977 let managed_spec = if let Some(spec) = spec {
4978 let (_, has_child) = ready_child_wait(guest, spec).await;
4979 (has_child || complete_lineage).then_some(spec)
4980 } else {
4981 None
4982 };
4983
4984 let value = if call.options().contains(WaitPidFlag::WNOHANG) {
4985 resource_request(guest, rsrc.clone()).await;
4986 info!(
4987 "[dtid {}] Executing non-blocking wait4 in one shot.",
4988 dettid
4989 );
4990 if let Some(spec) = spec {
4991 'select_child: loop {
4992 let (ready, has_child) = ready_child_wait(guest, spec).await;
4993 let Some(child) = ready else {
4994 break if has_child {
4995 0
4996 } else if complete_lineage {
4997 return Err(Errno::ECHILD.into());
4998 } else {
4999 guest.inject_with_retry(call).await?
5000 };
5001 };
5002 let _ = await_exact_child_physical_exit(guest, child).await;
5003 let exact_call = call.with_pid(
5004 self.guest_visible_pid(guest, reverie::syscalls::Sysno::wait4, child)
5005 .await?,
5006 );
5007 loop {
5008 let result = if serial_emulated_wait {
5009 complete_selected_emulated_wait4(guest, exact_call, child).await
5010 } else {
5011 guest
5012 .inject_with_retry(exact_call)
5013 .await
5014 .map_err(Error::from)
5015 };
5016 emulated_wait_consumed =
5017 serial_emulated_wait && matches!(result, Ok(value) if value > 0);
5018 match result {
5019 Ok(value) if value != 0 => break 'select_child value,
5020 Ok(_) => yield_once().await,
5021 Err(Error::Errno(Errno::ECHILD)) => {
5022 let _ = consume_child_wait(guest, child).await;
5023 if child_wait_can_retry_after_stale(spec) {
5024 continue 'select_child;
5025 }
5026 return Err(Errno::ECHILD.into());
5027 }
5028 Err(error) => return Err(error),
5029 }
5030 }
5031 }
5032 } else {
5033 guest.inject_with_retry(call).await?
5034 }
5035 } else if let Some(spec) = managed_spec {
5036 {
5037 let blocked_mask = blocked_signal_mask();
5043 let mut stack = guest.stack().await;
5044 let blocked_mask_addr = stack.push(blocked_mask);
5045 let old_mask_addr = stack.reserve::<KernelSigset>();
5046 let action_addr = stack.reserve::<KernelSigaction>();
5047 let _mask_guard = stack.commit()?;
5048 let guest_signal_mask =
5049 block_signals_for_disposition(guest, blocked_mask_addr, old_mask_addr).await?;
5050 let inspect_signal_action = guest
5051 .config()
5052 .backend
5053 .requires_thread_directed_process_signals;
5054
5055 let poll_call = call.with_options(call.options() | WaitPidFlag::WNOHANG);
5056 let mut pending_signal = None;
5057 let result: Result<i64, Error> = loop {
5058 let status = wait_for_child_lifecycle(guest, spec).await;
5059 if pending_signal.is_none() {
5060 pending_signal = wait_signal_disposition(
5061 guest,
5062 status,
5063 &guest_signal_mask,
5064 action_addr,
5065 inspect_signal_action,
5066 )
5067 .await?;
5068 }
5069 let (ready, has_child) = ready_child_wait(guest, spec).await;
5070 if let Some(child) = ready {
5071 let _ = await_exact_child_physical_exit(guest, child).await;
5072 let result = if serial_emulated_wait {
5073 complete_selected_emulated_wait4(guest, call, child).await
5074 } else {
5075 match self
5076 .guest_visible_pid(guest, reverie::syscalls::Sysno::wait4, child)
5077 .await
5078 {
5079 Ok(pid) => guest
5080 .inject_with_retry(call.with_pid(pid))
5081 .await
5082 .map_err(Error::from),
5083 Err(error) => Err(error),
5084 }
5085 };
5086 emulated_wait_consumed =
5087 serial_emulated_wait && matches!(result, Ok(value) if value > 0);
5088 match result {
5089 Ok(value) => break Ok(value),
5090 Err(Error::Errno(Errno::ECHILD)) => {
5091 let _ = consume_child_wait(guest, child).await;
5092 if child_wait_can_retry_after_stale(spec) {
5093 let (next_ready, _) = ready_child_wait(guest, spec).await;
5094 if stale_any_wait_must_interrupt(
5095 pending_signal.is_some(),
5096 next_ready,
5097 ) {
5098 break interrupted_child_wait_result(
5099 guest,
5100 call,
5101 pending_signal.expect("signal checked above"),
5102 )
5103 .await;
5104 }
5105 continue;
5106 }
5107 break Err(Errno::ECHILD.into());
5108 }
5109 Err(error) => break Err(error),
5110 }
5111 }
5112 if !has_child {
5113 break Err(Errno::ECHILD.into());
5114 }
5115 if serial_emulated_wait {
5116 if let Some(disposition) = pending_signal {
5120 break interrupted_child_wait_result(guest, call, disposition).await;
5121 }
5122 continue;
5123 }
5124 match guest.inject(poll_call).await {
5125 Ok(value) => {
5126 if value > 0 {
5127 break Ok(value);
5128 }
5129 if let Some(disposition) = pending_signal {
5130 break interrupted_child_wait_result(guest, call, disposition)
5131 .await;
5132 }
5133 }
5134 Err(errno) => break Err(errno.into()),
5135 }
5136 };
5137
5138 restore_signals_after_disposition(guest, old_mask_addr).await?;
5139 result?
5140 }
5141 } else {
5142 retry_nonblocking_syscall(guest, call, rsrc, None).await?
5145 };
5146 let reaped = if value > 0 {
5149 self.task_of_guest_pid(guest, reverie::syscalls::Sysno::wait4, value as i32)
5150 .await?
5151 } else {
5152 DetPid::from_raw(value as i32)
5153 };
5154 let consumed_termination = if emulated_wait_consumed || value <= 0 {
5155 false
5156 } else if let Some(status) = call.wstatus() {
5157 wait_status_is_termination(guest.memory().read_value(status)?)
5158 } else if !call
5159 .options()
5160 .intersects(WaitPidFlag::WUNTRACED | WaitPidFlag::WCONTINUED)
5161 {
5162 true
5167 } else {
5168 guest
5171 .thread_state()
5172 .has_exited_child_process_cpu_time(reaped)
5173 || matches!(
5174 crate::tool_global::exact_child_wait_state(guest, reaped).await,
5175 ExactChildWaitState::LogicallyExited
5176 | ExactChildWaitState::PhysicalExitPending
5177 | ExactChildWaitState::PhysicallyExited
5178 )
5179 };
5180 if consumed_termination {
5181 guest.thread_state_mut().reap_child_process_cpu_time(reaped);
5182 let _ = consume_child_wait(guest, reaped).await;
5183 }
5184 if value > 0
5185 && let Some(rusage) = call.rusage()
5186 {
5187 let usage: libc::rusage = unsafe { std::mem::zeroed() };
5189 guest.memory().write_value(rusage, &usage)?;
5190 }
5191 Ok(value)
5192 }
5193
5194 pub async fn handle_waitid<G: Guest<Self>>(
5200 &self,
5201 guest: &mut G,
5202 mut call: syscalls::Waitid,
5203 ) -> Result<i64, Error> {
5204 if guest.config().backend.emulates_child_waits && guest.config().sequentialize_threads {
5209 if guest.thread_state().nested_pid_namespace {
5213 return Err(self
5214 .refuse_nested_pid(
5215 guest,
5216 reverie::syscalls::Sysno::waitid,
5217 "a wait on a backend that emulates child waits",
5218 )
5219 .await);
5220 }
5221 return emulated_waitid::handle(guest, call).await;
5222 }
5223 let nested = guest.thread_state().nested_pid_namespace;
5227 if nested && call.which() == libc::P_PGID as i32 {
5228 return Err(self
5229 .refuse_nested_pid(
5230 guest,
5231 reverie::syscalls::Sysno::waitid,
5232 "a process-group wait",
5233 )
5234 .await);
5235 }
5236 let named_child = if nested && call.which() == libc::P_PID as i32 && call.pid() > 0 {
5237 match crate::tool_global::guest_pid(guest, call.pid()).await {
5238 GuestPidAnswer::Task { task, .. } => task,
5239 GuestPidAnswer::NoSuchPid => return Err(Errno::ECHILD.into()),
5240 GuestPidAnswer::Untranslatable(reason) => {
5241 return Err(self
5242 .refuse_nested_pid(guest, reverie::syscalls::Sysno::waitid, &reason)
5243 .await);
5244 }
5245 }
5246 } else {
5247 DetPid::from_raw(call.pid())
5248 };
5249 let dettid = guest.thread_state().dettid;
5250 let mut rsrc = Resources::new(dettid);
5251 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
5252 rsrc.fyi("waitid");
5253
5254 let event_options = libc::WEXITED | libc::WSTOPPED | libc::WCONTINUED;
5255 let allowed_options = event_options
5256 | libc::WNOHANG
5257 | libc::WNOWAIT
5258 | libc::__WNOTHREAD
5259 | libc::__WALL
5260 | libc::__WCLONE;
5261 if call.options() & event_options == 0 || call.options() & !allowed_options != 0 {
5262 return Err(Errno::EINVAL.into());
5263 }
5264
5265 if call.info().is_none() {
5270 return Err(Errno::EFAULT.into());
5271 }
5272
5273 let terminal_events_only = call.options() & libc::WEXITED != 0
5276 && call.options() & (libc::WSTOPPED | libc::WCONTINUED | libc::__WCLONE | libc::__WALL)
5277 == 0;
5278
5279 if !terminal_events_only && call.which() == libc::P_PGID as i32 && call.pid() == 0 {
5282 call = call.with_pid(snapshot_process_group(guest.pid())?);
5283 }
5284
5285 let pidfd_nonblocking =
5289 if call.which() == libc::P_PIDFD as i32 && call.options() & libc::WNOHANG == 0 {
5290 resource_request(guest, rsrc.clone()).await;
5291 guest_fd_status_flags(guest.pid(), call.pid())? & libc::O_NONBLOCK != 0
5292 } else {
5293 false
5294 };
5295 if call.which() == libc::P_PIDFD as i32
5296 && call.options() & libc::WNOHANG == 0
5297 && !pidfd_nonblocking
5298 {
5299 return Err(Errno::EOPNOTSUPP.into());
5303 }
5304 let info = call
5305 .info()
5306 .expect("waitid infop checked above")
5307 .cast::<GuestSiginfo>();
5308
5309 let terminal_spec = if terminal_events_only {
5319 let selector = match call.which() {
5320 which if which == libc::P_PID as i32 => Some(ChildWaitSelector::Exact(named_child)),
5321 which if which == libc::P_ALL as i32 => Some(ChildWaitSelector::Any),
5322 which if which == libc::P_PGID as i32 => {
5323 let group = if call.pid() == 0 {
5324 process_group(guest, guest.thread_state().detpid.expect("detpid unset"))
5325 .await
5326 } else {
5327 Some(DetPid::from_raw(call.pid()))
5328 };
5329 group.map(ChildWaitSelector::ProcessGroup)
5330 }
5331 _ => None,
5332 };
5333 selector.map(|selector| terminal_child_wait_spec(selector, dettid, call.options()))
5334 } else {
5335 None
5336 };
5337 let complete_lineage = guest.config().backend.tracks_process_children;
5338 let managed_terminal_spec = if let Some(spec) = terminal_spec {
5339 let (_, has_child) = ready_child_wait(guest, spec).await;
5340 (has_child || complete_lineage).then_some(spec)
5341 } else {
5342 None
5343 };
5344
5345 if call.options() & libc::WNOHANG != 0 || pidfd_nonblocking {
5346 if !pidfd_nonblocking {
5347 resource_request(guest, rsrc).await;
5348 }
5349 info!(
5350 "[dtid {}] Executing non-blocking waitid in one shot.",
5351 dettid
5352 );
5353 'select_child: loop {
5354 let selected = if let Some(spec) = terminal_spec {
5355 let (ready, has_child) = ready_child_wait(guest, spec).await;
5356 if ready.is_none() && has_child {
5357 guest.memory().write_value(info, &GuestSiginfo::zeroed())?;
5358 return Ok(0);
5359 }
5360 if ready.is_none() && complete_lineage {
5361 return Err(Errno::ECHILD.into());
5362 }
5363 ready
5364 } else {
5365 None
5366 };
5367 if let Some(child) = selected {
5368 let _ = await_exact_child_physical_exit(guest, child).await;
5369 }
5370 let effective_call = match selected {
5371 Some(child) => call.with_which(libc::P_PID as i32).with_pid(
5372 self.guest_visible_pid(guest, reverie::syscalls::Sysno::waitid, child)
5373 .await?,
5374 ),
5375 None => call,
5376 };
5377 loop {
5378 guest.memory().write_value(info, &GuestSiginfo::zeroed())?;
5379 let value = match guest.inject_with_retry(effective_call).await {
5380 Ok(value) => value,
5381 Err(Errno::ECHILD) if selected.is_some() => {
5382 let child = selected.expect("selected child checked above");
5383 let _ = consume_child_wait(guest, child).await;
5384 if terminal_spec.is_some_and(child_wait_can_retry_after_stale) {
5385 continue 'select_child;
5386 }
5387 return Err(Errno::ECHILD.into());
5388 }
5389 Err(errno) => return Err(errno.into()),
5390 };
5391 let info_value: GuestSiginfo = guest.memory().read_value(info)?;
5392 let child_pid = info_value.pid();
5393 if child_pid == 0 && selected.is_some() {
5394 yield_once().await;
5395 continue;
5396 }
5397 let consumed = child_pid != 0
5398 && call.options() & libc::WNOWAIT == 0
5399 && waitid_code_is_termination(info_value.code());
5400 let child = self
5401 .task_of_guest_pid(guest, reverie::syscalls::Sysno::waitid, child_pid)
5402 .await?;
5403 let result = finish_waitid_result(guest, call, value, info_value.0, child)?;
5404 if consumed {
5405 let _ = consume_child_wait(guest, child).await;
5406 }
5407 return Ok(result);
5408 }
5409 }
5410 }
5411
5412 {
5413 let blocked_mask = blocked_signal_mask();
5421 let mut stack = guest.stack().await;
5422 let blocked_mask_addr = stack.push(blocked_mask);
5423 let old_mask_addr = stack.reserve::<KernelSigset>();
5424 let action_addr = stack.reserve::<KernelSigaction>();
5425 let _mask_guard = stack.commit()?;
5426 let guest_signal_mask =
5427 block_signals_for_disposition(guest, blocked_mask_addr, old_mask_addr).await?;
5428 let inspect_signal_action = guest
5429 .config()
5430 .backend
5431 .requires_thread_directed_process_signals;
5432
5433 let poll_call = call.with_options(call.options() | libc::WNOHANG);
5434 let mut pending_signal = None;
5435 let result: Result<i64, Error> = loop {
5436 let managed_spec = managed_terminal_spec;
5447 let status = if let Some(spec) = managed_spec {
5451 wait_for_child_lifecycle(guest, spec).await
5452 } else {
5453 resource_request(guest, rsrc.clone()).await
5454 };
5455 if pending_signal.is_none() {
5456 pending_signal = wait_signal_disposition(
5457 guest,
5458 status,
5459 &guest_signal_mask,
5460 action_addr,
5461 inspect_signal_action,
5462 )
5463 .await?;
5464 }
5465 let (ready, has_child) = if let Some(spec) = managed_spec {
5466 ready_child_wait(guest, spec).await
5467 } else {
5468 (None, true)
5469 };
5470 if let Some(child) = ready {
5471 let _ = await_exact_child_physical_exit(guest, child).await;
5472 if let Err(error) = guest.memory().write_value(info, &GuestSiginfo::zeroed()) {
5473 break Err(error.into());
5474 }
5475 let pid = match self
5476 .guest_visible_pid(guest, reverie::syscalls::Sysno::waitid, child)
5477 .await
5478 {
5479 Ok(pid) => pid,
5480 Err(error) => break Err(error),
5481 };
5482 let exact_call = call.with_which(libc::P_PID as i32).with_pid(pid);
5483 match guest.inject_with_retry(exact_call).await {
5484 Ok(value) => {
5485 let info_value = match guest.memory().read_value(info) {
5486 Ok(value) => value,
5487 Err(error) => break Err(error.into()),
5488 };
5489 let reported = match self
5490 .task_of_guest_pid(
5491 guest,
5492 reverie::syscalls::Sysno::waitid,
5493 info_value.pid(),
5494 )
5495 .await
5496 {
5497 Ok(task) => task,
5498 Err(error) => break Err(error),
5499 };
5500 break finish_waitid_result(guest, call, value, info_value.0, reported);
5501 }
5502 Err(Errno::ECHILD) => {
5503 let _ = consume_child_wait(guest, child).await;
5504 if managed_spec.is_some_and(child_wait_can_retry_after_stale) {
5505 let (next_ready, _) =
5506 ready_child_wait(guest, managed_spec.expect("managed spec"))
5507 .await;
5508 if stale_any_wait_must_interrupt(
5509 pending_signal.is_some(),
5510 next_ready,
5511 ) {
5512 break interrupted_child_wait_result(
5513 guest,
5514 call,
5515 pending_signal.expect("signal checked above"),
5516 )
5517 .await;
5518 }
5519 continue;
5520 }
5521 break Err(Errno::ECHILD.into());
5522 }
5523 Err(errno) => break Err(errno.into()),
5524 }
5525 }
5526 if managed_spec.is_some() && !has_child {
5527 break Err(Errno::ECHILD.into());
5528 }
5529
5530 if let Err(error) = guest.memory().write_value(info, &GuestSiginfo::zeroed()) {
5531 break Err(error.into());
5532 }
5533 let result = guest.inject(poll_call).await;
5534 match result {
5535 Ok(value) => {
5536 let info_value = match guest.memory().read_value(info) {
5537 Ok(value) => value,
5538 Err(error) => break Err(error.into()),
5539 };
5540 let child_pid = info_value.pid();
5543 match exact_wait_poll_decision(
5544 child_pid != 0,
5545 pending_signal.is_some(),
5546 None,
5547 ) {
5548 ExactWaitPollDecision::ChildReady => {
5549 let reported = match self
5550 .task_of_guest_pid(
5551 guest,
5552 reverie::syscalls::Sysno::waitid,
5553 child_pid,
5554 )
5555 .await
5556 {
5557 Ok(task) => task,
5558 Err(error) => break Err(error),
5559 };
5560 break finish_waitid_result(
5561 guest,
5562 call,
5563 value,
5564 info_value.0,
5565 reported,
5566 );
5567 }
5568 ExactWaitPollDecision::Interrupted => {
5569 break interrupted_child_wait_result(
5570 guest,
5571 call,
5572 pending_signal.expect("signal checked above"),
5573 )
5574 .await;
5575 }
5576 ExactWaitPollDecision::Retry => {}
5577 ExactWaitPollDecision::AwaitPhysicalExit
5578 | ExactWaitPollDecision::ReapAfterLogicalExit => unreachable!(),
5579 }
5580 if managed_spec.is_some() {
5581 if !has_child {
5582 break Ok(value);
5583 }
5584 continue;
5585 }
5586 rsrc.poll_attempt += 1;
5587 trace!(
5588 "Retry #{} for waitid because no child state is ready",
5589 rsrc.poll_attempt
5590 );
5591 record_retry_event(guest, poll_call).await;
5592 }
5593 Err(Errno::ERESTARTSYS) if pending_signal.is_some() => {
5594 break Err(Errno::EINTR.into());
5595 }
5596 Err(errno) => break Err(errno.into()),
5597 }
5598 };
5599
5600 restore_signals_after_disposition(guest, old_mask_addr).await?;
5601 if result.is_ok() && call.options() & libc::WNOWAIT == 0 {
5602 let info_value: GuestSiginfo = guest.memory().read_value(info)?;
5603 let child_pid = info_value.pid();
5604 if child_pid != 0 && waitid_code_is_termination(info_value.code()) {
5605 let child = self
5606 .task_of_guest_pid(guest, reverie::syscalls::Sysno::waitid, child_pid)
5607 .await?;
5608 let _ = consume_child_wait(guest, child).await;
5609 }
5610 }
5611 result
5612 }
5613 }
5614
5615 pub async fn handle_sched_setaffinity<G: Guest<Self>>(
5622 &self,
5623 guest: &mut G,
5624 call: syscalls::SchedSetaffinity,
5625 ) -> Result<i64, Error> {
5626 if let Some(cpus) = self.cfg.virtual_cpus {
5627 return self
5628 .virtual_sched_setaffinity(guest, call, cpus.get())
5629 .await;
5630 }
5631 let size_bytes = call.len() as usize;
5632 if size_bytes == 0 {
5633 return Err(Errno::EINVAL.into());
5634 }
5635
5636 let mask = call.mask().ok_or(Errno::EFAULT)?;
5637 let mask: Addr<u8> = mask.cast();
5638 let mut requested = [0u8; VIRTUAL_CPUSET_BYTES];
5639 let bytes_to_read = size_bytes.min(VIRTUAL_CPUSET_BYTES);
5640 guest
5641 .memory()
5642 .read_exact(mask, &mut requested[..bytes_to_read])?;
5643 info!(
5644 "Suppressing sched_setaffinity mask {:?}; affinity remains virtual CPU 0",
5645 requested
5646 );
5647 Ok(0)
5648 }
5649
5650 pub async fn handle_sched_getaffinity<G: Guest<Self>>(
5664 &self,
5665 guest: &mut G,
5666 call: syscalls::SchedGetaffinity,
5667 ) -> Result<i64, Error> {
5668 if let Some(cpus) = self.cfg.virtual_cpus {
5669 return self
5670 .virtual_sched_getaffinity(guest, call, cpus.get())
5671 .await;
5672 }
5673 let size_bytes: usize = call.len() as usize;
5674 if size_bytes == 0 || !size_bytes.is_multiple_of(std::mem::size_of::<libc::c_ulong>()) {
5675 return Err(Errno::EINVAL.into());
5676 }
5677 let copied = size_bytes.min(VIRTUAL_CPUSET_BYTES);
5678
5679 let mut cpu_set = [0u8; VIRTUAL_CPUSET_BYTES];
5683 cpu_set[0] = 1;
5684
5685 info!(
5686 "Suppressing sched_getaffinity and returning {}-byte virtualized result, {:?}",
5687 copied,
5688 &cpu_set[..copied]
5689 );
5690 if let Some(mask) = call.mask() {
5691 let mask: AddrMut<u8> = mask.cast();
5692 guest.memory().write_exact(mask, &cpu_set[..copied])?;
5693 Ok(copied as i64)
5699 } else {
5700 Err(Error::Errno(Errno::EFAULT))
5701 }
5702 }
5703
5704 pub async fn handle_sched_getparam<G: Guest<Self>>(
5712 &self,
5713 guest: &mut G,
5714 call: syscalls::SchedGetparam,
5715 ) -> Result<i64, Error> {
5716 if let Some(param) = call.param() {
5717 write_sched_getparam(&mut guest.memory(), param)?;
5718 }
5719 Ok(0)
5720 }
5721
5722 pub async fn handle_sched_rr_get_interval<G: Guest<Self>>(
5729 &self,
5730 guest: &mut G,
5731 call: syscalls::SchedRrGetInterval,
5732 ) -> Result<i64, Error> {
5733 if let Some(tp) = call.tp() {
5734 let t = Timespec {
5735 tv_sec: 0,
5736 tv_nsec: 0,
5737 };
5738 guest.memory().write_value(tp, &t)?;
5739 }
5740 Ok(0)
5741 }
5742
5743 pub async fn handle_sched_getattr<G: Guest<Self>>(
5751 &self,
5752 guest: &mut G,
5753 call: syscalls::SchedGetattr,
5754 ) -> Result<i64, Error> {
5755 if call.flags() != 0 {
5757 return Err(Errno::EINVAL.into());
5758 }
5759 let attr_size = std::mem::size_of::<libc::sched_attr>();
5761 if (call.size() as usize) < attr_size {
5762 return Err(Errno::EINVAL.into());
5763 }
5764 let attr = call.attr().ok_or(Errno::EINVAL)?;
5765 let mut sa: libc::sched_attr = unsafe { std::mem::zeroed() };
5768 sa.size = attr_size as u32;
5769 sa.sched_policy = libc::SCHED_OTHER as u32;
5770 let bytes = unsafe {
5773 std::slice::from_raw_parts(&sa as *const libc::sched_attr as *const u8, attr_size)
5774 };
5775 let dst: AddrMut<u8> = attr.cast();
5776 guest.memory().write_exact(dst, bytes)?;
5777 info!(
5778 "Emulating sched_getattr(pid={}): fixed SCHED_OTHER, nice 0, priority 0",
5779 call.pid()
5780 );
5781 Ok(0)
5782 }
5783
5784 pub async fn handle_sched_setattr<G: Guest<Self>>(
5846 &self,
5847 guest: &mut G,
5848 call: syscalls::SchedSetattr,
5849 ) -> Result<i64, Error> {
5850 if call.flags() != 0 || call.pid() < 0 {
5855 return Err(Errno::EINVAL.into());
5856 }
5857 let attr = call.attr().ok_or(Errno::EINVAL)?;
5858
5859 let declared: u32 = guest
5868 .memory()
5869 .read_value(attr.cast())
5870 .map_err(|_| Errno::EFAULT)?;
5871
5872 fn refuse_too_big<S: MemoryAccess>(mut memory: S, attr: AddrMut<libc::c_void>) -> Error {
5878 let _ = memory.write_value(attr.cast::<u32>(), &SCHED_ATTR_KERNEL_SIZE);
5879 Errno::E2BIG.into()
5880 }
5881
5882 let size = match sched_attr_effective_size(declared) {
5883 Ok(size) => size,
5884 Err(()) => {
5885 let back = call.attr().ok_or(Errno::EINVAL)?;
5886 return Err(refuse_too_big(guest.memory(), back));
5887 }
5888 };
5889
5890 if size > SCHED_ATTR_KERNEL_SIZE {
5896 let base: AddrMut<u8> = call.attr().ok_or(Errno::EINVAL)?.cast();
5897 match scan_tail_is_zeroed(
5898 &guest.memory(),
5899 base,
5900 SCHED_ATTR_KERNEL_SIZE as usize,
5901 (size - SCHED_ATTR_KERNEL_SIZE) as usize,
5902 ) {
5903 TailVerdict::AllZero => {}
5904 TailVerdict::NotZeroed => {
5905 let back = call.attr().ok_or(Errno::EINVAL)?;
5906 return Err(refuse_too_big(guest.memory(), back));
5907 }
5908 TailVerdict::Faulted => return Err(Errno::EFAULT.into()),
5909 }
5910 }
5911
5912 let copied = std::cmp::min(size, SCHED_ATTR_KERNEL_SIZE) as usize;
5916 let mut raw = [0u8; SCHED_ATTR_KERNEL_SIZE as usize];
5917 let base: AddrMut<u8> = call.attr().ok_or(Errno::EINVAL)?.cast();
5918 guest
5919 .memory()
5920 .read_exact(base, &mut raw[..copied])
5921 .map_err(|_| Errno::EFAULT)?;
5922 let field32 = |offset: usize| -> u32 {
5923 u32::from_ne_bytes(raw[offset..offset + 4].try_into().expect("4 bytes"))
5924 };
5925 let field64 = |offset: usize| -> u64 {
5926 u64::from_ne_bytes(raw[offset..offset + 8].try_into().expect("8 bytes"))
5927 };
5928 let fields = SchedAttrFields {
5929 policy: field32(SCHED_ATTR_OFF_POLICY),
5930 sched_flags: field64(SCHED_ATTR_OFF_FLAGS),
5931 priority: field32(SCHED_ATTR_OFF_PRIORITY),
5932 runtime: field64(SCHED_ATTR_OFF_RUNTIME),
5933 deadline: field64(SCHED_ATTR_OFF_DEADLINE),
5934 period: field64(SCHED_ATTR_OFF_PERIOD),
5935 };
5936
5937 validate_sched_attr_before_lookup(size, &fields)?;
5939
5940 let named = if call.pid() > 0 && guest.thread_state().nested_pid_namespace {
5953 match crate::tool_global::guest_pid(guest, call.pid()).await {
5954 GuestPidAnswer::Task { task, .. } => Some(task),
5955 GuestPidAnswer::NoSuchPid => None,
5956 GuestPidAnswer::Untranslatable(reason) => {
5957 return Err(self
5958 .refuse_nested_pid(guest, reverie::syscalls::Sysno::sched_setattr, &reason)
5959 .await);
5960 }
5961 }
5962 } else {
5963 Some(DetTid::from_raw(call.pid()))
5964 };
5965 if call.pid() != 0 {
5966 match named {
5967 Some(task) if thread_is_live(guest, task).await => {}
5968 _ => return Err(Errno::ESRCH.into()),
5969 }
5970 }
5971
5972 validate_sched_attr_after_lookup(&fields)?;
5974
5975 info!(
5976 "Suppressing sched_setattr(pid={}, flags={}); Linux scheduler attributes are virtual",
5977 call.pid(),
5978 call.flags()
5979 );
5980 Ok(0)
5981 }
5982
5983 pub async fn handle_ioprio_set<G: Guest<Self>>(
5991 &self,
5992 _guest: &mut G,
5993 call: syscalls::IoprioSet,
5994 ) -> Result<i64, Error> {
5995 info!(
5996 "Suppressing ioprio_set(which={}, who={}, priority={}); I/O priority is virtual",
5997 call.which(),
5998 call.who(),
5999 call.priority()
6000 );
6001 Ok(0)
6002 }
6003
6004 pub async fn handle_ioprio_get<G: Guest<Self>>(
6011 &self,
6012 _guest: &mut G,
6013 call: syscalls::IoprioGet,
6014 ) -> Result<i64, Error> {
6015 let priority = virtual_ioprio(call.which())?;
6016
6017 info!(
6018 "Emulating ioprio_get(which={}, who={}): fixed priority {}",
6019 call.which(),
6020 call.who(),
6021 priority
6022 );
6023 Ok(priority)
6024 }
6025}
6026
6027struct GuestRobustEffects<'a, G, T> {
6033 guest: &'a mut G,
6034 dettid: DetTid,
6035 staged_wakes: Option<&'a mut Vec<RobustListWake>>,
6036 marked: &'a mut HashMap<FutexID, u32>,
6043 tool: PhantomData<T>,
6044}
6045
6046impl<G, T> robust_list::RobustDeathEffects for GuestRobustEffects<'_, G, T>
6047where
6048 G: Guest<Detcore<T>>,
6049 T: RecordOrReplay,
6050{
6051 fn read_u64(&mut self, address: usize) -> Option<u64> {
6052 let at = Addr::<u64>::from_raw(address)?;
6053 self.guest.memory().read_value::<_, u64>(at).ok()
6054 }
6055
6056 fn read_u32(&mut self, address: usize) -> Option<u32> {
6057 let at = Addr::<u32>::from_raw(address)?;
6058 let value = self.guest.memory().read_value::<_, u32>(at).ok()?;
6059 let futexid = self.guest.thread_state().futex_id(address, false);
6060 Some(self.marked.get(&futexid).copied().unwrap_or(value))
6061 }
6062
6063 fn compare_and_swap(
6064 &mut self,
6065 address: usize,
6066 expected: u32,
6067 desired: u32,
6068 ) -> robust_list::FutexCasOutcome {
6069 use robust_list::FutexCasOutcome;
6070
6071 let Some(read_at) = Addr::<u32>::from_raw(address) else {
6072 return FutexCasOutcome::Faulted;
6073 };
6074 let observed = match self.guest.memory().read_value::<_, u32>(read_at) {
6079 Ok(value) => value,
6080 Err(_) => return FutexCasOutcome::Faulted,
6081 };
6082 let futexid = self.guest.thread_state().futex_id(address, false);
6083 let observed = self.marked.get(&futexid).copied().unwrap_or(observed);
6084 if observed != expected {
6085 return FutexCasOutcome::Changed(observed);
6086 }
6087 self.marked.insert(futexid, desired);
6088 debug!(
6095 "[detcore, dtid {}] robust-list owner death: leaving futex word {:#x} for backend exit cleanup",
6096 self.dettid, address,
6097 );
6098 FutexCasOutcome::Matched
6099 }
6100
6101 async fn wake_one(&mut self, address: usize, observed: u32) {
6102 let futexid = self.guest.thread_state().futex_id(address, false);
6105 if let Some(wakes) = self.staged_wakes.as_mut() {
6106 wakes.push(RobustListWake { futex: futexid });
6107 debug!(
6108 "[detcore, dtid {}] staged robust-list owner-death wake until physical exit",
6109 self.dettid,
6110 );
6111 return;
6112 }
6113 let woken = match futex_action(
6114 self.guest,
6115 FutexAction::WakeRequest(1),
6116 &futexid,
6117 observed as i32,
6118 u32::MAX,
6119 )
6120 .await
6121 {
6122 Some(SchedValue::Value(count)) => count,
6123 Some(SchedValue::PiWaiterReached { woken }) => woken,
6126 Some(SchedValue::TimeOut)
6128 | Some(SchedValue::Signaled(_))
6129 | Some(SchedValue::Requeued { .. })
6130 | Some(SchedValue::PiFutex(_))
6131 | Some(SchedValue::PiTaken(_))
6132 | None => 0,
6133 };
6134 info!(
6138 "[detcore, dtid {}] robust-list owner death woke {} waiter(s) on futex {:?}",
6139 self.dettid, woken, futexid,
6140 );
6141 let _ = futex_action(
6142 self.guest,
6143 FutexAction::WakeFinished(0),
6144 &futexid,
6145 observed as i32,
6146 u32::MAX,
6147 )
6148 .await;
6149 }
6150}
6151
6152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6155enum OwnerDeath {
6156 Exit,
6158 Exec,
6161}
6162
6163fn owner_death_path(cfg: &Config) -> Option<OwnerDeathPath> {
6175 if !cfg.sequentialize_threads || cfg.debug_futex_mode != BlockingMode::Precise {
6176 return None;
6177 }
6178 if cfg.backend.runs_exit_robust_list {
6179 Some(OwnerDeathPath::Kernel)
6180 } else if cfg.backend.requires_thread_directed_process_signals {
6181 Some(OwnerDeathPath::Barrier)
6182 } else {
6183 None
6184 }
6185}
6186
6187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6191enum OwnerDeathPath {
6192 Kernel,
6196 Barrier,
6206}
6207
6208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6212pub(crate) enum FutexCommand {
6213 Served,
6219 NoSys,
6223 RequeuePi(&'static str),
6229}
6230
6231pub(crate) fn classify_futex_command(futex_op: i32) -> FutexCommand {
6234 let cmd = futex_op & libc::FUTEX_CMD_MASK;
6235 if futex_op & libc::FUTEX_CLOCK_REALTIME != 0
6236 && !matches!(
6237 cmd,
6238 libc::FUTEX_WAIT_BITSET | libc::FUTEX_WAIT_REQUEUE_PI | libc::FUTEX_LOCK_PI2
6239 )
6240 {
6241 return FutexCommand::NoSys;
6242 }
6243 match cmd {
6244 libc::FUTEX_WAIT
6245 | libc::FUTEX_WAIT_BITSET
6246 | libc::FUTEX_WAKE
6247 | libc::FUTEX_WAKE_BITSET
6248 | libc::FUTEX_REQUEUE
6249 | libc::FUTEX_CMP_REQUEUE
6250 | libc::FUTEX_WAKE_OP
6251 | libc::FUTEX_LOCK_PI
6253 | libc::FUTEX_LOCK_PI2
6254 | libc::FUTEX_TRYLOCK_PI
6255 | libc::FUTEX_UNLOCK_PI => FutexCommand::Served,
6256 libc::FUTEX_WAIT_REQUEUE_PI => FutexCommand::RequeuePi("FUTEX_WAIT_REQUEUE_PI"),
6257 libc::FUTEX_CMP_REQUEUE_PI => FutexCommand::RequeuePi("FUTEX_CMP_REQUEUE_PI"),
6258 _ => FutexCommand::NoSys,
6259 }
6260}
6261
6262const POLLING_REFUSAL: &str = "--debug-futex-mode polling does not model futex waiter lists";
6265
6266fn pi_lock_command_name(command: i32) -> Option<&'static str> {
6269 match command {
6270 libc::FUTEX_LOCK_PI => Some("FUTEX_LOCK_PI"),
6271 libc::FUTEX_LOCK_PI2 => Some("FUTEX_LOCK_PI2"),
6272 libc::FUTEX_TRYLOCK_PI => Some("FUTEX_TRYLOCK_PI"),
6273 libc::FUTEX_UNLOCK_PI => Some("FUTEX_UNLOCK_PI"),
6274 _ => None,
6275 }
6276}
6277
6278fn futex_word_is_file_backed<T: RecordOrReplay, G: Guest<Detcore<T>>>(
6283 guest: &G,
6284 address: usize,
6285) -> bool {
6286 matches!(
6287 guest.thread_state().futex_id(address, false),
6288 FutexID::Shared {
6289 object: SharedMemoryObjectId::File { .. } | SharedMemoryObjectId::OpenFile { .. },
6290 ..
6291 }
6292 )
6293}
6294
6295fn write_futex_word<T: RecordOrReplay, G: Guest<Detcore<T>>>(
6300 guest: &mut G,
6301 address: usize,
6302 value: u32,
6303) -> Result<(), Error> {
6304 let word = AddrMut::<u32>::from_raw(address).ok_or(Errno::EFAULT)?;
6305 guest
6306 .memory()
6307 .write_value(word, &value)
6308 .map_err(|_| Error::Errno(Errno::EFAULT))
6309}
6310
6311const NESTED_PID_NAMESPACE_REFUSAL: &str = "the caller is in a nested PID namespace, whose \
6314 priority-inheritance futex words hold TIDs Detcore does not translate";
6315
6316const FILE_BACKED_WORD_REFUSAL: &str = "its word is in a file-backed shared mapping that a process \
6319 outside Hermit could update concurrently, and Detcore cannot apply the operation atomically";
6320
6321fn futex_val2(call: &syscalls::Futex) -> u32 {
6326 call.timeout().map_or(0, Addr::as_raw) as u32
6327}
6328
6329fn futex_uaddr2(call: &syscalls::Futex) -> usize {
6331 call.uaddr2().map_or(0, AddrMut::as_raw)
6332}
6333
6334fn futex_uaddr(call: &syscalls::Futex) -> usize {
6337 call.uaddr().map_or(0, AddrMut::as_raw)
6338}
6339
6340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6342enum KeyAccess {
6343 Read,
6345 Write,
6347}
6348
6349pub(crate) fn user_ptr_max() -> usize {
6356 static MAX: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6357 *MAX.get_or_init(|| {
6358 let accepted = |address: usize| {
6359 let ret = unsafe {
6361 libc::syscall(
6362 libc::SYS_futex,
6363 address,
6364 libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,
6365 0,
6366 0,
6367 0,
6368 0,
6369 )
6370 };
6371 ret >= 0
6372 };
6373 let bad_start = if cfg!(target_arch = "aarch64") {
6378 1usize << 55
6379 } else {
6380 1usize << 57
6381 };
6382 let (mut ok, mut bad) = (0usize, bad_start);
6383 while bad - ok > 4 {
6384 let mid = (ok + (bad - ok) / 2) & !3;
6385 if accepted(mid) { ok = mid } else { bad = mid }
6386 }
6387 ok
6388 })
6389}
6390
6391async fn futex_key<G: Guest<Detcore<T>>, T: RecordOrReplay>(
6402 guest: &mut G,
6403 address: usize,
6404 private: bool,
6405 access: KeyAccess,
6406) -> Result<FutexID, Error> {
6407 if !address.is_multiple_of(std::mem::size_of::<u32>()) {
6408 return Err(Error::Errno(Errno::EINVAL));
6409 }
6410 if address > user_ptr_max() {
6411 return Err(Error::Errno(Errno::EFAULT));
6412 }
6413 if !private {
6414 probe_shared_futex_key(guest, address, access).await?;
6415 }
6416 Ok(guest.thread_state().futex_id(address, private))
6417}
6418
6419async fn probe_shared_futex_key<G: Guest<Detcore<T>>, T: RecordOrReplay>(
6433 guest: &mut G,
6434 address: usize,
6435 access: KeyAccess,
6436) -> Result<(), Error> {
6437 let probe = shared_futex_key_probe(address, access);
6438 match run_shared_futex_key_probe(guest, probe, access, |guest, probe| guest.inject(probe)).await
6439 {
6440 SharedKeyProbe::Admitted => Ok(()),
6441 SharedKeyProbe::Refused(errno) => Err(Error::Errno(errno)),
6442 SharedKeyProbe::Interrupted(errno) => {
6443 refuse_interrupted_shared_key_probe(guest, address, errno).await
6444 }
6445 }
6446}
6447
6448fn shared_futex_key_probe(address: usize, access: KeyAccess) -> syscalls::Futex {
6451 let word = AddrMut::<libc::c_int>::from_raw(address);
6452 match access {
6453 KeyAccess::Read => syscalls::Futex::new()
6454 .with_uaddr(word)
6455 .with_futex_op(libc::FUTEX_REQUEUE)
6456 .with_val(0)
6457 .with_uaddr2(word),
6458 KeyAccess::Write => syscalls::Futex::new()
6459 .with_uaddr(word)
6460 .with_futex_op(libc::FUTEX_WAKE_OP)
6461 .with_val(0)
6462 .with_uaddr2(word)
6463 .with_val3((7 << 28) | (1 << 12)),
6464 }
6465}
6466
6467type KeyProbeAnswer<'a> =
6469 std::pin::Pin<Box<dyn std::future::Future<Output = Result<i64, Errno>> + Send + 'a>>;
6470
6471#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6473enum SharedKeyProbe {
6474 Admitted,
6477 Refused(Errno),
6479 Interrupted(Errno),
6483}
6484
6485async fn run_shared_futex_key_probe<C>(
6493 context: &mut C,
6494 probe: syscalls::Futex,
6495 access: KeyAccess,
6496 inject: for<'a> fn(&'a mut C, syscalls::Futex) -> KeyProbeAnswer<'a>,
6497) -> SharedKeyProbe {
6498 match inject(context, probe).await {
6499 Ok(_) => SharedKeyProbe::Admitted,
6500 Err(errno @ (Errno::ERESTARTSYS | Errno::EINTR)) => SharedKeyProbe::Interrupted(errno),
6501 Err(Errno::ENOSYS) if access == KeyAccess::Write => SharedKeyProbe::Admitted,
6502 Err(errno) => SharedKeyProbe::Refused(errno),
6503 }
6504}
6505
6506async fn refuse_interrupted_shared_key_probe<G: Guest<Detcore<T>>, T: RecordOrReplay>(
6517 guest: &mut G,
6518 address: usize,
6519 errno: Errno,
6520) -> ! {
6521 {
6522 use std::io::Write;
6523 let _ = writeln!(
6524 crate::util::RetryingStderr,
6525 "hermit: a signal interrupted Detcore's shared futex key check for word {:#x} in thread {} ({}); \
6526 refusing the run rather than retrying it, which could lose that signal \
6527 (https://github.com/rrnewton/hermit/pull/4020)",
6528 address,
6529 guest.thread_state().dettid,
6530 errno,
6531 );
6532 }
6533 crate::tool_global::unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT)
6534 .await
6535}
6536
6537fn read_futex_word<G: Guest<Detcore<T>>, T: RecordOrReplay>(
6548 guest: &mut G,
6549 address: usize,
6550) -> Result<i32, Error> {
6551 let word = Addr::<u8>::from_raw(address).ok_or(Errno::EFAULT)?;
6552 let mut bytes = [0u8; 4];
6553 if guest
6554 .memory()
6555 .read_exact_with_user_access(word, &mut bytes)
6556 .is_ok()
6557 {
6558 return Ok(i32::from_ne_bytes(bytes));
6559 }
6560 if !guest.config().backend.host_task_reads_reach_guest_threads
6564 || !mapping_is_writable(guest.pid().as_raw(), address)
6565 {
6566 return Err(Error::Errno(Errno::EFAULT));
6567 }
6568 let aligned = address & !(std::mem::size_of::<u64>() - 1);
6569 let pair: u64 = guest
6570 .memory()
6571 .read_value(Addr::<u64>::from_raw(aligned).ok_or(Errno::EFAULT)?)
6572 .map_err(|_| Error::Errno(Errno::EFAULT))?;
6573 let offset = address - aligned;
6574 let pair = pair.to_ne_bytes();
6575 bytes.copy_from_slice(&pair[offset..offset + 4]);
6576 Ok(i32::from_ne_bytes(bytes))
6577}
6578
6579fn write_sched_getparam<M: MemoryAccess>(
6581 memory: &mut M,
6582 param: AddrMut<'_, syscalls::linux_abi::SchedParam>,
6583) -> Result<(), Errno> {
6584 memory.write_value(
6585 param,
6586 &syscalls::linux_abi::SchedParam { sched_priority: 0 },
6587 )
6588}
6589
6590fn mapping_is_writable(pid: i32, address: usize) -> bool {
6594 mapping_permissions(pid, address).is_some_and(|perms| perms[1] == b'w')
6595}
6596
6597fn mapping_permissions(pid: i32, address: usize) -> Option<[u8; 4]> {
6601 let maps = std::fs::read_to_string(format!("/proc/{pid}/maps")).ok()?;
6602 maps.lines().find_map(|line| {
6603 let mut fields = line.split_whitespace();
6604 let (Some(range), Some(perms)) = (fields.next(), fields.next()) else {
6605 return None;
6606 };
6607 let (start, end) = range.split_once('-')?;
6608 let (Ok(start), Ok(end)) = (
6609 usize::from_str_radix(start, 16),
6610 usize::from_str_radix(end, 16),
6611 ) else {
6612 return None;
6613 };
6614 if !(start..end).contains(&address) {
6615 return None;
6616 }
6617 perms.as_bytes().get(..4)?.try_into().ok()
6618 })
6619}
6620
6621fn read_user_timespec<G: Guest<Detcore<T>>, T: RecordOrReplay>(
6629 guest: &mut G,
6630 address: usize,
6631) -> Result<Timespec, Error> {
6632 let size = std::mem::size_of::<Timespec>();
6633 let last = address.checked_add(size - 1).ok_or(Errno::EFAULT)?;
6634 if last > user_ptr_max() {
6635 return Err(Error::Errno(Errno::EFAULT));
6636 }
6637 if !guest.config().backend.host_task_reads_reach_guest_threads {
6638 let mut bytes = [0u8; std::mem::size_of::<Timespec>()];
6641 guest
6642 .memory()
6643 .read_exact_with_user_access(
6644 Addr::<u8>::from_raw(address).ok_or(Errno::EFAULT)?,
6645 &mut bytes,
6646 )
6647 .map_err(|_| Error::Errno(Errno::EFAULT))?;
6648 let field = |at: usize| -> i64 {
6649 let mut raw = [0u8; 8];
6650 raw.copy_from_slice(&bytes[at..at + 8]);
6651 i64::from_ne_bytes(raw)
6652 };
6653 return Ok(Timespec {
6654 tv_sec: field(0) as libc::time_t,
6655 tv_nsec: field(8) as libc::c_long,
6656 });
6657 }
6658 let page = 4096;
6660 let pid = guest.pid().as_raw();
6661 let mut cursor = address & !(page - 1);
6662 while cursor <= last {
6663 let probe = cursor.max(address);
6664 let readable = mapping_permissions(pid, probe)
6665 .is_some_and(|perms| perms[0] == b'r' || perms[1] == b'w');
6666 if !readable {
6667 return Err(Error::Errno(Errno::EFAULT));
6668 }
6669 cursor += page;
6670 }
6671 let start = address & !7;
6675 let mut bytes = Vec::with_capacity(last + 1 - start);
6676 let mut word = start;
6677 while word <= last {
6678 let value: u64 = guest
6679 .memory()
6680 .read_value(Addr::<u64>::from_raw(word).ok_or(Errno::EFAULT)?)
6681 .map_err(|_| Error::Errno(Errno::EFAULT))?;
6682 bytes.extend_from_slice(&value.to_ne_bytes());
6683 word += 8;
6684 }
6685 let field = |at: usize| -> i64 {
6686 let mut raw = [0u8; 8];
6687 raw.copy_from_slice(&bytes[address - start + at..address - start + at + 8]);
6688 i64::from_ne_bytes(raw)
6689 };
6690 Ok(Timespec {
6691 tv_sec: field(0) as libc::time_t,
6692 tv_nsec: field(8) as libc::c_long,
6693 })
6694}
6695
6696const FUTEX_OP_SET: u32 = 0;
6698const FUTEX_OP_ADD: u32 = 1;
6699const FUTEX_OP_OR: u32 = 2;
6700const FUTEX_OP_ANDN: u32 = 3;
6701const FUTEX_OP_XOR: u32 = 4;
6702const FUTEX_OP_OPARG_SHIFT: u32 = 8;
6703const FUTEX_OP_CMP_EQ: u32 = 0;
6704const FUTEX_OP_CMP_NE: u32 = 1;
6705const FUTEX_OP_CMP_LT: u32 = 2;
6706const FUTEX_OP_CMP_GE: u32 = 3;
6707const FUTEX_OP_CMP_LE: u32 = 4;
6708const FUTEX_OP_CMP_GT: u32 = 5;
6709
6710#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6713pub(crate) struct FutexWakeOp {
6714 op: u32,
6715 cmp: u32,
6716 oparg: i32,
6717 cmparg: i32,
6718}
6719
6720impl FutexWakeOp {
6721 pub(crate) fn decode(encoded: u32) -> Self {
6722 let sign_extend_12 = |v: u32| ((v << 20) as i32) >> 20;
6723 let op = (encoded & 0x7000_0000) >> 28;
6724 let cmp = (encoded & 0x0f00_0000) >> 24;
6725 let mut oparg = sign_extend_12((encoded & 0x00ff_f000) >> 12);
6726 let cmparg = sign_extend_12(encoded & 0x0000_0fff);
6727 if encoded & (FUTEX_OP_OPARG_SHIFT << 28) != 0 {
6728 if !(0..=31).contains(&oparg) {
6730 oparg &= 31;
6731 }
6732 oparg = 1i32.wrapping_shl(oparg as u32);
6733 }
6734 FutexWakeOp {
6735 op,
6736 cmp,
6737 oparg,
6738 cmparg,
6739 }
6740 }
6741
6742 pub(crate) fn has_known_operation(&self) -> bool {
6745 self.apply(0).is_some()
6746 }
6747
6748 pub(crate) fn apply(&self, old: i32) -> Option<i32> {
6750 Some(match self.op {
6751 FUTEX_OP_SET => self.oparg,
6752 FUTEX_OP_ADD => old.wrapping_add(self.oparg),
6753 FUTEX_OP_OR => old | self.oparg,
6754 FUTEX_OP_ANDN => old & !self.oparg,
6755 FUTEX_OP_XOR => old ^ self.oparg,
6756 _ => return None,
6757 })
6758 }
6759
6760 pub(crate) fn compare(&self, old: i32) -> Option<bool> {
6763 Some(match self.cmp {
6764 FUTEX_OP_CMP_EQ => old == self.cmparg,
6765 FUTEX_OP_CMP_NE => old != self.cmparg,
6766 FUTEX_OP_CMP_LT => old < self.cmparg,
6767 FUTEX_OP_CMP_GE => old >= self.cmparg,
6768 FUTEX_OP_CMP_LE => old <= self.cmparg,
6769 FUTEX_OP_CMP_GT => old > self.cmparg,
6770 _ => return None,
6771 })
6772 }
6773}
6774
6775#[cfg(test)]
6776mod tests {
6777
6778 struct SchedParamMemory {
6779 bytes: [u8; 16],
6780 writable_end: usize,
6781 requested: Vec<usize>,
6782 }
6783 impl MemoryAccess for SchedParamMemory {
6784 fn read_vectored(
6785 &self,
6786 _: &[std::io::IoSlice<'_>],
6787 _: &mut [std::io::IoSliceMut<'_>],
6788 ) -> Result<usize, Errno> {
6789 panic!("sched_getparam must not read guest memory")
6790 }
6791 fn write_vectored(
6792 &mut self,
6793 _: &[std::io::IoSlice<'_>],
6794 _: &mut [std::io::IoSliceMut<'_>],
6795 ) -> Result<usize, Errno> {
6796 panic!("the offset-based mock must use write directly")
6797 }
6798 fn write(&mut self, address: AddrMut<u8>, source: &[u8]) -> Result<usize, Errno> {
6799 let start = address.as_raw();
6802 let want = source.len();
6803 self.requested.push(want);
6804 if start
6805 .checked_add(want)
6806 .is_none_or(|end| end > self.writable_end)
6807 {
6808 return Err(Errno::EFAULT);
6809 }
6810 self.bytes[start..start + want].copy_from_slice(source);
6811 Ok(want)
6812 }
6813 }
6814
6815 #[test]
6816 fn sched_getparam_writes_only_kernel_priority_before_guard() {
6817 let mut memory = SchedParamMemory {
6818 bytes: [0xA5; 16],
6819 writable_end: 5,
6820 requested: Vec::new(),
6821 };
6822 let param = AddrMut::<syscalls::linux_abi::SchedParam>::from_raw(1).unwrap();
6823 assert_eq!(write_sched_getparam(&mut memory, param), Ok(()));
6824 assert_eq!(memory.requested, vec![4]);
6825 assert_eq!(&memory.bytes[1..5], &0i32.to_ne_bytes());
6826 assert_eq!(memory.bytes[0], 0xA5);
6827 assert_eq!(&memory.bytes[5..], &[0xA5; 11]);
6828 }
6829
6830 #[test]
6831 fn sched_getparam_short_writable_kernel_word_remains_efault() {
6832 let mut memory = SchedParamMemory {
6833 bytes: [0xA5; 16],
6834 writable_end: 4,
6835 requested: Vec::new(),
6836 };
6837 let param = AddrMut::<syscalls::linux_abi::SchedParam>::from_raw(1).unwrap();
6838 assert_eq!(write_sched_getparam(&mut memory, param), Err(Errno::EFAULT));
6839 assert_eq!(memory.requested, vec![4]);
6840 assert_eq!(memory.bytes, [0xA5; 16]);
6841 }
6842
6843 #[test]
6849 fn a_discarded_copy_is_left_out_even_when_committed() {
6850 let chld = kernel_sigset_bit(libc::SIGCHLD);
6851 let usr1 = kernel_sigset_bit(libc::SIGUSR1);
6852 let state = KernelSignalState {
6853 pending: chld | usr1,
6854 thread_pending: chld,
6855 shared_pending: usr1,
6856 ..Default::default()
6857 };
6858 assert_eq!(with_kept_copies(&state, chld, 0, chld | usr1, chld), usr1);
6859 assert_eq!(with_kept_copies(&state, 0, usr1, chld | usr1, usr1), chld);
6860 assert_eq!(with_kept_copies(&state, 0, 0, chld | usr1, 0), chld | usr1);
6861 }
6862 use std::io::Read;
6863 use std::io::Seek;
6864 use std::os::fd::AsRawFd;
6865
6866 use reverie::GlobalRPC;
6867 use reverie::GlobalTool;
6868 use reverie::Tid;
6869 use reverie::Tool;
6870
6871 use super::*;
6872
6873 #[test]
6877 fn owner_death_path_is_tied_to_the_backends_that_have_its_properties() {
6878 let path = |backend: reverie::BackendCapabilities| {
6879 let mut cfg = Config::default().with_backend(|capabilities| *capabilities = backend);
6880 cfg.sequentialize_threads = true;
6881 cfg.debug_futex_mode = BlockingMode::Precise;
6882 owner_death_path(&cfg)
6883 };
6884 assert_eq!(
6885 path(reverie::BackendCapabilities::PTRACE),
6886 Some(OwnerDeathPath::Kernel)
6887 );
6888 assert_eq!(
6889 path(reverie::BackendCapabilities::DBT),
6890 Some(OwnerDeathPath::Barrier)
6891 );
6892 for backend in [
6893 reverie::BackendCapabilities::KVM,
6894 reverie::BackendCapabilities::SABRE,
6895 reverie::BackendCapabilities::LITEINST_IN_GUEST,
6896 ] {
6897 assert_eq!(path(backend), None, "{backend:?}");
6898 }
6899 }
6900
6901 #[test]
6907 fn each_configuration_takes_its_sched_yield_route() {
6908 let path = || Some(std::path::PathBuf::from("record.json"));
6909 let cases = [
6910 (Config::default(), SchedYieldRoute::EndTimeslice),
6911 (
6913 Config {
6914 ..Config::default()
6915 }
6916 .with_backend(|backend| {
6917 backend.delivers_preemption_timer_events = true;
6918 backend.runs_guest_code_between_injections = false;
6919 }),
6920 SchedYieldRoute::EndTimeslice,
6921 ),
6922 (
6923 Config {
6924 chaos: true,
6925 replay_schedule_from: path(),
6926 ..Config::default()
6927 }
6928 .with_backend(|backend| {
6929 backend.delivers_preemption_timer_events = true;
6930 backend.runs_guest_code_between_injections = false;
6931 }),
6932 SchedYieldRoute::WeakYield,
6933 ),
6934 (
6935 Config {
6936 chaos: true,
6937 ..Config::default()
6938 }
6939 .with_backend(|backend| {
6940 backend.delivers_preemption_timer_events = true;
6941 backend.runs_guest_code_between_injections = false;
6942 }),
6943 SchedYieldRoute::ChaosYieldEpoch,
6944 ),
6945 (
6946 Config {
6947 chaos: true,
6948 sched_heuristic: SchedHeuristic::Random,
6949 ..Config::default()
6950 }
6951 .with_backend(|backend| {
6952 backend.delivers_preemption_timer_events = true;
6953 backend.runs_guest_code_between_injections = false;
6954 }),
6955 SchedYieldRoute::WeakYield,
6956 ),
6957 (
6958 Config {
6959 chaos: true,
6960 sched_heuristic: SchedHeuristic::StickyRandom,
6961 ..Config::default()
6962 }
6963 .with_backend(|backend| {
6964 backend.delivers_preemption_timer_events = true;
6965 backend.runs_guest_code_between_injections = false;
6966 }),
6967 SchedYieldRoute::ChaosYieldEpoch,
6968 ),
6969 (
6970 Config {
6971 chaos: true,
6972 sched_heuristic: SchedHeuristic::ConnectBind,
6973 ..Config::default()
6974 }
6975 .with_backend(|backend| {
6976 backend.delivers_preemption_timer_events = true;
6977 backend.runs_guest_code_between_injections = false;
6978 }),
6979 SchedYieldRoute::ChaosYieldEpoch,
6980 ),
6981 (
6982 Config {
6983 chaos: true,
6984 max_timeslice: None,
6985 sched_heuristic: SchedHeuristic::Random,
6986 ..Config::default()
6987 },
6988 SchedYieldRoute::ChaosReprioritize,
6989 ),
6990 (
6992 Config {
6993 chaos: true,
6994 ..Config::default()
6995 }
6996 .with_backend(|backend| backend.delivers_preemption_timer_events = false),
6997 SchedYieldRoute::WeakYield,
6998 ),
6999 (
7000 Config {
7001 chaos: true,
7002 max_timeslice: None,
7003 ..Config::default()
7004 },
7005 SchedYieldRoute::ChaosReprioritize,
7006 ),
7007 (
7008 Config {
7009 chaos: true,
7010 replay_preemptions_from: path(),
7011 ..Config::default()
7012 }
7013 .with_backend(|backend| {
7014 backend.delivers_preemption_timer_events = true;
7015 backend.runs_guest_code_between_injections = false;
7016 }),
7017 SchedYieldRoute::ChaosYieldEpoch,
7018 ),
7019 (
7020 Config {
7021 chaos: true,
7022 replay_schedule_from: path(),
7023 ..Config::default()
7024 },
7025 SchedYieldRoute::WeakYield,
7026 ),
7027 (
7028 Config {
7029 replay_schedule_from: path(),
7030 ..Config::default()
7031 },
7032 SchedYieldRoute::WeakYield,
7033 ),
7034 (
7035 Config {
7036 replay_preemptions_from: path(),
7037 ..Config::default()
7038 },
7039 SchedYieldRoute::ReplayStrongYield,
7040 ),
7041 ];
7042 for (cfg, route) in cases {
7043 assert_eq!(
7044 sched_yield_route(&cfg),
7045 route,
7046 "chaos={} timer delivered={} heuristic={:?} max_timeslice={:?} preemptions={:?} schedule={:?}",
7047 cfg.chaos,
7048 cfg.backend.delivers_preemption_timer_events,
7049 cfg.sched_heuristic,
7050 cfg.max_timeslice,
7051 cfg.replay_preemptions_from,
7052 cfg.replay_schedule_from
7053 );
7054 }
7055 }
7056
7057 #[test]
7063 fn a_thread_reported_under_another_pid_is_read_and_alive() {
7064 let tid = Pid::from_raw(unsafe { libc::syscall(libc::SYS_gettid) } as i32);
7065 let wrong = Pid::from_raw(unsafe { libc::getppid() });
7066 assert_ne!(wrong, tid);
7067 assert!(read_kernel_signal_state(wrong, tid).is_err());
7068 assert!(read_thread_signal_state(wrong, tid).is_ok());
7069 assert!(!thread_is_gone(wrong, tid));
7070 assert!(read_thread_wait_signal_state(wrong, tid).is_ok());
7071 }
7072
7073 #[test]
7076 fn host_signal_state_reads_are_refused_only_where_guest_tids_are_not_host_tasks() {
7077 assert!(refuses_host_signal_state_reads(&BackendCapabilities::KVM));
7078 for backend in [
7079 BackendCapabilities::PTRACE,
7080 BackendCapabilities::E9PATCH,
7081 BackendCapabilities::SABRE,
7082 BackendCapabilities::DBT,
7083 BackendCapabilities::LITEINST_IN_GUEST,
7084 ] {
7085 assert!(!refuses_host_signal_state_reads(&backend));
7086 }
7087 }
7088
7089 #[test]
7093 fn a_latched_refusal_never_reads_a_host_task_signal_state() {
7094 let pid = Pid::from_raw(unsafe { libc::getpid() });
7095 let tid = Pid::from_raw(unsafe { libc::syscall(libc::SYS_gettid) } as i32);
7096 refuse_host_signal_state_reads(&BackendCapabilities::PTRACE);
7097 assert!(!host_signal_state_reads_refused());
7098 assert!(read_kernel_signal_state(pid, tid).is_ok());
7099 refuse_host_signal_state_reads(&BackendCapabilities::KVM);
7100 assert!(host_signal_state_reads_refused());
7101 assert_eq!(read_kernel_signal_state(pid, tid), Err(Errno::ENOSYS));
7102 assert_eq!(read_thread_signal_state(pid, tid), Err(Errno::ENOSYS));
7103 }
7104
7105 #[test]
7109 fn a_kept_status_file_reads_the_current_state_until_its_thread_is_forgotten() {
7110 std::thread::spawn(|| {
7111 status_files::enable();
7112 let pid = Pid::from_raw(unsafe { libc::getpid() });
7113 let tid = Pid::from_raw(unsafe { libc::syscall(libc::SYS_gettid) } as i32);
7114 let usr2 = crate::scheduler::kernel_signal_bit(libc::SIGUSR2);
7115 let before = read_kernel_signal_state(pid, tid).unwrap();
7116 assert_eq!(before.blocked & usr2, 0);
7117 assert!(status_files::keeps(pid, tid));
7118 unsafe {
7120 let mut set: libc::sigset_t = std::mem::zeroed();
7121 libc::sigemptyset(&mut set);
7122 libc::sigaddset(&mut set, libc::SIGUSR2);
7123 assert_eq!(
7124 libc::pthread_sigmask(libc::SIG_BLOCK, &set, std::ptr::null_mut()),
7125 0
7126 );
7127 }
7128 let after = read_kernel_signal_state(pid, tid).unwrap();
7129 assert_eq!(after.blocked & usr2, usr2);
7130 assert!(status_files::keeps(pid, tid));
7131 forget_thread_status(tid);
7132 assert!(!status_files::keeps(pid, tid));
7133 })
7134 .join()
7135 .unwrap();
7136 }
7137
7138 #[test]
7143 #[allow(clippy::disallowed_methods)] fn without_keeping_a_read_leaves_no_descriptor_open() {
7145 std::thread::spawn(|| {
7146 let pid = Pid::from_raw(unsafe { libc::getpid() });
7147 let tid = Pid::from_raw(unsafe { libc::syscall(libc::SYS_gettid) } as i32);
7148 assert!(read_kernel_signal_state(pid, tid).is_ok());
7149 assert!(read_kernel_signal_state(pid, tid).is_ok());
7150 assert!(status_files::kept_file(pid, tid).is_none());
7151 let status = format!("/proc/{}/task/{}/status", pid.as_raw(), tid.as_raw());
7152 for entry in std::fs::read_dir("/proc/self/fd").unwrap() {
7153 let target = std::fs::read_link(entry.unwrap().path()).unwrap_or_default();
7154 assert_ne!(target.to_str(), Some(status.as_str()));
7155 }
7156 forget_thread_status(tid);
7157 })
7158 .join()
7159 .unwrap();
7160 }
7161
7162 #[test]
7169 fn a_kept_status_file_of_an_exited_thread_is_unreadable_and_dropped() {
7170 status_files::enable();
7171 let pid = Pid::from_raw(unsafe { libc::getpid() });
7172 let (tid, kept) = std::thread::spawn(move || {
7173 status_files::enable();
7174 let tid = Pid::from_raw(unsafe { libc::syscall(libc::SYS_gettid) } as i32);
7175 assert!(read_kernel_signal_state(pid, tid).is_ok());
7176 let kept = status_files::kept_file(pid, tid).expect("the read keeps its file");
7177 (tid, kept)
7178 })
7179 .join()
7180 .unwrap();
7181 let deadline = std::time::Instant::now() + Duration::from_secs(5);
7185 let error = loop {
7186 match status_files::read_kept(&kept) {
7187 Ok(text) => {
7188 let own = format!("\nPid:\t{}\n", tid.as_raw());
7189 assert!(text.contains(&own), "{text}");
7190 assert!(std::time::Instant::now() < deadline, "{text}");
7191 std::thread::sleep(Duration::from_millis(1));
7192 }
7193 Err(error) => break error,
7194 }
7195 };
7196 assert_eq!(error.raw_os_error(), Some(libc::ESRCH));
7197 match read_kernel_signal_state(pid, tid) {
7198 Err(errno) => {
7199 assert_eq!(errno, Errno::ESRCH);
7200 assert!(!status_files::keeps(pid, tid));
7201 }
7202 Ok(_) => {
7204 if let Some(now) = status_files::kept_file(pid, tid) {
7207 assert!(!Arc::ptr_eq(&now, &kept));
7208 }
7209 }
7210 }
7211 assert!(status_files::kept() <= status_files::CAPACITY);
7212 }
7213 use crate::config::Config;
7214 use crate::tool_global::GlobalRequest;
7215 use crate::tool_global::GlobalState;
7216 use crate::types::MmId;
7217
7218 #[test]
7223 fn a_child_exit_signal_other_than_sigchld_is_host_timed_for_its_creator() {
7224 let usr1 = 1_u64 << (libc::SIGUSR1 - 1);
7225 let none = CloneFlags::empty();
7226 assert_eq!(clone_host_timed_signals(none, libc::SIGUSR1), usr1);
7227 assert_eq!(
7228 clone_host_timed_signals(CloneFlags::CLONE_VFORK, libc::SIGUSR1),
7229 usr1
7230 );
7231 assert_eq!(clone_host_timed_signals(none, libc::SIGCHLD), 0);
7232 assert_eq!(clone_host_timed_signals(none, 0), 0);
7233 assert_eq!(clone_host_timed_signals(none, 65), 0);
7234 let thread = CloneFlags::CLONE_THREAD | CloneFlags::CLONE_SIGHAND | CloneFlags::CLONE_VM;
7235 assert_eq!(clone_host_timed_signals(thread, libc::SIGUSR1), 0);
7236 assert_eq!(
7237 clone_host_timed_signals(CloneFlags::CLONE_PARENT, libc::SIGUSR1),
7238 0
7239 );
7240 }
7241
7242 struct FailedExecStack;
7243 struct FailedExecStackGuard;
7244
7245 impl Drop for FailedExecStackGuard {
7246 fn drop(&mut self) {}
7247 }
7248
7249 impl reverie::Stack for FailedExecStack {
7250 type StackGuard = FailedExecStackGuard;
7251
7252 fn size(&self) -> usize {
7253 panic!("failed exec must not use the guest stack")
7254 }
7255 fn capacity(&self) -> usize {
7256 panic!("failed exec must not use the guest stack")
7257 }
7258 fn push<'stack, T>(&mut self, _: T) -> Addr<'stack, T> {
7259 panic!("failed exec must not use the guest stack")
7260 }
7261 fn reserve<'stack, T>(&mut self) -> AddrMut<'stack, T> {
7262 panic!("failed exec must not use the guest stack")
7263 }
7264 fn commit(self) -> Result<Self::StackGuard, Errno> {
7265 panic!("failed exec must not use the guest stack")
7266 }
7267 }
7268
7269 struct FailedExecGuest<'a> {
7273 config: &'a Config,
7274 global: &'a GlobalState,
7275 thread: crate::ThreadState<()>,
7276 sender: Tid,
7277 process: Tid,
7278 old_mm: MmId,
7279 errno: Errno,
7280 backend_refuses: bool,
7281 refusal: Option<reverie::UnsupportedRefusal>,
7282 refusal_takes: usize,
7283 injections: usize,
7284 requests: Mutex<Vec<GlobalRequest>>,
7285 }
7286
7287 #[reverie::tool]
7288 impl GlobalRPC<GlobalState> for FailedExecGuest<'_> {
7289 async fn send_rpc(
7290 &self,
7291 message: <GlobalState as GlobalTool>::Request,
7292 ) -> <GlobalState as GlobalTool>::Response {
7293 assert_eq!(message.1, self.old_mm, "RPC follows restored exec identity");
7294 self.requests.lock().unwrap().push(message.2.clone());
7295 self.global.receive_rpc(self.sender, message).await
7296 }
7297 fn config(&self) -> &Config {
7298 self.config
7299 }
7300 }
7301
7302 #[reverie::tool]
7303 impl Guest<Detcore> for FailedExecGuest<'_> {
7304 type Memory = reverie::syscalls::LocalMemory;
7305 type Stack = FailedExecStack;
7306
7307 fn tid(&self) -> Tid {
7308 self.sender
7309 }
7310 fn pid(&self) -> Tid {
7311 self.process
7312 }
7313 fn ppid(&self) -> Option<Tid> {
7314 None
7315 }
7316 fn memory(&self) -> Self::Memory {
7317 panic!("failed exec rollback must not read guest memory")
7318 }
7319 fn thread_state(&self) -> &crate::ThreadState<()> {
7320 &self.thread
7321 }
7322 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
7323 &mut self.thread
7324 }
7325 async fn regs(&mut self) -> libc::user_regs_struct {
7326 panic!("failed exec rollback must not read registers")
7327 }
7328 async fn stack(&mut self) -> Self::Stack {
7329 panic!("failed exec rollback must not use a guest stack")
7330 }
7331 async fn daemonize(&mut self) {
7332 panic!("failed exec rollback must not daemonize")
7333 }
7334 async fn inject<S: SyscallInfo>(&mut self, call: S) -> Result<i64, Errno> {
7335 assert_eq!(call.number(), syscalls::Sysno::execveat);
7336 assert_eq!(
7337 self.thread.mm_id,
7338 self.old_mm.for_exec(self.thread.detpid.unwrap())
7339 );
7340 assert_eq!(self.thread.robust_list_head, None);
7341 self.injections += 1;
7342 self.refusal = self.backend_refuses.then(|| {
7344 reverie::UnsupportedRefusal::new(
7345 reverie::UnsupportedOperation::NonLeaderExec,
7346 self.errno,
7347 "KVM nonleader exec is unsupported",
7348 )
7349 });
7350 Err(self.errno)
7351 }
7352 fn take_unsupported_refusal(&mut self) -> Option<reverie::UnsupportedRefusal> {
7353 assert_eq!(
7354 self.thread.mm_id,
7355 self.old_mm.for_exec(self.thread.detpid.unwrap()),
7356 "the refusal is taken before the failed exec is rolled back"
7357 );
7358 self.refusal_takes += 1;
7359 self.refusal.take()
7360 }
7361 async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
7362 panic!("failed exec must not retire a live guest")
7363 }
7364 fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7365 panic!("failed exec rollback must not replace the timer")
7366 }
7367 fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7368 panic!("failed exec rollback must not replace the timer")
7369 }
7370 fn read_clock(&mut self) -> Result<u64, Error> {
7371 panic!("failed exec rollback must not sample the clock")
7372 }
7373 }
7374
7375 #[tokio::test]
7376 async fn backend_exec_refusal_preserves_failed_exec_rollback_and_policy() {
7377 let process = DetPid::from_raw(17);
7378 let worker = DetTid::from_raw(18);
7379 for (kvm_behaviours, caller, errno, backend_refuses, fail_closed, refused, reported) in [
7388 (true, worker, Errno::ENOSYS, true, true, true, false),
7389 (true, worker, Errno::ENOSYS, true, false, false, true),
7390 (true, worker, Errno::ENOENT, false, true, false, false),
7391 (true, worker, Errno::ENOEXEC, false, true, false, false),
7392 (true, worker, Errno::EFAULT, false, true, false, false),
7393 (true, worker, Errno::EACCES, false, true, false, false),
7394 (true, worker, Errno::EOPNOTSUPP, false, true, false, false),
7395 (true, process, Errno::ENOSYS, false, true, false, false),
7396 (false, worker, Errno::ENOSYS, false, true, false, false),
7397 (true, worker, Errno::ENOSYS, false, true, false, false),
7398 ] {
7399 let mut report = tempfile::tempfile().unwrap();
7400 let config = Config {
7401 sequentialize_threads: false,
7402 panic_on_unsupported_syscalls: fail_closed,
7403 exit_on_unsupported_syscall: true,
7404 shutdown_on_unsupported_syscall: false,
7405 unsupported_syscall_report_fd: Some(report.as_raw_fd()),
7406 ..Config::default().with_backend(|backend| {
7407 if kvm_behaviours {
7408 backend.provides_process_signal_control = true;
7413 backend.emulates_child_waits = true;
7414 }
7415 })
7416 };
7417 let global = GlobalState::init_global_state(&config).await;
7418 let tool = Detcore::new(Tid::from_raw(process.as_raw()), &config);
7419 let mut thread = crate::ThreadState::new(caller, &config, ());
7420 thread.detpid = Some(process);
7421 thread.mm_id = MmId::initial(process);
7422 thread.record_robust_list_head(Some(0x12340));
7423 thread.thread_logical_time.add_syscall_with_cost(123);
7424 let old_time = thread.thread_logical_time.as_nanos();
7425 let old_files = Arc::clone(&thread.file_metadata);
7426 let old_memory = Arc::clone(&thread.memory_metadata);
7427 let old_mm = thread.mm_id;
7428 let mut guest = FailedExecGuest {
7429 config: &config,
7430 global: &global,
7431 thread,
7432 sender: Tid::from_raw(caller.as_raw()),
7433 process: Tid::from_raw(process.as_raw()),
7434 old_mm,
7435 errno,
7436 backend_refuses,
7437 refusal: None,
7438 refusal_takes: 0,
7439 injections: 0,
7440 requests: Mutex::new(Vec::new()),
7441 };
7442 let result = tool
7443 .handle_execveat(&mut guest, syscalls::Execveat::new())
7444 .await;
7445 match result {
7446 Err(Error::Tool(error)) if refused => assert_eq!(
7447 error
7448 .downcast_ref::<crate::UnsupportedSyscallError>()
7449 .unwrap()
7450 .0,
7451 syscalls::Sysno::execveat
7452 ),
7453 Err(Error::Errno(actual)) if !refused => assert_eq!(actual, errno),
7454 result => panic!("wrong failed-exec policy: {result:?}"),
7455 }
7456 assert_eq!(guest.injections, 1, "backend preflight must run first");
7457 assert_eq!(guest.refusal_takes, 1, "the refusal must be asked for once");
7458 assert_eq!(guest.refusal, None, "a reported refusal must be taken");
7459 assert_eq!(guest.thread.dettid, caller);
7460 assert_eq!(guest.thread.detpid, Some(process));
7461 assert_eq!(guest.thread.mm_id, old_mm);
7462 assert_eq!(guest.thread.robust_list_head, Some(0x12340));
7463 assert_eq!(guest.thread.thread_logical_time.as_nanos(), old_time);
7464 assert!(Arc::ptr_eq(&guest.thread.file_metadata, &old_files));
7465 assert!(Arc::ptr_eq(&guest.thread.memory_metadata, &old_memory));
7466 let requests = guest.requests.lock().unwrap();
7467 assert!(matches!(requests[0], GlobalRequest::PrepareExec(..)));
7468 assert!(matches!(requests[1], GlobalRequest::CancelExec(..)));
7469 assert_eq!(requests.len(), if reported { 3 } else { 2 });
7470 if reported {
7471 assert!(
7472 matches!(&requests[2], GlobalRequest::ReportUnsupportedSyscall(name) if name == "execveat")
7473 );
7474 }
7475 report.rewind().unwrap();
7476 let mut aggregate = String::new();
7477 report.read_to_string(&mut aggregate).unwrap();
7478 assert_eq!(aggregate, if reported { "execveat\n" } else { "" });
7479 }
7480 }
7481
7482 struct FailedCloneGuest<'a> {
7486 config: &'a Config,
7487 global: &'a GlobalState,
7488 thread: crate::ThreadState<()>,
7489 sender: Tid,
7490 errno: Errno,
7491 backend_refuses: bool,
7492 refusal: Option<reverie::UnsupportedRefusal>,
7493 refusal_takes: usize,
7494 injections: usize,
7495 requests: Mutex<Vec<GlobalRequest>>,
7496 }
7497
7498 #[reverie::tool]
7499 impl GlobalRPC<GlobalState> for FailedCloneGuest<'_> {
7500 async fn send_rpc(
7501 &self,
7502 message: <GlobalState as GlobalTool>::Request,
7503 ) -> <GlobalState as GlobalTool>::Response {
7504 self.requests.lock().unwrap().push(message.2.clone());
7505 self.global.receive_rpc(self.sender, message).await
7506 }
7507 fn config(&self) -> &Config {
7508 self.config
7509 }
7510 }
7511
7512 #[reverie::tool]
7513 impl Guest<Detcore> for FailedCloneGuest<'_> {
7514 type Memory = reverie::syscalls::LocalMemory;
7515 type Stack = FailedExecStack;
7516
7517 fn tid(&self) -> Tid {
7518 self.sender
7519 }
7520 fn pid(&self) -> Tid {
7521 self.sender
7522 }
7523 fn ppid(&self) -> Option<Tid> {
7524 None
7525 }
7526 fn memory(&self) -> Self::Memory {
7527 reverie::syscalls::LocalMemory::new()
7528 }
7529 fn thread_state(&self) -> &crate::ThreadState<()> {
7530 &self.thread
7531 }
7532 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
7533 &mut self.thread
7534 }
7535 async fn regs(&mut self) -> libc::user_regs_struct {
7536 panic!("a failed clone must not read registers")
7537 }
7538 async fn stack(&mut self) -> Self::Stack {
7539 panic!("a failed clone must not use a guest stack")
7540 }
7541 async fn daemonize(&mut self) {
7542 panic!("a failed clone must not daemonize")
7543 }
7544 async fn inject<S: SyscallInfo>(&mut self, call: S) -> Result<i64, Errno> {
7545 assert_eq!(call.number(), syscalls::Sysno::clone);
7546 self.injections += 1;
7547 self.refusal = self.backend_refuses.then(|| {
7548 reverie::UnsupportedRefusal::new(
7549 reverie::UnsupportedOperation::NativeExec,
7550 self.errno,
7551 "this backend cannot create a thread",
7552 )
7553 });
7554 Err(self.errno)
7555 }
7556 fn take_unsupported_refusal(&mut self) -> Option<reverie::UnsupportedRefusal> {
7557 self.refusal_takes += 1;
7558 self.refusal.take()
7559 }
7560 async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
7561 panic!("a failed clone must not retire a live guest")
7562 }
7563 fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7564 panic!("a failed clone must not replace the timer")
7565 }
7566 fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
7567 panic!("a failed clone must not replace the timer")
7568 }
7569 fn read_clock(&mut self) -> Result<u64, Error> {
7570 panic!("a failed clone must not sample the clock")
7571 }
7572 }
7573
7574 #[tokio::test]
7578 async fn backend_clone_refusal_follows_the_unsupported_operation_policy() {
7579 let process = DetPid::from_raw(23);
7580 for (errno, backend_refuses, fail_closed, refused, reported) in [
7581 (Errno::EOPNOTSUPP, true, true, true, false),
7582 (Errno::EOPNOTSUPP, true, false, false, true),
7583 (Errno::EAGAIN, false, true, false, false),
7584 (Errno::EOPNOTSUPP, false, true, false, false),
7585 ] {
7586 let mut report = tempfile::tempfile().unwrap();
7587 let config = Config {
7588 sequentialize_threads: false,
7589 panic_on_unsupported_syscalls: fail_closed,
7590 exit_on_unsupported_syscall: true,
7591 shutdown_on_unsupported_syscall: false,
7592 unsupported_syscall_report_fd: Some(report.as_raw_fd()),
7593 ..Config::default()
7594 };
7595 let global = GlobalState::init_global_state(&config).await;
7596 let tool = Detcore::new(Tid::from_raw(process.as_raw()), &config);
7597 let mut thread = crate::ThreadState::new(process, &config, ());
7598 thread.detpid = Some(process);
7599 let mut guest = FailedCloneGuest {
7600 config: &config,
7601 global: &global,
7602 thread,
7603 sender: Tid::from_raw(process.as_raw()),
7604 errno,
7605 backend_refuses,
7606 refusal: None,
7607 refusal_takes: 0,
7608 injections: 0,
7609 requests: Mutex::new(Vec::new()),
7610 };
7611 let flags = (libc::CLONE_VM
7612 | libc::CLONE_FS
7613 | libc::CLONE_FILES
7614 | libc::CLONE_SIGHAND
7615 | libc::CLONE_THREAD
7616 | libc::CLONE_SYSVSEM) as u64;
7617 let clone = syscalls::Clone::new()
7618 .with_flags(nix::sched::CloneFlags::from_bits_truncate(flags as _));
7619 let result = tool
7620 .handle_clone_family(&mut guest, syscalls::family::CloneFamily::Clone(clone))
7621 .await;
7622 match result {
7623 Err(Error::Tool(error)) if refused => assert_eq!(
7624 error
7625 .downcast_ref::<crate::UnsupportedSyscallError>()
7626 .unwrap()
7627 .0,
7628 syscalls::Sysno::clone
7629 ),
7630 Err(Error::Errno(actual)) if !refused => assert_eq!(actual, errno),
7631 result => panic!("wrong failed-clone policy: {result:?}"),
7632 }
7633 assert_eq!(guest.injections, 1);
7634 assert_eq!(guest.refusal_takes, 1, "the refusal must be asked for once");
7635 assert_eq!(guest.refusal, None, "a reported refusal must be taken");
7636 assert_eq!(guest.thread.clone_flags, None);
7637 let requests = guest.requests.lock().unwrap();
7638 assert_eq!(
7639 requests
7640 .iter()
7641 .filter(|request| matches!(request, GlobalRequest::ReportUnsupportedSyscall(name) if name == "clone"))
7642 .count(),
7643 usize::from(reported)
7644 );
7645 report.rewind().unwrap();
7646 let mut aggregate = String::new();
7647 report.read_to_string(&mut aggregate).unwrap();
7648 assert_eq!(aggregate, if reported { "clone\n" } else { "" });
7649 }
7650 }
7651
7652 fn bits(signals: &[i32]) -> KernelSigset {
7653 signals
7654 .iter()
7655 .fold(0, |set, &signal| set | kernel_sigset_bit(signal))
7656 }
7657
7658 #[test]
7659 fn a_precise_futex_wait_holds_a_committed_signal_only_if_a_host_timed_source_can_post_it() {
7660 let chld = kernel_sigset_bit(libc::SIGCHLD);
7661 let usr1 = kernel_sigset_bit(libc::SIGUSR1);
7662 assert_eq!(futex_wait_held_after_wake(0, 0), chld);
7664 assert_eq!(futex_wait_held_after_wake(usr1, 0), chld | usr1);
7665 assert_eq!(futex_wait_held_after_wake(0, chld), 0);
7667 assert_eq!(futex_wait_held_after_wake(usr1, chld), usr1);
7668 assert_eq!(futex_wait_held_after_wake(chld, chld), chld);
7671 assert_eq!(futex_wait_held_after_wake(usr1, usr1 | chld), usr1);
7672 }
7673
7674 #[test]
7675 fn kernel_signal_state_parses_proc_status() {
7676 let status = "Name:\tguest\nSigQ:\t1/1024\nSigPnd:\t0000000000000200\n\
7677 ShdPnd:\t0000000000000400\nSigBlk:\t0000000000000800\n\
7678 SigIgn:\t0000000000001000\nSigCgt:\t0000000000000400\n";
7679 assert_eq!(
7680 KernelSignalState::parse(status),
7681 Some(KernelSignalState {
7682 pending: bits(&[libc::SIGUSR1, libc::SIGSEGV]),
7683 thread_pending: bits(&[libc::SIGUSR1]),
7684 shared_pending: bits(&[libc::SIGSEGV]),
7685 blocked: bits(&[libc::SIGUSR2]),
7686 ignored: bits(&[libc::SIGPIPE]),
7687 caught: bits(&[libc::SIGSEGV]),
7688 })
7689 );
7690 assert_eq!(
7692 KernelSignalState::parse("SigPnd:\t0\nShdPnd:\t0\nSigBlk:\t0\nSigIgn:\t0\n"),
7693 None
7694 );
7695 assert_eq!(
7696 KernelSignalState::parse(
7697 "SigPnd:\tzz\nShdPnd:\t0\nSigBlk:\t0\nSigIgn:\t0\nSigCgt:\t0\n"
7698 ),
7699 None
7700 );
7701 }
7702
7703 #[test]
7704 fn only_unblocked_caught_or_fatal_signals_interrupt_a_wait() {
7705 let state = KernelSignalState {
7706 pending: bits(&[
7707 libc::SIGUSR1,
7708 libc::SIGUSR2,
7709 libc::SIGPIPE,
7710 libc::SIGTERM,
7711 libc::SIGCHLD,
7712 libc::SIGWINCH,
7713 libc::SIGURG,
7714 libc::SIGCONT,
7715 ]),
7716 blocked: bits(&[libc::SIGUSR2]),
7717 ignored: bits(&[libc::SIGPIPE]),
7718 caught: bits(&[libc::SIGUSR1, libc::SIGUSR2, libc::SIGWINCH]),
7719 ..Default::default()
7720 };
7721 assert_eq!(
7724 state.pending_interrupting(state.blocked, false),
7725 bits(&[libc::SIGUSR1, libc::SIGWINCH, libc::SIGTERM])
7726 );
7727 assert_eq!(
7729 state.pending_interrupting(0, false),
7730 bits(&[libc::SIGUSR1, libc::SIGUSR2, libc::SIGWINCH, libc::SIGTERM])
7731 );
7732 let interrupting = state.interrupting(state.blocked);
7733 assert_ne!(interrupting & kernel_sigset_bit(libc::SIGKILL), 0);
7734 assert_ne!(interrupting & kernel_sigset_bit(libc::SIGSTOP), 0);
7735 assert_eq!(
7736 interrupting & kernel_sigset_bit(reverie::PERF_EVENT_SIGNAL as i32),
7737 0
7738 );
7739 assert_eq!(kernel_sigset_bit(0), 0);
7740 assert_eq!(kernel_sigset_bit(65), 0);
7741 }
7742
7743 #[test]
7750 fn a_rearming_wait_is_not_ended_by_a_default_job_control_stop() {
7751 let stops = bits(&[libc::SIGTSTP, libc::SIGTTIN, libc::SIGTTOU]);
7752 let state = KernelSignalState {
7753 pending: stops | bits(&[libc::SIGSTOP, libc::SIGTERM]),
7754 ..Default::default()
7755 };
7756 assert_eq!(state.default_job_control_stops(), stops);
7757 assert_eq!(state.pending_interrupting(0, false), state.pending);
7758 assert_eq!(
7759 state.pending_interrupting(0, true),
7760 bits(&[libc::SIGSTOP, libc::SIGTERM])
7761 );
7762 let rearming = state.interrupting_wait(0, true);
7763 assert_eq!(rearming & stops, 0);
7764 assert_eq!(rearming, state.interrupting(0) & !stops);
7765
7766 let state = KernelSignalState {
7769 pending: stops,
7770 caught: bits(&[libc::SIGTSTP]),
7771 ignored: bits(&[libc::SIGTTIN]),
7772 ..Default::default()
7773 };
7774 assert_eq!(state.default_job_control_stops(), bits(&[libc::SIGTTOU]));
7775 assert_eq!(state.pending_interrupting(0, true), bits(&[libc::SIGTSTP]));
7776 assert_eq!(
7777 state.pending_interrupting(0, false),
7778 bits(&[libc::SIGTSTP, libc::SIGTTOU])
7779 );
7780 assert_eq!(state.pending_interrupting(bits(&[libc::SIGTSTP]), true), 0);
7782 }
7783
7784 #[test]
7785 fn kernel_blocked_mask_preserves_libc_signal_membership() {
7786 let mask = blocked_signal_mask();
7787 let mut libc_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
7788 unsafe {
7789 libc::sigfillset(&mut libc_mask);
7790 libc::sigdelset(&mut libc_mask, reverie::PERF_EVENT_SIGNAL as i32);
7791 }
7792 for raw_signal in 1..=KernelSigset::BITS as i32 {
7793 assert_eq!(
7794 signal_is_blocked(&mask, SigWrapper(raw_signal)),
7795 unsafe { libc::sigismember(&libc_mask, raw_signal) == 1 },
7796 "signal {raw_signal} membership changed while converting to the kernel ABI"
7797 );
7798 }
7799 }
7800
7801 #[test]
7802 fn clone_without_child_cleartid_does_not_register_the_pointer() {
7803 assert_eq!(
7804 child_tid_clear_address(CloneFlags::CLONE_CHILD_SETTID, 0x1234),
7805 0
7806 );
7807 }
7808
7809 #[test]
7810 fn clone_with_child_cleartid_registers_the_pointer() {
7811 assert_eq!(
7812 child_tid_clear_address(CloneFlags::CLONE_CHILD_CLEARTID, 0x1234),
7813 0x1234
7814 );
7815 }
7816
7817 #[test]
7818 fn linux_default_dispositions_that_do_not_interrupt_child_waits() {
7819 for signal in [libc::SIGCHLD, libc::SIGCONT, libc::SIGURG, libc::SIGWINCH] {
7820 assert!(signal_default_disposition_does_not_interrupt_child_wait(
7821 SigWrapper(signal)
7822 ));
7823 }
7824 for signal in [libc::SIGALRM, libc::SIGSTOP, libc::SIGUSR1] {
7825 assert!(!signal_default_disposition_does_not_interrupt_child_wait(
7826 SigWrapper(signal)
7827 ));
7828 }
7829 }
7830
7831 #[test]
7835 fn only_serialized_ptrace_runs_without_a_gdbserver_retire_at_a_fatal_delivery() {
7836 let ptrace = Config {
7837 sequentialize_threads: true,
7838 ..Config::default()
7839 }
7840 .with_backend(|backend| *backend = reverie::BackendCapabilities::PTRACE);
7841 assert!(retires_others_at_fatal_delivery(&ptrace));
7842 for (case, cfg) in [
7843 (
7844 "gdbserver",
7845 Config {
7846 gdbserver: true,
7847 ..ptrace.clone()
7848 },
7849 ),
7850 (
7851 "unserialized",
7852 Config {
7853 sequentialize_threads: false,
7854 ..ptrace.clone()
7855 },
7856 ),
7857 (
7858 "DBT",
7859 ptrace
7860 .clone()
7861 .with_backend(|backend| *backend = reverie::BackendCapabilities::DBT),
7862 ),
7863 ] {
7864 assert!(!retires_others_at_fatal_delivery(&cfg), "{case}");
7865 }
7866 }
7867
7868 #[test]
7871 fn only_an_unblocked_default_fatal_delivery_ends_the_process() {
7872 let abrt = kernel_sigset_bit(libc::SIGABRT);
7873 let default = KernelSignalState::default();
7874 assert!(default.delivery_carries_out_a_fatal_default(libc::SIGABRT));
7875 assert!(default.delivery_carries_out_a_fatal_default(libc::SIGSEGV));
7876 for signal in [libc::SIGCHLD, libc::SIGWINCH, libc::SIGTSTP, libc::SIGCONT] {
7877 assert!(
7878 !default.delivery_carries_out_a_fatal_default(signal),
7879 "{signal}"
7880 );
7881 }
7882 for state in [
7883 KernelSignalState {
7884 blocked: abrt,
7885 ..Default::default()
7886 },
7887 KernelSignalState {
7888 caught: abrt,
7889 ..Default::default()
7890 },
7891 KernelSignalState {
7892 ignored: abrt,
7893 ..Default::default()
7894 },
7895 ] {
7896 assert!(
7897 !state.delivery_carries_out_a_fatal_default(libc::SIGABRT),
7898 "{state:?}"
7899 );
7900 }
7901 }
7902
7903 #[test]
7908 fn default_actions_that_end_the_process() {
7909 for signal in [
7910 libc::SIGTERM,
7911 libc::SIGINT,
7912 libc::SIGHUP,
7913 libc::SIGQUIT,
7914 libc::SIGABRT,
7915 libc::SIGSEGV,
7916 libc::SIGBUS,
7917 libc::SIGFPE,
7918 libc::SIGILL,
7919 libc::SIGPIPE,
7920 libc::SIGALRM,
7921 libc::SIGUSR1,
7922 libc::SIGUSR2,
7923 libc::SIGRTMIN(),
7924 libc::SIGRTMAX(),
7925 ] {
7926 assert!(
7927 signal_default_action_ends_the_process(SigWrapper(signal)),
7928 "{signal}"
7929 );
7930 }
7931 for signal in [
7932 libc::SIGCHLD,
7933 libc::SIGCONT,
7934 libc::SIGURG,
7935 libc::SIGWINCH,
7936 libc::SIGSTOP,
7937 libc::SIGTSTP,
7938 libc::SIGTTIN,
7939 libc::SIGTTOU,
7940 libc::SIGKILL,
7941 ] {
7942 assert!(
7943 !signal_default_action_ends_the_process(SigWrapper(signal)),
7944 "{signal}"
7945 );
7946 }
7947 }
7948
7949 #[test]
7950 fn uncatchable_signals_do_not_require_a_sigaction_query() {
7951 assert!(signal_has_uncatchable_default_disposition(SigWrapper(
7952 libc::SIGKILL
7953 )));
7954 assert!(signal_has_uncatchable_default_disposition(SigWrapper(
7955 libc::SIGSTOP
7956 )));
7957 assert!(!signal_has_uncatchable_default_disposition(SigWrapper(
7958 libc::SIGUSR1
7959 )));
7960 }
7961
7962 #[test]
7963 fn waitid_ready_child_wins_when_scheduler_also_reports_a_signal() {
7964 assert_eq!(
7965 exact_wait_poll_decision(true, true, Some(ExactChildWaitState::Running)),
7966 ExactWaitPollDecision::ChildReady
7967 );
7968 assert_eq!(
7969 exact_wait_poll_decision(false, true, Some(ExactChildWaitState::LogicallyExited)),
7970 ExactWaitPollDecision::ReapAfterLogicalExit
7971 );
7972 assert_eq!(
7973 exact_wait_poll_decision(false, true, Some(ExactChildWaitState::PhysicalExitPending)),
7974 ExactWaitPollDecision::AwaitPhysicalExit
7975 );
7976 assert_eq!(
7977 exact_wait_poll_decision(false, true, Some(ExactChildWaitState::Running)),
7978 ExactWaitPollDecision::Interrupted
7979 );
7980 assert_eq!(
7981 exact_wait_poll_decision(false, false, Some(ExactChildWaitState::Running)),
7982 ExactWaitPollDecision::Retry
7983 );
7984 }
7985
7986 #[test]
7987 fn wait4_argument_validation_follows_linux_precedence() {
7988 let valid_bits = [0, 1, 3, 29, 30, 31];
7989 for bit in valid_bits {
7990 let options = WaitPidFlag::from_bits_retain((1_u32 << bit) as libc::c_int);
7991 assert_eq!(validate_wait4_arguments(-1, options), Ok(()), "bit {bit}");
7992 }
7993
7994 for bit in (0..u32::BITS).filter(|bit| !valid_bits.contains(bit)) {
7995 let options = WaitPidFlag::from_bits_retain((1_u32 << bit) as libc::c_int);
7996 assert_eq!(
7997 validate_wait4_arguments(-1, options),
7998 Err(Errno::EINVAL),
7999 "bit {bit}"
8000 );
8001 }
8002
8003 for pid in [libc::pid_t::MIN + 1, -2, 0, 1, libc::pid_t::MAX] {
8006 assert_eq!(
8007 validate_wait4_arguments(pid, WaitPidFlag::empty()),
8008 Ok(()),
8009 "pid {pid}"
8010 );
8011 }
8012 assert_eq!(
8013 validate_wait4_arguments(libc::pid_t::MIN, WaitPidFlag::empty()),
8014 Err(Errno::ESRCH)
8015 );
8016 assert_eq!(
8017 validate_wait4_arguments(libc::pid_t::MIN, WaitPidFlag::from_bits_retain(0x10)),
8018 Err(Errno::EINVAL),
8019 "invalid options must win over the INT_MIN selector"
8020 );
8021 }
8022
8023 #[test]
8024 fn stale_any_child_preserves_interrupt_until_no_ready_child_remains() {
8025 let next_child = DetPid::from_raw(200);
8026
8027 assert!(
8028 !stale_any_wait_must_interrupt(true, Some(next_child)),
8029 "another ready child must retain child-ready precedence"
8030 );
8031 assert!(
8032 stale_any_wait_must_interrupt(true, None),
8033 "a pending signal must interrupt before the wait parks again"
8034 );
8035 assert!(!stale_any_wait_must_interrupt(false, None));
8036 }
8037
8038 #[test]
8039 fn ioprio_query_reports_fixed_raw_and_effective_defaults() {
8040 assert_eq!(virtual_ioprio(IOPRIO_WHO_PROCESS), Ok(0));
8041 assert_eq!(
8042 virtual_ioprio(IOPRIO_WHO_PGRP),
8043 Ok(i64::from(IOPRIO_DEFAULT_EFFECTIVE))
8044 );
8045 assert_eq!(
8046 virtual_ioprio(IOPRIO_WHO_USER),
8047 Ok(i64::from(IOPRIO_DEFAULT_EFFECTIVE))
8048 );
8049 assert_eq!(virtual_ioprio(0), Err(Errno::EINVAL));
8050 assert_eq!(virtual_ioprio(4), Err(Errno::EINVAL));
8051 }
8052
8053 #[test]
8054 fn waitid_siginfo_canonicalization_clears_only_cpu_accounting() {
8055 let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
8056 info.si_signo = libc::SIGCHLD;
8057 info.si_code = libc::CLD_EXITED;
8058 let fields = unsafe {
8061 &mut (*(std::ptr::addr_of_mut!(info)).cast::<WaitidSiginfoHead>())
8062 .fields
8063 .sigchld
8064 };
8065 fields.pid = 123;
8066 fields.uid = 456;
8067 fields.status = 7;
8068 fields.utime = 8;
8069 fields.stime = 9;
8070
8071 canonicalize_waitid_siginfo(&mut info);
8072
8073 assert_eq!(info.si_signo, libc::SIGCHLD);
8074 assert_eq!(info.si_code, libc::CLD_EXITED);
8075 assert_eq!(unsafe { info.si_pid() }, 123);
8076 assert_eq!(unsafe { info.si_uid() }, 456);
8077 assert_eq!(unsafe { info.si_status() }, 7);
8078 assert_eq!(unsafe { info.si_utime() }, 0);
8079 assert_eq!(unsafe { info.si_stime() }, 0);
8080 }
8081
8082 #[test]
8083 fn wait_status_rollup_only_accepts_process_termination() {
8084 assert!(wait_status_is_termination(0));
8085 assert!(wait_status_is_termination(libc::SIGTERM));
8086 assert!(!wait_status_is_termination((libc::SIGSTOP << 8) | 0x7f));
8087 assert!(!wait_status_is_termination(0xffff));
8088
8089 assert!(waitid_code_is_termination(libc::CLD_EXITED));
8090 assert!(waitid_code_is_termination(libc::CLD_KILLED));
8091 assert!(waitid_code_is_termination(libc::CLD_DUMPED));
8092 assert!(!waitid_code_is_termination(libc::CLD_STOPPED));
8093 assert!(!waitid_code_is_termination(libc::CLD_CONTINUED));
8094 assert!(!waitid_code_is_termination(libc::CLD_TRAPPED));
8095 }
8096
8097 #[test]
8103 fn futex_commands_are_classified_as_linux_dispatches_them() {
8104 use FutexCommand::*;
8105 let private = libc::FUTEX_PRIVATE_FLAG;
8106 let realtime = libc::FUTEX_CLOCK_REALTIME;
8107 for cmd in [
8108 libc::FUTEX_WAIT,
8109 libc::FUTEX_WAIT_BITSET,
8110 libc::FUTEX_WAKE,
8111 libc::FUTEX_WAKE_BITSET,
8112 libc::FUTEX_REQUEUE,
8113 libc::FUTEX_CMP_REQUEUE,
8114 libc::FUTEX_WAKE_OP,
8115 ] {
8116 assert_eq!(classify_futex_command(cmd), Served, "cmd {cmd}");
8117 assert_eq!(classify_futex_command(cmd | private), Served, "cmd {cmd}");
8118 }
8119 assert_eq!(
8120 classify_futex_command(libc::FUTEX_WAIT_BITSET | realtime),
8121 Served
8122 );
8123 for cmd in [
8124 libc::FUTEX_WAIT,
8125 libc::FUTEX_WAKE,
8126 libc::FUTEX_REQUEUE,
8127 libc::FUTEX_WAKE_OP,
8128 ] {
8129 assert_eq!(classify_futex_command(cmd | realtime), NoSys, "cmd {cmd}");
8130 }
8131 for cmd in [libc::FUTEX_FD, 14, 99, libc::FUTEX_CMD_MASK] {
8132 assert_eq!(classify_futex_command(cmd), NoSys, "cmd {cmd}");
8133 }
8134 for cmd in [
8135 libc::FUTEX_LOCK_PI,
8136 libc::FUTEX_LOCK_PI2,
8137 libc::FUTEX_TRYLOCK_PI,
8138 libc::FUTEX_UNLOCK_PI,
8139 ] {
8140 assert_eq!(classify_futex_command(cmd), Served, "cmd {cmd}");
8141 assert_eq!(classify_futex_command(cmd | private), Served, "cmd {cmd}");
8142 }
8143 assert_eq!(
8144 classify_futex_command(libc::FUTEX_LOCK_PI2 | realtime),
8145 Served
8146 );
8147 for cmd in [
8148 libc::FUTEX_LOCK_PI,
8149 libc::FUTEX_TRYLOCK_PI,
8150 libc::FUTEX_UNLOCK_PI,
8151 ] {
8152 assert_eq!(classify_futex_command(cmd | realtime), NoSys, "cmd {cmd}");
8153 }
8154 assert_eq!(
8155 classify_futex_command(libc::FUTEX_WAIT_REQUEUE_PI | realtime),
8156 RequeuePi("FUTEX_WAIT_REQUEUE_PI")
8157 );
8158 for (cmd, name) in [
8159 (libc::FUTEX_WAIT_REQUEUE_PI, "FUTEX_WAIT_REQUEUE_PI"),
8160 (libc::FUTEX_CMP_REQUEUE_PI, "FUTEX_CMP_REQUEUE_PI"),
8161 ] {
8162 assert_eq!(classify_futex_command(cmd), RequeuePi(name));
8163 }
8164 assert_eq!(
8165 classify_futex_command(libc::FUTEX_CMP_REQUEUE_PI | realtime),
8166 NoSys
8167 );
8168 }
8169
8170 #[test]
8175 fn futex_deadlines_are_measured_as_linux_measures_them() {
8176 let one_second = Timespec {
8177 tv_sec: 1,
8178 tv_nsec: 0,
8179 };
8180 let realtime = libc::FUTEX_CLOCK_REALTIME;
8181 assert_eq!(
8182 parse_futex_timeout(libc::FUTEX_WAIT, one_second),
8183 Ok(FutexTimeout::Relative(1_000_000_000))
8184 );
8185 for cmd in [
8186 libc::FUTEX_WAIT_BITSET,
8187 libc::FUTEX_LOCK_PI,
8188 libc::FUTEX_LOCK_PI2,
8189 libc::FUTEX_LOCK_PI | libc::FUTEX_PRIVATE_FLAG,
8190 ] {
8191 assert_eq!(
8192 parse_futex_timeout(cmd, one_second),
8193 Ok(FutexTimeout::Absolute(LogicalTime::from_nanos(
8194 1_000_000_000
8195 ))),
8196 "cmd {cmd}"
8197 );
8198 }
8199 assert_eq!(
8200 futex_deadline_clock(libc::FUTEX_LOCK_PI),
8201 syscalls::ClockId::CLOCK_REALTIME
8202 );
8203 assert_eq!(
8204 futex_deadline_clock(libc::FUTEX_LOCK_PI2),
8205 syscalls::ClockId::CLOCK_MONOTONIC
8206 );
8207 assert_eq!(
8208 futex_deadline_clock(libc::FUTEX_LOCK_PI2 | realtime),
8209 syscalls::ClockId::CLOCK_REALTIME
8210 );
8211 assert_eq!(
8212 futex_deadline_clock(libc::FUTEX_WAIT_BITSET),
8213 syscalls::ClockId::CLOCK_MONOTONIC
8214 );
8215 }
8216
8217 type ProbeArgs = (i32, usize, usize, i32, i32);
8222
8223 struct ScriptedProbes {
8226 answers: std::collections::VecDeque<Result<i64, Errno>>,
8227 probes: Vec<ProbeArgs>,
8228 }
8229
8230 impl ScriptedProbes {
8231 fn inject(&mut self, probe: syscalls::Futex) -> KeyProbeAnswer<'_> {
8232 self.probes.push((
8233 probe.futex_op(),
8234 probe.uaddr().map_or(0, AddrMut::as_raw),
8235 probe.uaddr2().map_or(0, AddrMut::as_raw),
8236 probe.val(),
8237 probe.val3(),
8238 ));
8239 let answer = self
8240 .answers
8241 .pop_front()
8242 .expect("the probe was injected more often than scripted");
8243 Box::pin(async move { answer })
8244 }
8245 }
8246
8247 fn run_scripted(
8248 access: KeyAccess,
8249 answer: Result<i64, Errno>,
8250 ) -> (SharedKeyProbe, Vec<ProbeArgs>) {
8251 let mut guest = ScriptedProbes {
8252 answers: [answer].into_iter().collect(),
8253 probes: Vec::new(),
8254 };
8255 let result = futures::executor::block_on(run_shared_futex_key_probe(
8256 &mut guest,
8257 shared_futex_key_probe(0x1000, access),
8258 access,
8259 |guest, probe| guest.inject(probe),
8260 ));
8261 (result, guest.probes)
8262 }
8263
8264 #[test]
8274 fn an_interrupted_shared_key_probe_is_never_retried_or_taken_as_an_answer() {
8275 let read = (libc::FUTEX_REQUEUE, 0x1000, 0x1000, 0, 0);
8276 let write = (
8277 libc::FUTEX_WAKE_OP,
8278 0x1000,
8279 0x1000,
8280 0,
8281 (7 << 28) | (1 << 12),
8282 );
8283 for (access, args) in [(KeyAccess::Read, read), (KeyAccess::Write, write)] {
8284 for errno in [Errno::ERESTARTSYS, Errno::EINTR] {
8285 assert_eq!(
8286 run_scripted(access, Err(errno)),
8287 (SharedKeyProbe::Interrupted(errno), vec![args]),
8288 "{access:?} {errno}"
8289 );
8290 }
8291 assert_eq!(
8292 run_scripted(access, Ok(0)),
8293 (SharedKeyProbe::Admitted, vec![args])
8294 );
8295 assert_eq!(
8296 run_scripted(access, Err(Errno::EFAULT)),
8297 (SharedKeyProbe::Refused(Errno::EFAULT), vec![args])
8298 );
8299 }
8300 assert_eq!(
8301 run_scripted(KeyAccess::Read, Err(Errno::ENOSYS)),
8302 (SharedKeyProbe::Refused(Errno::ENOSYS), vec![read])
8303 );
8304 assert_eq!(
8305 run_scripted(KeyAccess::Write, Err(Errno::ENOSYS)),
8306 (SharedKeyProbe::Admitted, vec![write])
8307 );
8308 }
8309
8310 #[test]
8311 fn futex_wake_op_decodes_and_applies_as_linux() {
8312 let encode = |op: u32, cmp: u32, oparg: u32, cmparg: u32| {
8313 ((op & 0xf) << 28) | ((cmp & 0xf) << 24) | ((oparg & 0xfff) << 12) | (cmparg & 0xfff)
8314 };
8315 let set5_eq0 = FutexWakeOp::decode(encode(FUTEX_OP_SET, FUTEX_OP_CMP_EQ, 5, 0));
8316 assert_eq!(set5_eq0.apply(0), Some(5));
8317 assert_eq!(set5_eq0.compare(0), Some(true));
8318 assert_eq!(set5_eq0.compare(1), Some(false));
8319 let add_minus1 = FutexWakeOp::decode(encode(FUTEX_OP_ADD, FUTEX_OP_CMP_LT, 0xfff, 0x800));
8320 assert_eq!(add_minus1.apply(10), Some(9));
8321 assert_eq!(add_minus1.apply(i32::MIN), Some(i32::MAX));
8322 assert_eq!(add_minus1.compare(-2048), Some(false));
8323 assert_eq!(add_minus1.compare(-2049), Some(true));
8324 let or = FutexWakeOp::decode(encode(FUTEX_OP_OR, FUTEX_OP_CMP_NE, 0b100, 7));
8325 assert_eq!(or.apply(0b011), Some(0b111));
8326 assert_eq!(or.compare(7), Some(false));
8327 let andn = FutexWakeOp::decode(encode(FUTEX_OP_ANDN, FUTEX_OP_CMP_GE, 0b110, 3));
8328 assert_eq!(andn.apply(0b111), Some(0b001));
8329 assert_eq!(andn.compare(3), Some(true));
8330 let xor = FutexWakeOp::decode(encode(FUTEX_OP_XOR, FUTEX_OP_CMP_LE, 0xff, 0));
8331 assert_eq!(xor.apply(0x0f), Some(0xf0));
8332 assert_eq!(xor.compare(1), Some(false));
8333 let shift = FutexWakeOp::decode(encode(
8334 FUTEX_OP_OPARG_SHIFT | FUTEX_OP_OR,
8335 FUTEX_OP_CMP_GT,
8336 3,
8337 0,
8338 ));
8339 assert_eq!(shift.apply(0), Some(8));
8340 assert_eq!(shift.compare(1), Some(true));
8341 let masked = FutexWakeOp::decode(encode(FUTEX_OP_OPARG_SHIFT | FUTEX_OP_SET, 0, 0xfff, 0));
8343 assert_eq!(masked.apply(0), Some(i32::MIN));
8344 let unknown_op = FutexWakeOp::decode(encode(7, FUTEX_OP_CMP_EQ, 1, 0));
8345 assert!(!unknown_op.has_known_operation());
8346 assert_eq!(unknown_op.apply(0), None);
8347 let unknown_cmp = FutexWakeOp::decode(encode(FUTEX_OP_SET, 6, 1, 0));
8348 assert!(unknown_cmp.has_known_operation());
8349 assert_eq!(unknown_cmp.compare(0), None);
8350 }
8351
8352 #[test]
8353 fn futex_timeout_units_and_modes_match_linux() {
8354 let timeout = Timespec {
8355 tv_sec: 2,
8356 tv_nsec: 3,
8357 };
8358 assert_eq!(
8359 parse_futex_timeout(libc::FUTEX_WAIT, timeout),
8360 Ok(FutexTimeout::Relative(2_000_000_003))
8361 );
8362 assert_eq!(
8363 parse_futex_timeout(libc::FUTEX_WAIT_BITSET, timeout),
8364 Ok(FutexTimeout::Absolute(LogicalTime::from_nanos(
8365 2_000_000_003
8366 )))
8367 );
8368 assert_eq!(
8372 parse_futex_timeout(libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG, timeout),
8373 Ok(FutexTimeout::Absolute(LogicalTime::from_nanos(
8374 2_000_000_003
8375 )))
8376 );
8377 assert_eq!(
8378 parse_futex_timeout(libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG, timeout),
8379 Ok(FutexTimeout::Relative(2_000_000_003))
8380 );
8381 }
8382
8383 #[test]
8384 fn absolute_futex_timeout_is_rebased_to_logical_time() {
8385 let logical_now = LogicalTime::from_secs(100);
8386 let clock_now = LogicalTime::from_secs(5_000);
8387 let deadline = clock_now + Duration::from_millis(100);
8388 assert_eq!(
8389 rebase_absolute_timeout(deadline, clock_now, logical_now),
8390 logical_now + Duration::from_millis(100)
8391 );
8392 assert_eq!(
8393 rebase_absolute_timeout(
8394 clock_now - LogicalTime::from_nanos(1),
8395 clock_now,
8396 logical_now
8397 ),
8398 logical_now
8399 );
8400 }
8401
8402 #[test]
8403 fn absolute_futex_timeout_detects_host_and_logical_clock_domains() {
8404 let host_monotonic_now = LogicalTime::from_secs(374_766);
8405 let logical_now = LogicalTime::from_secs(1_640_995_199);
8406 let delta = Duration::from_millis(100);
8407
8408 assert!(absolute_timeout_uses_host_clock(
8409 host_monotonic_now + delta,
8410 host_monotonic_now,
8411 logical_now
8412 ));
8413 assert!(!absolute_timeout_uses_host_clock(
8414 logical_now + delta,
8415 host_monotonic_now,
8416 logical_now
8417 ));
8418
8419 let host_realtime_now = LogicalTime::from_secs(1_785_142_800);
8420 assert!(absolute_timeout_uses_host_clock(
8421 host_realtime_now + delta,
8422 host_realtime_now,
8423 logical_now
8424 ));
8425 }
8426
8427 #[test]
8431 fn futex_timeout_clamps_a_huge_valid_timespec_like_ktime() {
8432 let huge = Timespec {
8433 tv_sec: libc::time_t::MAX,
8434 tv_nsec: 0,
8435 };
8436 assert!(futex_timespec_is_valid(huge));
8437 assert_eq!(
8438 parse_futex_timeout(libc::FUTEX_WAIT, huge),
8439 Ok(FutexTimeout::Relative(FUTEX_KTIME_MAX_NANOS))
8440 );
8441 assert_eq!(
8442 parse_futex_timeout(libc::FUTEX_WAIT_BITSET, huge),
8443 Ok(FutexTimeout::Absolute(LogicalTime::from_nanos(
8444 FUTEX_KTIME_MAX_NANOS
8445 )))
8446 );
8447 let at_ktime_sec_max = Timespec {
8448 tv_sec: (FUTEX_KTIME_MAX_NANOS / 1_000_000_000) as libc::time_t,
8449 tv_nsec: 0,
8450 };
8451 assert_eq!(
8452 parse_futex_timeout(libc::FUTEX_WAIT, at_ktime_sec_max),
8453 Ok(FutexTimeout::Relative(FUTEX_KTIME_MAX_NANOS))
8454 );
8455 let just_below = Timespec {
8456 tv_sec: (FUTEX_KTIME_MAX_NANOS / 1_000_000_000 - 1) as libc::time_t,
8457 tv_nsec: 999_999_999,
8458 };
8459 assert_eq!(
8460 parse_futex_timeout(libc::FUTEX_WAIT, just_below),
8461 Ok(FutexTimeout::Relative(
8462 (FUTEX_KTIME_MAX_NANOS / 1_000_000_000) * 1_000_000_000 - 1
8463 ))
8464 );
8465 for invalid in [(-1, 0), (0, -1), (0, 1_000_000_000)] {
8466 assert!(!futex_timespec_is_valid(Timespec {
8467 tv_sec: invalid.0,
8468 tv_nsec: invalid.1,
8469 }));
8470 }
8471 }
8472
8473 #[test]
8474 fn futex_timeout_rejects_invalid_timespecs() {
8475 assert_eq!(
8476 parse_futex_timeout(
8477 libc::FUTEX_WAIT,
8478 Timespec {
8479 tv_sec: -1,
8480 tv_nsec: 0,
8481 },
8482 ),
8483 Err(Errno::EINVAL)
8484 );
8485 assert_eq!(
8486 parse_futex_timeout(
8487 libc::FUTEX_WAIT_BITSET,
8488 Timespec {
8489 tv_sec: 0,
8490 tv_nsec: 1_000_000_000,
8491 },
8492 ),
8493 Err(Errno::EINVAL)
8494 );
8495 }
8496
8497 fn plain_attr() -> SchedAttrFields {
8504 SchedAttrFields {
8505 policy: 0,
8506 sched_flags: 0,
8507 priority: 0,
8508 runtime: 0,
8509 deadline: 0,
8510 period: 0,
8511 }
8512 }
8513
8514 #[test]
8515 fn size_zero_is_a_well_formed_ver0_request() {
8516 assert_eq!(sched_attr_effective_size(0), Ok(SCHED_ATTR_SIZE_VER0));
8519 }
8520
8521 #[test]
8522 fn size_below_ver0_or_past_a_page_is_too_big() {
8523 assert_eq!(sched_attr_effective_size(1), Err(()));
8525 assert_eq!(sched_attr_effective_size(SCHED_ATTR_SIZE_VER0 - 1), Err(()));
8526 assert_eq!(
8527 sched_attr_effective_size(sched_attr_max_size() + 1),
8528 Err(())
8529 );
8530 }
8531
8532 #[test]
8533 fn size_from_ver0_through_one_page_is_accepted_unchanged() {
8534 for size in [
8536 SCHED_ATTR_SIZE_VER0,
8537 SCHED_ATTR_SIZE_VER1,
8538 SCHED_ATTR_SIZE_VER1 + 1,
8539 sched_attr_max_size(),
8540 ] {
8541 assert_eq!(sched_attr_effective_size(size), Ok(size), "size {}", size);
8542 }
8543 }
8544
8545 #[test]
8546 fn sched_ext_is_a_valid_policy_and_sched_iso_is_not() {
8547 assert!(is_valid_sched_policy(SCHED_EXT), "SCHED_EXT is accepted");
8550 assert!(!is_valid_sched_policy(4), "SCHED_ISO is reserved");
8551 for policy in [0, 1, 2, 3, 5, 6] {
8552 assert!(is_valid_sched_policy(policy), "policy {}", policy);
8553 }
8554 for policy in [8, 99] {
8556 assert!(!is_valid_sched_policy(policy), "policy {}", policy);
8557 }
8558 }
8559
8560 #[test]
8572 fn util_clamp_size_rule_is_decided_before_the_pid_lookup() {
8573 let mut attr = plain_attr();
8575 attr.sched_flags = SCHED_FLAG_UTIL_CLAMP_MIN;
8576 assert_eq!(
8577 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &attr),
8578 Err(Errno::EINVAL)
8579 );
8580 assert_eq!(
8581 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER1, &attr),
8582 Ok(())
8583 );
8584 assert_eq!(validate_sched_attr_after_lookup(&attr), Ok(()));
8586 }
8587
8588 #[test]
8589 fn a_negative_policy_is_decided_before_the_pid_lookup() {
8590 let mut attr = plain_attr();
8593 attr.policy = 0x8000_0000;
8594 assert_eq!(
8595 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &attr),
8596 Err(Errno::EINVAL)
8597 );
8598 attr.sched_flags = SCHED_FLAG_KEEP_POLICY;
8599 assert_eq!(
8600 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &attr),
8601 Err(Errno::EINVAL),
8602 "KEEP_POLICY must not hide a negative policy"
8603 );
8604 }
8605
8606 #[test]
8607 fn policy_and_flag_rules_are_decided_after_the_pid_lookup() {
8608 let mut bad_policy = plain_attr();
8611 bad_policy.policy = 99;
8612 assert_eq!(
8613 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &bad_policy),
8614 Ok(()),
8615 "an undefined policy must survive the near side so ESRCH can win"
8616 );
8617 assert_eq!(
8618 validate_sched_attr_after_lookup(&bad_policy),
8619 Err(Errno::EINVAL)
8620 );
8621
8622 let mut bad_flag = plain_attr();
8623 bad_flag.sched_flags = 0x80;
8624 assert_eq!(
8625 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &bad_flag),
8626 Ok(()),
8627 "an undefined sched_flags bit must survive the near side"
8628 );
8629 assert_eq!(
8630 validate_sched_attr_after_lookup(&bad_flag),
8631 Err(Errno::EINVAL)
8632 );
8633 }
8634
8635 #[test]
8636 fn keep_policy_makes_the_policy_field_irrelevant() {
8637 let mut attr = plain_attr();
8642 attr.policy = 99;
8643 assert_eq!(validate_sched_attr_after_lookup(&attr), Err(Errno::EINVAL));
8644 attr.sched_flags = SCHED_FLAG_KEEP_POLICY;
8645 assert_eq!(validate_sched_attr_after_lookup(&attr), Ok(()));
8646 }
8647
8648 #[test]
8649 fn defined_sched_flags_bits_are_accepted_and_undefined_ones_are_not() {
8650 let mut attr = plain_attr();
8654 attr.sched_flags = 0x80;
8655 assert_eq!(validate_sched_attr_after_lookup(&attr), Err(Errno::EINVAL));
8656 for flag in [
8657 SCHED_FLAG_RESET_ON_FORK,
8658 SCHED_FLAG_RECLAIM,
8659 SCHED_FLAG_DL_OVERRUN,
8660 SCHED_FLAG_KEEP_PARAMS,
8661 SCHED_FLAG_KEEP_POLICY,
8662 ] {
8663 let mut ok = plain_attr();
8664 ok.sched_flags = flag;
8665 assert_eq!(
8666 validate_sched_attr_after_lookup(&ok),
8667 Ok(()),
8668 "flag {:#x}",
8669 flag
8670 );
8671 }
8672 let mut all = plain_attr();
8676 all.sched_flags = SCHED_FLAG_ALL;
8677 assert_eq!(
8678 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER0, &all),
8679 Err(Errno::EINVAL)
8680 );
8681 assert_eq!(
8682 validate_sched_attr_before_lookup(SCHED_ATTR_SIZE_VER1, &all),
8683 Ok(())
8684 );
8685 assert_eq!(validate_sched_attr_after_lookup(&all), Ok(()));
8686 }
8687
8688 #[test]
8689 fn priority_must_agree_with_the_policy() {
8690 for policy in [0u32, 3, 5] {
8694 let mut attr = plain_attr();
8695 attr.policy = policy;
8696 attr.priority = 1;
8697 assert_eq!(
8698 validate_sched_attr_after_lookup(&attr),
8699 Err(Errno::EINVAL),
8700 "policy {} with priority 1",
8701 policy
8702 );
8703 attr.priority = 0;
8704 assert_eq!(
8705 validate_sched_attr_after_lookup(&attr),
8706 Ok(()),
8707 "policy {} with priority 0",
8708 policy
8709 );
8710 }
8711 for policy in [SCHED_FIFO, SCHED_RR] {
8712 let mut attr = plain_attr();
8713 attr.policy = policy;
8714 attr.priority = 0;
8715 assert_eq!(
8716 validate_sched_attr_after_lookup(&attr),
8717 Err(Errno::EINVAL),
8718 "rt policy {} with priority 0",
8719 policy
8720 );
8721 attr.priority = 1;
8725 assert_eq!(validate_sched_attr_after_lookup(&attr), Ok(()));
8726 }
8727 }
8728
8729 #[test]
8730 fn a_priority_past_max_rt_prio_is_refused() {
8731 let mut attr = plain_attr();
8734 attr.policy = SCHED_FIFO;
8735 attr.priority = MAX_RT_PRIO;
8736 assert_eq!(validate_sched_attr_after_lookup(&attr), Err(Errno::EINVAL));
8737 attr.priority = MAX_RT_PRIO - 1;
8738 assert_eq!(validate_sched_attr_after_lookup(&attr), Ok(()));
8739 }
8740
8741 #[test]
8742 fn deadline_parameters_are_checked() {
8743 let mut zeroed = plain_attr();
8746 zeroed.policy = SCHED_DEADLINE;
8747 assert_eq!(
8748 validate_sched_attr_after_lookup(&zeroed),
8749 Err(Errno::EINVAL),
8750 "all-zero deadline parameters"
8751 );
8752
8753 let mut inverted = plain_attr();
8754 inverted.policy = SCHED_DEADLINE;
8755 inverted.runtime = 90_000_000;
8756 inverted.deadline = 30_000_000;
8757 inverted.period = 30_000_000;
8758 assert_eq!(
8759 validate_sched_attr_after_lookup(&inverted),
8760 Err(Errno::EINVAL),
8761 "runtime > deadline"
8762 );
8763
8764 let mut sane = plain_attr();
8767 sane.policy = SCHED_DEADLINE;
8768 sane.runtime = 10_000_000;
8769 sane.deadline = 30_000_000;
8770 sane.period = 30_000_000;
8771 assert_eq!(validate_sched_attr_after_lookup(&sane), Ok(()));
8772 }
8773
8774 #[test]
8775 fn deadline_parameter_edges_follow_checkparam_dl() {
8776 assert!(!deadline_params_are_valid(1 << 20, 0, 0));
8778 assert!(!deadline_params_are_valid((1 << 10) - 1, 1 << 20, 1 << 20));
8780 assert!(deadline_params_are_valid(1 << 10, 1 << 20, 1 << 20));
8781 assert!(!deadline_params_are_valid(1 << 20, 1 << 63, 0));
8783 assert!(!deadline_params_are_valid(1 << 20, 1 << 20, 1 << 63));
8784 assert!(deadline_params_are_valid(1 << 20, 1 << 21, 0));
8787 assert!(!deadline_params_are_valid(1 << 20, 1 << 22, 1 << 21));
8789 }
8790
8791 #[test]
8792 fn the_size_written_back_on_refusal_is_the_kernel_struct_not_the_libc_mirror() {
8793 assert_eq!(SCHED_ATTR_KERNEL_SIZE, 56);
8797 assert_eq!(std::mem::size_of::<libc::sched_attr>(), 48);
8798 assert_ne!(
8799 SCHED_ATTR_KERNEL_SIZE as usize,
8800 std::mem::size_of::<libc::sched_attr>()
8801 );
8802 }
8803
8804 struct BoundedMemory {
8807 bytes: Vec<u8>,
8808 readable_len: usize,
8809 reads: std::cell::RefCell<Vec<usize>>,
8810 }
8811
8812 impl reverie::syscalls::MemoryAccess for BoundedMemory {
8813 fn read_vectored(
8814 &self,
8815 read_from: &[std::io::IoSlice<'_>],
8816 write_to: &mut [std::io::IoSliceMut<'_>],
8817 ) -> Result<usize, Errno> {
8818 let start = read_from[0].as_ptr() as usize;
8819 let want = read_from.iter().map(|slice| slice.len()).sum::<usize>();
8820 self.reads.borrow_mut().push(want);
8821 if start >= self.readable_len {
8822 return Err(Errno::EFAULT);
8823 }
8824 let avail = (self.readable_len - start).min(want);
8825 let mut copied = 0;
8826 for out in write_to.iter_mut() {
8827 if copied == avail {
8828 break;
8829 }
8830 let take = out.len().min(avail - copied);
8831 out[..take].copy_from_slice(&self.bytes[start + copied..start + copied + take]);
8832 copied += take;
8833 }
8834 Ok(copied)
8835 }
8836
8837 fn write_vectored(
8838 &mut self,
8839 _read_from: &[std::io::IoSlice<'_>],
8840 _write_to: &mut [std::io::IoSliceMut<'_>],
8841 ) -> Result<usize, Errno> {
8842 unimplemented!("this fixture never writes")
8843 }
8844 }
8845
8846 fn bounded(bytes: Vec<u8>, readable_len: usize) -> BoundedMemory {
8847 BoundedMemory {
8848 bytes,
8849 readable_len,
8850 reads: std::cell::RefCell::new(Vec::new()),
8851 }
8852 }
8853
8854 fn tail_base() -> AddrMut<'static, u8> {
8855 AddrMut::<u8>::from_raw(1).expect("nonzero base")
8856 }
8857
8858 #[test]
8866 fn a_nonzero_byte_before_a_fault_is_e2big_not_efault() {
8867 let off = SCHED_ATTR_KERNEL_SIZE as usize;
8868 let mut bytes = vec![0u8; off + 4096];
8869 bytes[off + 1] = 0xAA; let readable = off + 512; let memory = bounded(bytes, readable);
8872 assert_eq!(
8873 scan_tail_is_zeroed(&memory, tail_base(), off, 4096),
8874 TailVerdict::NotZeroed,
8875 "a non-zero byte the scan reaches first must win over a later fault"
8876 );
8877 }
8878
8879 #[test]
8882 fn a_fault_before_any_nonzero_byte_is_efault() {
8883 let off = SCHED_ATTR_KERNEL_SIZE as usize;
8884 let bytes = vec![0u8; off + 4096];
8885 let memory = bounded(bytes, off + 512);
8886 assert_eq!(
8887 scan_tail_is_zeroed(&memory, tail_base(), off, 4096),
8888 TailVerdict::Faulted,
8889 "an unreadable byte reached before anything non-zero must be EFAULT"
8890 );
8891 }
8892
8893 #[test]
8902 fn tail_reads_are_never_small_enough_to_bypass_guest_protection() {
8903 let off = SCHED_ATTR_KERNEL_SIZE as usize;
8904 for tail_len in 1..=16usize {
8905 let bytes = vec![0u8; off + tail_len + 64];
8906 let memory = bounded(bytes, off + tail_len + 64);
8907 assert_eq!(
8908 scan_tail_is_zeroed(&memory, tail_base(), off, tail_len),
8909 TailVerdict::AllZero
8910 );
8911 let reads = memory.reads.borrow().clone();
8912 assert!(!reads.is_empty(), "tail_len {tail_len} issued no read");
8913 for length in reads {
8914 assert!(
8915 length > std::mem::size_of::<u64>(),
8916 "tail_len {tail_len} issued a {length}-byte read, which safeptrace \
8917 serves with PTRACE_PEEKDATA and which therefore bypasses guest \
8918 page protections"
8919 );
8920 }
8921 }
8922 }
8923
8924 #[test]
8931 fn keep_policy_validates_against_the_virtual_current_policy() {
8932 let attr = SchedAttrFields {
8933 policy: 99, sched_flags: SCHED_FLAG_KEEP_POLICY,
8935 priority: 1,
8936 runtime: 0,
8937 deadline: 0,
8938 period: 0,
8939 };
8940 assert_eq!(
8941 validate_sched_attr_after_lookup(&attr),
8942 Err(Errno::EINVAL),
8943 "priority 1 under the virtual SCHED_OTHER current policy must be refused"
8944 );
8945
8946 let ok = SchedAttrFields {
8949 priority: 0,
8950 ..attr
8951 };
8952 assert_eq!(
8953 validate_sched_attr_after_lookup(&ok),
8954 Ok(()),
8955 "KEEP_POLICY must still ignore the policy field itself"
8956 );
8957 }
8958
8959 #[test]
8962 fn the_virtual_current_policy_is_what_sched_getattr_reports() {
8963 assert_eq!(
8964 VIRTUAL_CURRENT_POLICY,
8965 libc::SCHED_OTHER as u32,
8966 "handle_sched_getattr writes SCHED_OTHER into sched_policy for every thread; \
8967 KEEP_POLICY must substitute that same value"
8968 );
8969 }
8970}
8971
8972pub(crate) fn real_uid(tid: i32) -> Option<u32> {
8974 let status = std::fs::read_to_string(format!("/proc/{tid}/status")).ok()?;
8975 status
8976 .lines()
8977 .find_map(|line| line.strip_prefix("Uid:"))?
8978 .split_whitespace()
8979 .next()?
8980 .parse()
8981 .ok()
8982}
8983
8984const CLONE_ARGS_SIZE_VER0: usize = 64;
8988const CLONE_ARGS_SIZE_VER1: usize = 80;
8989const CLONE_ARGS_SIZE_VER2: usize = 88;
8990
8991const CLONE3_STAGED_MAX: usize = 512;
8994
8995enum CloneInjection {
8997 Ran(Result<i64, Errno>),
8999 Refused(String),
9001}
9002
9003async fn inject_traced_clone<T: RecordOrReplay, G: Guest<Detcore<T>>>(
9018 guest: &mut G,
9019 clone_family: syscalls::family::CloneFamily,
9020 flags: CloneFlags,
9021) -> CloneInjection {
9022 use syscalls::family::CloneFamily;
9023 if !flags.contains(CloneFlags::CLONE_UNTRACED) {
9024 return CloneInjection::Ran(
9025 crate::reserved_input::inject_original_clone(guest, Syscall::from(clone_family)).await,
9026 );
9027 }
9028 match clone_family {
9029 CloneFamily::Clone(clone) => {
9030 let traced = clone.with_flags(clone.flags() - nix::sched::CloneFlags::CLONE_UNTRACED);
9031 CloneInjection::Ran(
9032 crate::reserved_input::inject_original_clone(guest, Syscall::Clone(traced)).await,
9033 )
9034 }
9035 CloneFamily::Clone3(clone) => inject_traced_clone3(guest, clone).await,
9036 #[cfg(not(target_arch = "aarch64"))]
9037 CloneFamily::Fork(_) | CloneFamily::Vfork(_) => {
9038 unreachable!("fork and vfork take no flags")
9039 }
9040 }
9041}
9042
9043async fn inject_traced_clone3<T: RecordOrReplay, G: Guest<Detcore<T>>>(
9060 guest: &mut G,
9061 clone: syscalls::Clone3,
9062) -> CloneInjection {
9063 let size = clone.size();
9064 let page = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).unwrap_or(4096);
9065 let mut bytes = vec![0_u8; size];
9066 let readable = clone.args().is_some_and(|args| {
9067 (CLONE_ARGS_SIZE_VER0..=page).contains(&size)
9068 && crate::syscalls::read_guest_exact(&guest.memory(), args.cast(), &mut bytes).is_ok()
9069 });
9070 if !readable {
9071 return CloneInjection::Ran(
9072 crate::reserved_input::inject_original_clone(guest, Syscall::Clone3(clone)).await,
9073 );
9074 }
9075 let refused = |why: &str| {
9076 CloneInjection::Refused(format!(
9077 "clone3 with CLONE_UNTRACED: hermit must trace the child, so it clears the flag in a \
9078 copy of clone_args staged below the stack pointer, but {why}"
9079 ))
9080 };
9081 let nonzero = bytes
9082 .iter()
9083 .rposition(|byte| *byte != 0)
9084 .map_or(0, |last| last + 1);
9085 let len = nonzero.max(size.min(CLONE_ARGS_SIZE_VER2));
9086 if len > CLONE3_STAGED_MAX {
9087 return refused(&format!(
9088 "its nonzero bytes run past the {CLONE3_STAGED_MAX} that can be staged"
9089 ));
9090 }
9091 let mut copy = [0_u8; CLONE3_STAGED_MAX];
9092 copy[..len].copy_from_slice(&bytes[..len]);
9093 let field = |copy: &[u8], offset: usize| {
9094 u64::from_ne_bytes(copy[offset..offset + 8].try_into().unwrap())
9095 };
9096 let traced = field(©, 0) & !CloneFlags::CLONE_UNTRACED.bits();
9097 copy[..8].copy_from_slice(&traced.to_ne_bytes());
9098
9099 let rsp = guest.regs().await.rsp as usize;
9101 let Some(start) = rsp.checked_sub(128 + CLONE3_STAGED_MAX) else {
9102 return refused("the stack pointer is too low to stage it");
9103 };
9104 let staged = start..start + CLONE3_STAGED_MAX;
9105 let set_tid_bytes = if len >= CLONE_ARGS_SIZE_VER1 {
9109 field(©, 72).saturating_mul(4)
9110 } else {
9111 0
9112 };
9113 let pointed = [
9114 (field(©, 8), 4),
9115 (field(©, 16), 4),
9116 (field(©, 24), 4),
9117 (field(©, 64), set_tid_bytes),
9118 ];
9119 if pointed.iter().any(|&(address, bytes)| {
9120 let address = address as usize;
9121 bytes > 0 && address < staged.end && address.saturating_add(bytes as usize) > staged.start
9122 }) {
9123 return refused("one of its pointers leads into the staged copy");
9124 }
9125
9126 let mut stack = guest.stack().await;
9127 let scratch: Addr<[u8; CLONE3_STAGED_MAX]> = stack.push(copy);
9128 let mut saved = [0_u8; CLONE3_STAGED_MAX];
9129 if let Err(errno) = crate::syscalls::read_guest_exact(
9130 &guest.memory(),
9131 AddrMut::from_raw(scratch.as_raw()).expect("a nonzero address"),
9132 &mut saved,
9133 ) {
9134 return refused(&format!("the stack below it cannot be read ({errno})"));
9135 }
9136 let guard = match stack.commit() {
9137 Ok(guard) => guard,
9138 Err(errno) => {
9139 restore_staged_clone_args(guest, scratch, &saved);
9140 return refused(&format!("the stack below it cannot be written ({errno})"));
9141 }
9142 };
9143 let result = crate::reserved_input::inject_original_clone(
9144 guest,
9145 Syscall::Clone3(
9146 clone
9147 .with_args(AddrMut::from_raw(scratch.as_raw()))
9148 .with_size(len),
9149 ),
9150 )
9151 .await;
9152 restore_staged_clone_args(guest, scratch, &saved);
9153 drop(guard);
9154 CloneInjection::Ran(result)
9155}
9156
9157fn restore_staged_clone_args<T: RecordOrReplay, G: Guest<Detcore<T>>>(
9160 guest: &mut G,
9161 scratch: Addr<[u8; CLONE3_STAGED_MAX]>,
9162 saved: &[u8; CLONE3_STAGED_MAX],
9163) {
9164 let restored = AddrMut::<u8>::from_raw(scratch.as_raw())
9165 .is_some_and(|scratch| guest.memory().write_exact(scratch, saved).is_ok());
9166 if !restored {
9167 info!("Could not restore the guest bytes under a staged clone_args.");
9168 }
9169}
9170
9171fn clone3_field<M: MemoryAccess>(
9177 memory: &M,
9178 clone: &syscalls::Clone3,
9179 offset: usize,
9180) -> Option<u64> {
9181 if clone.size() < CLONE_ARGS_SIZE_VER0 || offset + 8 > clone.size() {
9182 return None;
9183 }
9184 let field = clone.args()?.as_raw().checked_add(offset)?;
9185 if !super::chosen_ids::within_user_memory(field, std::mem::size_of::<u64>()) {
9188 return None;
9189 }
9190 memory.read_value(Addr::<u64>::from_raw(field)?).ok()
9191}
9192
9193fn clone3_signal_fields_valid(flags: u64, exit_signal: u64) -> bool {
9200 const CSIGNAL: u64 = 0xff;
9201 const CLONE_NEWTIME: u64 = 0x80;
9202 exit_signal <= 64 && flags & CSIGNAL & !CLONE_NEWTIME == 0
9203}
9204
9205#[cfg(test)]
9206mod clone3_signal_fields_tests {
9207 #[test]
9210 fn clone3_accepts_only_clone_newtime_among_the_csignal_bits() {
9211 assert!(super::clone3_signal_fields_valid(0x80, 17));
9212 assert!(super::clone3_signal_fields_valid(0, 64));
9213 for bit in 0..7 {
9214 assert!(!super::clone3_signal_fields_valid(1 << bit, 17), "{bit}");
9215 }
9216 assert!(!super::clone3_signal_fields_valid(0, 65));
9217 }
9218}
9219
9220fn linux_refuses_clone_flags<M: MemoryAccess>(
9232 memory: &M,
9233 clone_family: &syscalls::family::CloneFamily,
9234 user_limit: Option<u64>,
9235) -> bool {
9236 match clone_family {
9237 syscalls::family::CloneFamily::Clone(clone) => {
9240 clone_flags_einval(u64::from(clone.flags().bits() as u32) & !0xff, true)
9241 }
9242 syscalls::family::CloneFamily::Clone3(clone) => {
9243 let size = clone.size();
9244 let page = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).unwrap_or(0);
9245 let mut bytes = vec![0u8; size.min(page)];
9246 (CLONE_ARGS_SIZE_VER0..=page).contains(&size)
9247 && clone
9248 .args()
9249 .and_then(|args| Addr::<u8>::from_raw(args.as_raw()))
9250 .filter(|args| super::chosen_ids::within_user_memory(args.as_raw(), size))
9251 .is_some_and(|args| {
9252 memory.read_exact_with_user_access(args, &mut bytes).is_ok()
9253 })
9254 && clone3_args_einval(&bytes, user_limit)
9255 }
9256 #[cfg(not(target_arch = "aarch64"))]
9257 _ => false,
9258 }
9259}
9260
9261fn clone_flags_einval(flags: u64, pidfd_is_parent_tid: bool) -> bool {
9268 let flags = CloneFlags::from_bits_retain(flags);
9269 let thread = flags.contains(CloneFlags::CLONE_THREAD);
9270 (flags.contains(CloneFlags::CLONE_PIDFD | CloneFlags::CLONE_PARENT_SETTID)
9271 && pidfd_is_parent_tid)
9272 || flags.contains(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_FS)
9273 || flags.contains(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_FS)
9274 || (thread && !flags.contains(CloneFlags::CLONE_SIGHAND))
9275 || (flags.contains(CloneFlags::CLONE_SIGHAND) && !flags.contains(CloneFlags::CLONE_VM))
9276 || (thread && flags.intersects(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWPID))
9277 || flags.contains(CloneFlags::CLONE_PIDFD | CloneFlags::CLONE_DETACHED)
9278}
9279
9280fn clone3_args_einval(bytes: &[u8], user_limit: Option<u64>) -> bool {
9296 const CLONE_CLEAR_SIGHAND: u64 = 1 << 32;
9297 const CLONE_INTO_CGROUP: u64 = 1 << 33;
9298 if bytes
9299 .iter()
9300 .skip(CLONE_ARGS_SIZE_VER2)
9301 .any(|byte| *byte != 0)
9302 {
9303 return false;
9304 }
9305 let field = |offset: usize| {
9306 let mut word = [0u8; 8];
9307 for (i, byte) in word.iter_mut().enumerate() {
9308 *byte = bytes.get(offset + i).copied().unwrap_or(0);
9309 }
9310 u64::from_ne_bytes(word)
9311 };
9312 let (flags, exit_signal, stack, stack_size) = (field(0), field(32), field(40), field(48));
9313 let (set_tid, set_tid_size) = (field(64), field(72));
9314 let into_cgroup = flags & CLONE_INTO_CGROUP != 0;
9315 if set_tid_size > 32
9316 || (set_tid == 0) != (set_tid_size == 0)
9317 || exit_signal > 64
9318 || (into_cgroup && (field(80) > i32::MAX as u64 || bytes.len() < CLONE_ARGS_SIZE_VER2))
9319 {
9320 return true;
9321 }
9322 if set_tid != 0 {
9323 return false;
9324 }
9325 let sighand = libc::CLONE_SIGHAND as u64 | CLONE_CLEAR_SIGHAND;
9326 flags & !(0xffff_ffff | CLONE_CLEAR_SIGHAND | CLONE_INTO_CGROUP) != 0
9327 || !clone3_signal_fields_valid(flags, exit_signal)
9328 || flags & libc::CLONE_DETACHED as u64 != 0
9329 || flags & sighand == sighand
9330 || (flags & (libc::CLONE_THREAD | libc::CLONE_PARENT) as u64 != 0 && exit_signal != 0)
9331 || (stack == 0) != (stack_size == 0)
9332 || stack.checked_add(stack_size).is_none()
9333 || (stack != 0 && user_limit.is_some_and(|limit| stack + stack_size > limit))
9334 || clone_flags_einval(flags, field(8) == field(24))
9335}
9336
9337#[cfg(test)]
9338mod linux_clone_einval_tests {
9339 use super::clone_flags_einval;
9340 use super::clone3_args_einval;
9341
9342 const VM: u64 = libc::CLONE_VM as u64;
9343 const FS: u64 = libc::CLONE_FS as u64;
9344 const FILES: u64 = libc::CLONE_FILES as u64;
9345 const SIGHAND: u64 = libc::CLONE_SIGHAND as u64;
9346 const THREAD: u64 = libc::CLONE_THREAD as u64;
9347 const PIDFD: u64 = 0x1000;
9348 const PARENT_SETTID: u64 = libc::CLONE_PARENT_SETTID as u64;
9349 const PTHREAD: u64 = VM | FS | FILES | SIGHAND | THREAD | libc::CLONE_SYSVSEM as u64;
9351
9352 #[test]
9357 fn only_the_kernels_flag_rules_refuse() {
9358 let newns = libc::CLONE_NEWNS as u64;
9359 let newuser = libc::CLONE_NEWUSER as u64;
9360 let newpid = libc::CLONE_NEWPID as u64;
9361 let detached = libc::CLONE_DETACHED as u64;
9362 for (flags, pidfd_is_parent_tid, einval) in [
9363 (0, true, false),
9364 (VM | libc::CLONE_VFORK as u64, true, false),
9365 (PTHREAD, true, false),
9366 (VM | SIGHAND | THREAD, true, false),
9367 (VM | FS, true, false),
9368 (VM | FILES, true, false),
9369 (VM | SIGHAND, true, false),
9370 (VM | SIGHAND | THREAD | newns, true, false),
9371 (PIDFD | PTHREAD, true, false),
9372 (PIDFD | PARENT_SETTID, false, false),
9373 (newns | FS, true, true),
9374 (newuser | FS, true, true),
9375 (VM | FS | FILES | THREAD, true, true),
9376 (SIGHAND, true, true),
9377 (PTHREAD | newpid, true, true),
9378 (VM | SIGHAND | THREAD | newuser, true, true),
9379 (PIDFD | PARENT_SETTID, true, true),
9380 (PIDFD | detached, true, true),
9381 ] {
9382 assert_eq!(
9383 clone_flags_einval(flags, pidfd_is_parent_tid),
9384 einval,
9385 "{flags:#x} {pidfd_is_parent_tid}"
9386 );
9387 }
9388 }
9389
9390 fn args(len: usize, fields: &[(usize, u64)]) -> Vec<u8> {
9392 let mut bytes = vec![0u8; len];
9393 for (offset, value) in fields {
9394 bytes[*offset..*offset + 8].copy_from_slice(&value.to_ne_bytes());
9395 }
9396 bytes
9397 }
9398
9399 #[test]
9406 fn clone3_decides_only_what_the_kernel_checks_first() {
9407 let sigchld = libc::SIGCHLD as u64;
9408 let fork = [(32, sigchld)];
9409 for (bytes, einval) in [
9410 (args(64, &fork), false),
9411 (args(88, &[(0, PTHREAD), (40, 0x1000), (48, 0x1000)]), false),
9412 (args(88, &[(0, 0x80), (32, sigchld)]), false),
9413 (args(128, &[(0, VM | FS), (32, sigchld)]), false),
9414 (args(88, &[(0, PTHREAD), (32, sigchld)]), true),
9415 (
9416 args(88, &[(0, libc::CLONE_PARENT as u64), (32, sigchld)]),
9417 true,
9418 ),
9419 (args(88, &[(0, 0x11)]), true),
9420 (args(88, &[(0, libc::CLONE_DETACHED as u64)]), true),
9421 (args(88, &[(0, VM | SIGHAND | (1 << 32))]), true),
9422 (args(88, &[(0, 1 << 34)]), true),
9423 (args(88, &[(32, 65)]), true),
9424 (args(88, &[(40, 0x1000)]), true),
9425 (args(88, &[(48, 0x1000)]), true),
9426 (args(88, &[(40, u64::MAX), (48, 2)]), true),
9427 (args(88, &[(64, 0x1000)]), true),
9428 (args(88, &[(72, 1)]), true),
9429 (args(88, &[(64, 0x1000), (72, 33)]), true),
9430 (args(80, &[(0, 1 << 33)]), true),
9431 (args(88, &[(0, 1 << 33), (80, 1 << 31)]), true),
9432 (args(88, &[(0, 1 << 33), (80, 3)]), false),
9433 (args(88, &[(0, THREAD), (64, 0x1000), (72, 1)]), false),
9434 (args(96, &[(0, THREAD), (88, 1)]), false),
9435 (args(96, &[(0, THREAD)]), true),
9436 (
9437 args(88, &[(0, PIDFD | PARENT_SETTID), (8, 8), (24, 8)]),
9438 true,
9439 ),
9440 (
9441 args(88, &[(0, PIDFD | PARENT_SETTID), (8, 8), (24, 16)]),
9442 false,
9443 ),
9444 (
9445 args(64, &[(0, libc::CLONE_NEWNS as u64 | FS), (32, sigchld)]),
9446 true,
9447 ),
9448 ] {
9449 assert_eq!(clone3_args_einval(&bytes, None), einval, "{bytes:x?}");
9450 }
9451 }
9452
9453 #[test]
9458 fn clone3_refuses_a_stack_past_the_reported_user_limit() {
9459 let sigchld = libc::SIGCHLD as u64;
9460 let limit = (1_u64 << 47) - 4096;
9461 let stack = |base: u64, size: u64| args(88, &[(32, sigchld), (40, base), (48, size)]);
9462 for (bytes, user_limit, einval) in [
9463 (stack(0xffff_8000_0000_0000, 0x1000), Some(limit), true),
9464 (stack(0xffff_8000_0000_0000, 0x1000), None, false),
9465 (stack(limit - 0x1000, 0x1001), Some(limit), true),
9466 (stack(limit - 0x1000, 0x1000), Some(limit), false),
9467 (stack(0x7000_0000, 0x1000), Some(limit), false),
9468 (args(88, &[(32, sigchld)]), Some(limit), false),
9469 ] {
9470 assert_eq!(
9471 clone3_args_einval(&bytes, user_limit),
9472 einval,
9473 "{bytes:x?} {user_limit:?}"
9474 );
9475 }
9476 }
9477}
9478
9479fn clone_pidfd_address<M: MemoryAccess>(
9490 clone_family: &syscalls::family::CloneFamily,
9491 memory: &M,
9492 flags: CloneFlags,
9493) -> Option<usize> {
9494 let (flags, address) = match clone_family {
9495 syscalls::family::CloneFamily::Clone(clone) => {
9496 (flags, clone.ptid().map(|ptid| ptid.as_raw()))
9497 }
9498 syscalls::family::CloneFamily::Clone3(clone) => (
9499 flags,
9500 Some(usize::try_from(clone3_field(memory, clone, 8)?).ok()?),
9501 ),
9502 #[cfg(not(target_arch = "aarch64"))]
9503 _ => return None,
9504 };
9505 if !flags.contains(CloneFlags::CLONE_PIDFD)
9506 || flags
9507 .intersects(CloneFlags::CLONE_VM | CloneFlags::CLONE_FILES | CloneFlags::CLONE_VFORK)
9508 {
9509 return None;
9510 }
9511 address
9512}
9513
9514#[cfg(test)]
9520mod disposition_mask_tests {
9521 use std::sync::Mutex;
9522
9523 use reverie::Errno;
9524 use reverie::Error;
9525 use reverie::GlobalRPC;
9526 use reverie::GlobalTool;
9527 use reverie::Guest;
9528 use reverie::Pid;
9529 use reverie::syscalls::Addr;
9530 use reverie::syscalls::AddrMut;
9531 use reverie::syscalls::LocalMemory;
9532 use reverie::syscalls::MemoryAccess;
9533 use reverie::syscalls::SyscallInfo;
9534
9535 use super::*;
9536 use crate::tool_global::GlobalState;
9537
9538 const GUEST_MASK: KernelSigset = (1 << (libc::SIGUSR1 - 1)) | (1 << (libc::SIGHUP - 1));
9539
9540 struct UnusedStack;
9541 struct UnusedStackGuard;
9542 impl Drop for UnusedStackGuard {
9543 fn drop(&mut self) {}
9544 }
9545 impl reverie::Stack for UnusedStack {
9546 type StackGuard = UnusedStackGuard;
9547 fn size(&self) -> usize {
9548 panic!("the disposition mask uses the caller's addresses")
9549 }
9550 fn capacity(&self) -> usize {
9551 panic!("the disposition mask uses the caller's addresses")
9552 }
9553 fn push<'a, V>(&mut self, _: V) -> Addr<'a, V> {
9554 panic!("the disposition mask uses the caller's addresses")
9555 }
9556 fn reserve<'a, V>(&mut self) -> AddrMut<'a, V> {
9557 panic!("the disposition mask uses the caller's addresses")
9558 }
9559 fn commit(self) -> Result<Self::StackGuard, Errno> {
9560 panic!("the disposition mask uses the caller's addresses")
9561 }
9562 }
9563
9564 struct MaskGuest {
9568 config: Config,
9569 thread: crate::ThreadState<()>,
9570 mask: KernelSigset,
9571 calls: Mutex<Vec<(i32, bool)>>,
9572 }
9573
9574 #[reverie::tool]
9575 impl GlobalRPC<GlobalState> for MaskGuest {
9576 async fn send_rpc(
9577 &self,
9578 message: <GlobalState as GlobalTool>::Request,
9579 ) -> <GlobalState as GlobalTool>::Response {
9580 panic!("the disposition mask sends no RPC here: {:?}", message.2)
9581 }
9582 fn config(&self) -> &Config {
9583 &self.config
9584 }
9585 }
9586
9587 #[reverie::tool]
9588 impl Guest<Detcore> for MaskGuest {
9589 type Memory = LocalMemory;
9590 type Stack = UnusedStack;
9591 fn tid(&self) -> Pid {
9592 Pid::from_raw(41)
9593 }
9594 fn pid(&self) -> Pid {
9595 Pid::from_raw(41)
9596 }
9597 fn ppid(&self) -> Option<Pid> {
9598 None
9599 }
9600 fn memory(&self) -> LocalMemory {
9601 LocalMemory::new()
9602 }
9603 fn thread_state(&self) -> &crate::ThreadState<()> {
9604 &self.thread
9605 }
9606 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
9607 &mut self.thread
9608 }
9609 async fn regs(&mut self) -> libc::user_regs_struct {
9610 panic!("no registers are read")
9611 }
9612 async fn stack(&mut self) -> UnusedStack {
9613 panic!("the disposition mask uses the caller's addresses")
9614 }
9615 async fn daemonize(&mut self) {
9616 panic!("no daemonize")
9617 }
9618 async fn inject<S: SyscallInfo>(&mut self, call: S) -> Result<i64, Errno> {
9619 assert_eq!(call.number(), syscalls::Sysno::rt_sigprocmask);
9620 let args = call.into_parts().1;
9621 let (how, set, old) = (args.arg0 as i32, args.arg1, args.arg2);
9622 self.calls.lock().unwrap().push((how, set != 0));
9623 let previous = self.mask;
9624 if set != 0 {
9625 let set: KernelSigset = LocalMemory::new()
9626 .read_value(Addr::<KernelSigset>::from_raw(set).unwrap())
9627 .unwrap();
9628 self.mask = set;
9629 }
9630 if old != 0 {
9631 LocalMemory::new()
9632 .write_value(AddrMut::<KernelSigset>::from_raw(old).unwrap(), &previous)
9633 .unwrap();
9634 }
9635 Ok(0)
9636 }
9637 async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
9638 panic!("no tail injection")
9639 }
9640 fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
9641 panic!("no timer")
9642 }
9643 fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
9644 panic!("no timer")
9645 }
9646 fn read_clock(&mut self) -> Result<u64, Error> {
9647 panic!("no clock")
9648 }
9649 }
9650
9651 fn mask_guest(gate: bool) -> MaskGuest {
9652 let mut config = Config {
9653 sequentialize_threads: true,
9654 max_timeslice: None,
9655 in_guest_signal_delivery: gate,
9656 ..Config::default()
9657 };
9658 config.backend = reverie::BackendCapabilities::LITEINST_IN_GUEST;
9659 let detpid = DetPid::from_raw(41);
9660 let mut thread = crate::ThreadState::new(detpid, &config, ());
9661 thread.detpid = Some(detpid);
9662 MaskGuest {
9663 config,
9664 thread,
9665 mask: GUEST_MASK,
9666 calls: Mutex::new(Vec::new()),
9667 }
9668 }
9669
9670 #[tokio::test]
9676 async fn a_gated_managed_wait_reads_the_guest_mask_without_replacing_it() {
9677 for (gate, calls_during, mask_during, calls_after) in [
9678 (
9679 true,
9680 vec![(libc::SIG_SETMASK, false)],
9681 GUEST_MASK,
9682 vec![(libc::SIG_SETMASK, false)],
9683 ),
9684 (
9685 false,
9686 vec![(libc::SIG_SETMASK, true)],
9687 blocked_signal_mask(),
9688 vec![(libc::SIG_SETMASK, true), (libc::SIG_SETMASK, true)],
9689 ),
9690 ] {
9691 let mut guest = mask_guest(gate);
9692 let blocked = blocked_signal_mask();
9693 let mut old = 0 as KernelSigset;
9694 let blocked_addr =
9695 Addr::<KernelSigset>::from_raw(&blocked as *const _ as usize).unwrap();
9696 let old_addr = AddrMut::<KernelSigset>::from_raw(&mut old as *mut _ as usize).unwrap();
9697 let read = block_signals_for_disposition(&mut guest, blocked_addr, old_addr)
9698 .await
9699 .unwrap();
9700 assert_eq!(read, GUEST_MASK, "gate={gate}");
9701 assert_eq!(*guest.calls.lock().unwrap(), calls_during, "gate={gate}");
9702 assert_eq!(
9703 guest.mask, mask_during,
9704 "gate={gate}: the mask while the wait runs"
9705 );
9706 restore_signals_after_disposition(&mut guest, old_addr)
9707 .await
9708 .unwrap();
9709 assert_eq!(*guest.calls.lock().unwrap(), calls_after, "gate={gate}");
9710 assert_eq!(
9711 guest.mask, GUEST_MASK,
9712 "gate={gate}: the mask after the wait"
9713 );
9714 }
9715 }
9716}