1use std::collections::hash_map::DefaultHasher;
12use std::hash::Hash;
13use std::hash::Hasher;
14
15use reverie::BackendCapabilities;
16use reverie::Error;
17use reverie::Guest;
18use reverie::syscalls;
19use reverie::syscalls::AddrMut;
20#[cfg(target_arch = "x86_64")]
21use reverie::syscalls::ArchPrctlCmd;
22use reverie::syscalls::Errno;
23use reverie::syscalls::MemoryAccess;
24use reverie::syscalls::Sysno;
25
26use crate::consts::DEFAULT_HOSTNAME;
27use crate::detlog;
28use crate::fd::RandomDeviceStream;
29use crate::random::RANDOM_FILL_CHUNK_BYTES;
30#[cfg(test)]
31use crate::random::getrandom_request_len;
32use crate::random::splitmix64;
33#[cfg(test)]
34use crate::random::validate_getrandom_flags;
35use crate::random::write_random_chunk;
36use crate::record_or_replay::RecordOrReplay;
37use crate::scheduler::HostTimedSignalScope;
38use crate::tool_global::SigalrmControl;
39use crate::tool_global::create_session;
40use crate::tool_global::record_host_timed_signals;
41use crate::tool_global::refuse_sigalrm;
42use crate::tool_global::registered_process_of;
43use crate::tool_global::set_child_subreaper;
44use crate::tool_global::set_process_group;
45use crate::tool_local::Detcore;
46use crate::types::DetPid;
47
48#[cfg(target_arch = "x86_64")]
49const ARCH_GET_XCOMP_SUPP: libc::c_int = 0x1021;
50#[cfg(target_arch = "x86_64")]
51const ARCH_GET_XCOMP_PERM: libc::c_int = 0x1022;
52#[cfg(target_arch = "x86_64")]
53const ARCH_REQ_XCOMP_PERM: libc::c_int = 0x1023;
54#[cfg(target_arch = "x86_64")]
55const ARCH_GET_XCOMP_GUEST_PERM: libc::c_int = 0x1024;
56#[cfg(target_arch = "x86_64")]
57const ARCH_REQ_XCOMP_GUEST_PERM: libc::c_int = 0x1025;
58
59#[cfg(target_arch = "x86_64")]
60const ARCH_SHSTK_ENABLE: libc::c_int = 0x5001;
61#[cfg(target_arch = "x86_64")]
62const ARCH_SHSTK_DISABLE: libc::c_int = 0x5002;
63#[cfg(target_arch = "x86_64")]
64const ARCH_SHSTK_LOCK: libc::c_int = 0x5003;
65#[cfg(target_arch = "x86_64")]
66const ARCH_SHSTK_UNLOCK: libc::c_int = 0x5004;
67#[cfg(target_arch = "x86_64")]
68const ARCH_SHSTK_STATUS: libc::c_int = 0x5005;
69#[cfg(target_arch = "x86_64")]
70const ARCH_SHSTK_VALID_MASK: usize = 0b11;
71
72const SECCOMP_SET_MODE_STRICT: u32 = 0;
73const SECCOMP_SET_MODE_FILTER: u32 = 1;
74const SECCOMP_GET_ACTION_AVAIL: u32 = 2;
75const SECCOMP_GET_NOTIF_SIZES: u32 = 3;
76const SECCOMP_FILTER_FLAG_TSYNC: u32 = 1;
77
78fn seccomp_result(op: u32, flags: u32, has_args: bool) -> Result<i64, Errno> {
79 if op > SECCOMP_GET_NOTIF_SIZES {
80 return Err(Errno::EINVAL);
81 }
82 if op == SECCOMP_SET_MODE_STRICT && (flags != 0 || has_args) {
83 return Err(Errno::EINVAL);
84 }
85 if op == SECCOMP_SET_MODE_FILTER && flags & !SECCOMP_FILTER_FLAG_TSYNC != 0 {
86 return Err(Errno::EINVAL);
87 }
88 if matches!(
89 op,
90 SECCOMP_SET_MODE_FILTER | SECCOMP_GET_ACTION_AVAIL | SECCOMP_GET_NOTIF_SIZES
91 ) && !has_args
92 {
93 return Err(Errno::EFAULT);
94 }
95
96 Err(Errno::EOPNOTSUPP)
99}
100
101fn delivers_parent_death_signal(config: &crate::config::Config) -> bool {
106 config.sequentialize_threads && config.backend.host_task_reads_reach_guest_threads
107}
108
109fn holds_parent_death_signal(config: &crate::config::Config) -> bool {
117 delivers_parent_death_signal(config) || config.in_guest_refuses_host_timed_producers
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122enum ParentDeathSignalRoute {
123 Hold,
125 Kernel,
127 Refuse,
130}
131
132fn parent_death_signal_route(
142 signal: libc::c_ulong,
143 holds: bool,
144 refuses_unheld: bool,
145) -> ParentDeathSignalRoute {
146 if holds && (signal == 0 || detcore_sends_parent_death_signal(signal)) {
147 ParentDeathSignalRoute::Hold
148 } else if refuses_unheld
149 && (1..=64).contains(&signal)
150 && signal != libc::SIGKILL as libc::c_ulong
151 {
152 ParentDeathSignalRoute::Refuse
153 } else {
154 ParentDeathSignalRoute::Kernel
155 }
156}
157
158fn detcore_sends_parent_death_signal(signal: libc::c_ulong) -> bool {
164 (1..=31).contains(&signal)
165 && ![libc::SIGKILL, libc::SIGSTOP, libc::SIGALRM].contains(&(signal as libc::c_int))
166}
167
168fn is_supported_prctl_option(option: libc::c_int) -> bool {
169 matches!(
170 option,
171 libc::PR_SET_NAME
172 | libc::PR_GET_NAME
173 | libc::PR_SET_THP_DISABLE
174 | libc::PR_GET_THP_DISABLE
175 | libc::PR_SET_DUMPABLE
183 | libc::PR_GET_DUMPABLE
184 | libc::PR_SET_KEEPCAPS
197 | libc::PR_GET_KEEPCAPS
198 | libc::PR_SET_PDEATHSIG
216 | libc::PR_GET_PDEATHSIG
217 | libc::PR_CAPBSET_DROP
231 | libc::PR_CAP_AMBIENT
232 | libc::PR_SET_PTRACER
248 )
249}
250
251fn models_child_subreapers(sequentialize_threads: bool, backend: &BackendCapabilities) -> bool {
263 sequentialize_threads
264 && backend.tracks_process_children
265 && !backend.emulates_child_waits
266 && !backend.process_exits_complete_asynchronously
267 && !backend.reports_physical_process_exits
268}
269
270fn prctl_task_name<M: reverie::syscalls::MemoryAccess>(
279 memory: &M,
280 address: libc::c_ulong,
281) -> Option<crate::procfs::task_stat::TaskComm> {
282 let mut bytes = [0_u8; crate::procfs::task_stat::TASK_COMM_LEN - 1];
283 let address = AddrMut::<u8>::from_raw(usize::try_from(address).ok()?)?;
284 let read = crate::syscalls::files::read_guest_prefix(memory, address, &mut bytes).ok()?;
285 let readable = &bytes[..read];
286 if read < bytes.len() && !readable.contains(&0) {
287 return None;
288 }
289 Some(crate::procfs::task_stat::TaskComm::new(readable))
290}
291
292fn is_valid_prio_which(which: i32) -> bool {
294 which == libc::PRIO_PROCESS as i32
295 || which == libc::PRIO_PGRP as i32
296 || which == libc::PRIO_USER as i32
297}
298
299fn getpriority_result(which: i32) -> Result<i64, Errno> {
307 if is_valid_prio_which(which) {
308 Ok(20)
309 } else {
310 Err(Errno::EINVAL)
311 }
312}
313
314fn setpriority_result(which: i32) -> Result<i64, Errno> {
319 if is_valid_prio_which(which) {
320 Ok(0)
321 } else {
322 Err(Errno::EINVAL)
323 }
324}
325
326fn deterministic_syslog_result(action: i32, len: usize) -> Result<i64, Errno> {
329 const SYSLOG_ACTION_CONSOLE_LEVEL: i32 = 8;
330
331 match action {
332 0..=7 | 9..=10 => Ok(0),
333 SYSLOG_ACTION_CONSOLE_LEVEL if (1..=8).contains(&len) => Ok(0),
334 _ => Err(Errno::EINVAL),
335 }
336}
337
338fn write_get_mempolicy_output<M: MemoryAccess>(
345 memory: &mut M,
346 address: AddrMut<'_, u8>,
347 bytes: &[u8],
348) -> Result<(), Error> {
349 match memory.write_with_user_access(address, bytes) {
350 Ok(written) if written == bytes.len() => Ok(()),
351 Ok(_) | Err(Errno::EFAULT) => Err(Errno::EFAULT.into()),
352 Err(errno) => Err(Error::Tool(anyhow::anyhow!(
353 "get_mempolicy output write failed: {errno}"
354 ))),
355 }
356}
357
358fn from_str(s: &str) -> [libc::c_char; 65] {
359 let mut ret: [libc::c_char; 65] = [0; 65];
360 for (i, ch) in s.bytes().take(64).enumerate() {
361 ret[i] = ch as libc::c_char;
362 }
363 ret
364}
365
366const RANDOM_DEVICE_BYTE_STRIDE: u8 = 73;
367const RANDOM_DEVICE_FIRST_BYTE: u8 = 41;
368
369fn canonical_random_device_byte(seed: u64, index: u64) -> u8 {
372 let seed_byte = seed.rotate_right(((index % 8) * 8) as u32) as u8;
373 (index as u8)
374 .wrapping_mul(RANDOM_DEVICE_BYTE_STRIDE)
375 .wrapping_add(RANDOM_DEVICE_FIRST_BYTE)
376 ^ seed_byte
377}
378
379fn random_device_stream_seed(run_seed: u64, stream: RandomDeviceStream) -> u64 {
384 [stream.creator.as_raw() as u32 as u64, stream.sequence]
385 .into_iter()
386 .fold(run_seed, |seed, word| splitmix64(seed ^ splitmix64(word)))
387}
388
389fn fill_canonical_random_iovecs(
392 memory: &mut impl MemoryAccess,
393 iovecs: &[crate::iovecs::ImportedIovec],
394 seed: u64,
395 stream_offset: u64,
396 hasher: &mut DefaultHasher,
397) -> Result<usize, Error> {
398 let mut local_words = [0_u64; RANDOM_FILL_CHUNK_BYTES / std::mem::size_of::<u64>()];
399 let mut written = 0_usize;
400 for iov in iovecs {
401 let mut segment_written = 0;
402 while segment_written < iov.len {
403 let remote_chunk = match iov
404 .base
405 .checked_add(segment_written)
406 .and_then(AddrMut::<u8>::from_raw)
407 {
408 Some(address) => address,
409 None if written == 0 => return Err(Errno::EFAULT.into()),
410 None => return Ok(written),
411 };
412 let chunk_len = (iov.len - segment_written).min(RANDOM_FILL_CHUNK_BYTES);
413 let local_buf = unsafe {
414 std::slice::from_raw_parts_mut(local_words.as_mut_ptr().cast::<u8>(), chunk_len)
415 };
416 for (index, byte) in local_buf.iter_mut().enumerate() {
417 *byte = canonical_random_device_byte(
418 seed,
419 stream_offset
420 .saturating_add(written as u64)
421 .saturating_add(index as u64),
422 );
423 }
424 let n = match write_random_chunk(memory, remote_chunk, local_buf) {
425 Ok(n) => n,
426 Err(Errno::EFAULT) if written > 0 => return Ok(written),
427 Err(error) => return Err(crate::random::copy_error(error)),
428 };
429 if n == 0 && written == 0 {
430 return Err(Errno::EFAULT.into());
431 }
432 if cfg!(debug_assertions) {
433 Hash::hash_slice(&local_buf[..n], hasher);
434 }
435 written += n;
436 segment_written += n;
437 if n < chunk_len {
438 return Ok(written);
439 }
440 }
441 }
442 Ok(written)
443}
444
445impl<T: RecordOrReplay> Detcore<T> {
446 pub async fn handle_seccomp<G: Guest<Self>>(
449 &self,
450 _guest: &mut G,
451 call: syscalls::Seccomp,
452 ) -> Result<i64, Error> {
453 seccomp_result(call.op(), call.flags(), call.args().is_some()).map_err(Into::into)
454 }
455
456 #[cfg(target_arch = "x86_64")]
458 fn v3_host_xstate(&self) -> Option<&detcore_model::cpu_profile::HostXstate> {
459 (self.cfg.cpu_profile == detcore_model::cpu_profile::CpuProfile::X86_64V3)
460 .then_some(self.cfg.cpu_profile_host.as_ref())
461 .flatten()
462 }
463
464 #[cfg(target_arch = "x86_64")]
465 fn write_arch_prctl_u64<G: Guest<Self>>(
466 &self,
467 guest: &mut G,
468 raw_addr: usize,
469 value: u64,
470 ) -> Result<i64, Error> {
471 let addr = AddrMut::<u64>::from_raw(raw_addr).ok_or(Errno::EFAULT)?;
472 guest.memory().write_value(addr, &value)?;
473 Ok(0)
474 }
475
476 #[cfg(target_arch = "x86_64")]
480 pub async fn handle_arch_prctl<G: Guest<Self>>(
481 &self,
482 guest: &mut G,
483 call: syscalls::ArchPrctl,
484 ) -> Result<i64, Error> {
485 let cpuid_uses_faulting = self.cfg.virtualize_cpuid && guest.has_cpuid_interception();
486 match call.cmd() {
487 ArchPrctlCmd::ARCH_SET_FS(_)
488 | ArchPrctlCmd::ARCH_SET_GS(_)
489 | ArchPrctlCmd::ARCH_GET_FS(_)
490 | ArchPrctlCmd::ARCH_GET_GS(_) => Ok(guest.inject(call).await?),
491
492 ArchPrctlCmd::ARCH_GET_CPUID(_) if cpuid_uses_faulting => Ok(0),
495 ArchPrctlCmd::ARCH_SET_CPUID(value) if cpuid_uses_faulting => {
496 if value == 0 {
497 Ok(0)
498 } else {
499 Err(Errno::EPERM.into())
500 }
501 }
502 ArchPrctlCmd::ARCH_GET_CPUID(_) if !self.cfg.virtualize_cpuid => Ok(1),
505 ArchPrctlCmd::ARCH_SET_CPUID(value) if !self.cfg.virtualize_cpuid => {
506 if value == 0 {
507 Err(Errno::EPERM.into())
508 } else {
509 Ok(0)
510 }
511 }
512
513 ArchPrctlCmd::ARCH_GET_CPUID(_) | ArchPrctlCmd::ARCH_SET_CPUID(_) => {
515 Ok(guest.inject(call).await?)
516 }
517
518 ArchPrctlCmd::Other(
525 ARCH_GET_XCOMP_SUPP
526 | ARCH_GET_XCOMP_PERM
527 | ARCH_GET_XCOMP_GUEST_PERM
528 | ARCH_REQ_XCOMP_PERM
529 | ARCH_REQ_XCOMP_GUEST_PERM,
530 _,
531 ) if self.cfg.cpu_profile == detcore_model::cpu_profile::CpuProfile::HostNoRdrand => {
532 Ok(guest.inject(call).await?)
533 }
534
535 ArchPrctlCmd::Other(
542 cmd @ (ARCH_GET_XCOMP_SUPP | ARCH_GET_XCOMP_PERM | ARCH_GET_XCOMP_GUEST_PERM),
543 addr,
544 ) if let Some(host) = self.v3_host_xstate() => {
545 let mask = match cmd {
546 ARCH_GET_XCOMP_SUPP => host.xcomp_supp,
547 ARCH_GET_XCOMP_PERM => host.xcomp_perm,
548 _ => host.xcomp_guest_perm,
549 };
550 self.write_arch_prctl_u64(guest, addr, mask)
551 }
552 ArchPrctlCmd::Other(ARCH_REQ_XCOMP_PERM | ARCH_REQ_XCOMP_GUEST_PERM, index)
553 if let Some(host) = self.v3_host_xstate() =>
554 {
555 if index >= host.xfeature_max as usize {
556 Err(Errno::EINVAL.into())
557 } else {
558 Err(Errno::EOPNOTSUPP.into())
559 }
560 }
561 ArchPrctlCmd::Other(
563 ARCH_GET_XCOMP_SUPP | ARCH_GET_XCOMP_PERM | ARCH_GET_XCOMP_GUEST_PERM,
564 addr,
565 ) => self.write_arch_prctl_u64(guest, addr, 0),
566 ArchPrctlCmd::Other(ARCH_REQ_XCOMP_PERM | ARCH_REQ_XCOMP_GUEST_PERM, _) => {
567 Err(Errno::EINVAL.into())
568 }
569
570 ArchPrctlCmd::Other(ARCH_SHSTK_STATUS, addr) => {
573 self.write_arch_prctl_u64(guest, addr, 0)
574 }
575 ArchPrctlCmd::Other(ARCH_SHSTK_DISABLE, features)
576 if features != 0 && features & !ARCH_SHSTK_VALID_MASK == 0 =>
577 {
578 Ok(0)
579 }
580 ArchPrctlCmd::Other(ARCH_SHSTK_DISABLE, _)
581 | ArchPrctlCmd::Other(ARCH_SHSTK_ENABLE | ARCH_SHSTK_LOCK | ARCH_SHSTK_UNLOCK, _) => {
582 Err(Errno::EINVAL.into())
583 }
584
585 ArchPrctlCmd::Other(_, _) => Err(Errno::EINVAL.into()),
586 }
587 }
588
589 pub async fn handle_prctl<G: Guest<Self>>(
595 &self,
596 guest: &mut G,
597 call: syscalls::Prctl,
598 ) -> Result<i64, Error> {
599 match call.option() {
600 libc::PR_CAPBSET_READ => Ok(self.record_or_replay(guest, call).await?),
602 libc::PR_SET_TIMERSLACK => {
608 let requested = call.arg2();
609 let state = guest.thread_state_mut();
610 state.timer_slack_ns = if requested == 0 {
614 state.default_timer_slack_ns
615 } else {
616 requested
617 };
618 Ok(0)
619 }
620 libc::PR_GET_TIMERSLACK => Ok(guest.thread_state().timer_slack_ns as i64),
621 libc::PR_GET_PDEATHSIG if holds_parent_death_signal(guest.config()) => {
632 let held = crate::tool_global::parent_death_signal(guest, None).await;
633 if held == 0 {
634 return self.passthrough(guest, call.into()).await;
635 }
636 let target =
637 AddrMut::<libc::c_int>::from_raw(call.arg2() as usize).ok_or(Errno::EFAULT)?;
638 guest
639 .memory()
640 .write_value(target, &held)
641 .map_err(|_| Errno::EFAULT)?;
642 Ok(0)
643 }
644 libc::PR_SET_PDEATHSIG => {
645 let signal = call.arg2();
646 Self::refuse_reserved_signal_arming(
650 guest,
651 crate::refused_signal_controls::ReservedArming::PrctlPdeathsig,
652 i64::try_from(signal).unwrap_or(-1),
653 )?;
654 if signal == libc::SIGALRM as libc::c_ulong {
655 refuse_sigalrm(guest, SigalrmControl::ArmProducer).await?;
656 }
657 let held = holds_parent_death_signal(guest.config());
669 let route = parent_death_signal_route(
670 signal,
671 held,
672 guest.config().in_guest_refuses_host_timed_producers,
673 );
674 if route == ParentDeathSignalRoute::Refuse {
675 tracing::warn!(
676 "{}",
677 crate::host_timed_producers::parent_death_signal_refused_warning(
678 signal as i32,
679 guest.thread_state().dettid.as_raw(),
680 )
681 );
682 return Err(Errno::EPERM.into());
683 }
684 if route == ParentDeathSignalRoute::Hold {
685 let previous = crate::tool_global::parent_death_signal(guest, None).await;
695 crate::tool_global::parent_death_signal(guest, Some(signal as i32)).await;
696 if let Err(errno) = guest
697 .inject_with_retry(syscalls::Syscall::from(call.with_arg2(0)))
698 .await
699 {
700 crate::tool_global::parent_death_signal(guest, Some(previous)).await;
701 return Err(errno.into());
702 }
703 return Ok(0);
704 }
705 if (1..=64).contains(&signal)
706 && guest.config().backend.host_timed_signals_are_physical
707 {
708 record_host_timed_signals(
709 guest,
710 HostTimedSignalScope::Caller,
711 1_u64 << (signal - 1),
712 )
713 .await;
714 }
715 if signal == libc::SIGKILL as libc::c_ulong {
716 crate::reserved_input::expose_global(guest, "kernel-parent-death-sigkill")?;
717 }
718 let result = self.passthrough(guest, call.into()).await?;
719 if held {
720 crate::tool_global::parent_death_signal(guest, Some(0)).await;
721 }
722 Ok(result)
723 }
724 libc::PR_SET_CHILD_SUBREAPER
743 if models_child_subreapers(
744 guest.config().sequentialize_threads,
745 &guest.config().backend,
746 ) =>
747 {
748 let dettid = guest.thread_state().dettid;
749 let Some(process) = registered_process_of(guest, dettid).await else {
750 return Err(Errno::ENOSYS.into());
751 };
752 let result = guest.inject(call).await?;
753 set_child_subreaper(guest, process, call.arg2() != 0).await;
754 Ok(result)
755 }
756 libc::PR_GET_CHILD_SUBREAPER
757 if models_child_subreapers(
758 guest.config().sequentialize_threads,
759 &guest.config().backend,
760 ) =>
761 {
762 Ok(guest.inject(call).await?)
763 }
764 libc::PR_SET_NAME => {
774 let result = self.passthrough(guest, call.into()).await?;
775 let dettid = guest.thread_state().dettid;
776 match prctl_task_name(&guest.memory(), call.arg2()) {
777 Some(comm) => crate::tool_global::set_task_comm(guest, dettid, comm).await,
778 None => tracing::warn!(
779 "[detcore, dtid {dettid}] PR_SET_NAME succeeded but its name could not \
780 be read again; the previous recorded name stands"
781 ),
782 }
783 Ok(result)
784 }
785 option if is_supported_prctl_option(option) => {
786 self.passthrough(guest, call.into()).await
787 }
788 _ => Err(Errno::ENOSYS.into()),
789 }
790 }
791
792 pub async fn handle_getpriority<G: Guest<Self>>(
805 &self,
806 _guest: &mut G,
807 call: syscalls::Getpriority,
808 ) -> Result<i64, Error> {
809 Ok(getpriority_result(call.which())?)
810 }
811
812 pub async fn handle_syslog<G: Guest<Self>>(
817 &self,
818 _guest: &mut G,
819 call: syscalls::Syslog,
820 ) -> Result<i64, Error> {
821 Ok(deterministic_syslog_result(call.priority(), call.len())?)
822 }
823
824 pub async fn handle_setpriority<G: Guest<Self>>(
835 &self,
836 _guest: &mut G,
837 call: syscalls::Setpriority,
838 ) -> Result<i64, Error> {
839 Ok(setpriority_result(call.which())?)
840 }
841
842 pub fn handle_process_madvise(pidfd: usize, flags: usize) -> Result<i64, Error> {
846 if flags != 0 {
847 return Err(Errno::EINVAL.into());
848 }
849
850 if (pidfd as libc::c_int) < 0 {
853 Err(Errno::EBADF.into())
854 } else {
855 Err(Errno::EPERM.into())
856 }
857 }
858
859 pub(super) fn fill_random_device_bytes<G: Guest<Self>>(
864 &self,
865 guest: &mut G,
866 stream: RandomDeviceStream,
867 remote_buf: AddrMut<u8>,
868 len: usize,
869 stream_offset: u64,
870 ) -> Result<usize, Error> {
871 self.fill_random_device_iovecs(
872 guest,
873 stream,
874 &[crate::iovecs::ImportedIovec {
875 base: remote_buf.as_raw(),
876 len,
877 }],
878 stream_offset,
879 )
880 }
881
882 pub(super) fn fill_random_device_iovecs<G: Guest<Self>>(
883 &self,
884 guest: &mut G,
885 stream: RandomDeviceStream,
886 iovecs: &[crate::iovecs::ImportedIovec],
887 stream_offset: u64,
888 ) -> Result<usize, Error> {
889 let seed = random_device_stream_seed(guest.config().rng_seed(), stream);
890 let mut hasher = DefaultHasher::new();
891 let written = fill_canonical_random_iovecs(
892 &mut guest.memory(),
893 iovecs,
894 seed,
895 stream_offset,
896 &mut hasher,
897 )?;
898 if cfg!(debug_assertions) {
899 detlog!(
900 "[dtid {}] USER RAND [/dev/[u]random] Filled guest memory with {} canonical random bytes of {:?} at offset {}, hash of bytes: {}",
901 guest.thread_state().dettid,
902 written,
903 stream,
904 stream_offset,
905 hasher.finish()
906 );
907 }
908 Ok(written)
909 }
910
911 pub async fn handle_uname<G: Guest<Self>>(
913 &self,
914 guest: &mut G,
915 call: syscalls::Uname,
916 ) -> Result<i64, Error> {
917 let ret = self.record_or_replay(guest, call).await?;
918 if let Some(buf) = call.buf() {
919 let mut un = guest.memory().read_value(buf)?;
920 let kernel = crate::machine::MachineDescription::of(guest.config()).kernel;
921
922 if !guest.config().has_uts_namespace {
923 un.nodename = from_str(DEFAULT_HOSTNAME);
926 un.domainname = from_str(DEFAULT_HOSTNAME.split('.').next_back().unwrap_or(""));
927 }
928
929 un.sysname = from_str(&kernel.sysname);
931 un.release = from_str(&kernel.release);
932 un.version = from_str(&kernel.version());
933 guest.memory().write_value(buf, &un)?;
934 }
935
936 Ok(ret)
937 }
938
939 pub async fn handle_getrandom<G: Guest<Self>>(
943 &self,
944 guest: &mut G,
945 call: syscalls::Getrandom,
946 ) -> Result<i64, Error> {
947 let memory = guest.memory();
948 let dettid = guest.thread_state().dettid;
949 crate::random::getrandom(guest.thread_state_mut().thread_prng(), memory, dettid, call)
950 }
951
952 pub async fn handle_setsid<G: Guest<Self>>(
964 &self,
965 guest: &mut G,
966 call: syscalls::Setsid,
967 ) -> Result<i64, Error> {
968 let process = guest.thread_state().detpid.expect("detpid unset");
969 let res = if guest.thread_state().nested_pid_namespace {
970 let res = guest.inject(call).await?;
971 let _ = create_session(guest, process).await;
972 res
973 } else {
974 crate::tool_global::setsid_decision(guest, process).await?;
975 self.mirror_group_change(guest, reverie::syscalls::Syscall::Setsid(call))
976 .await?;
977 let _ = create_session(guest, process).await;
978 i64::from(process.as_raw())
979 };
980
981 if guest.config().kill_daemons {
984 guest.daemonize().await;
985 }
986 Ok(res)
987 }
988
989 pub async fn handle_group_or_session_getter<G: Guest<Self>>(
1003 &self,
1004 guest: &mut G,
1005 call: reverie::syscalls::Syscall,
1006 ) -> Result<i64, Error> {
1007 if guest.thread_state().nested_pid_namespace {
1008 return Ok(guest.inject(call).await?);
1009 }
1010 let (sysno, args) = reverie::syscalls::SyscallInfo::into_parts(call);
1011 #[cfg(target_arch = "x86_64")]
1012 let pid = if sysno == Sysno::getpgrp {
1013 0
1014 } else {
1015 args.arg0 as libc::pid_t
1016 };
1017 #[cfg(not(target_arch = "x86_64"))]
1018 let pid = {
1019 let _ = sysno;
1020 args.arg0 as libc::pid_t
1021 };
1022 let task = match pid {
1023 0 => guest.thread_state().detpid.expect("detpid unset"),
1024 pid if pid < 0 => return Err(Errno::ESRCH.into()),
1025 pid => DetPid::from_raw(pid),
1026 };
1027 let answer =
1028 crate::tool_global::group_or_session_of(guest, task, sysno == Sysno::getsid).await;
1029 Ok(i64::from(answer.ok_or(Errno::ESRCH)?.as_raw()))
1030 }
1031
1032 pub async fn handle_setpgid<G: Guest<Self>>(
1039 &self,
1040 guest: &mut G,
1041 call: syscalls::Setpgid,
1042 ) -> Result<i64, Error> {
1043 let caller = guest.thread_state().detpid.expect("detpid unset");
1044 if !guest.thread_state().nested_pid_namespace {
1045 let (process, group) =
1046 crate::tool_global::setpgid_decision(guest, caller, call.pid(), call.pgid())
1047 .await?;
1048 self.mirror_group_change(guest, reverie::syscalls::Syscall::Setpgid(call))
1049 .await?;
1050 let _ = set_process_group(guest, process, group).await;
1051 return Ok(0);
1052 }
1053 let res = guest.inject(call).await?;
1054 let process = if call.pid() == 0 {
1058 caller
1059 } else {
1060 self.task_named_after_success(guest, Sysno::setpgid, call.pid())
1061 .await?
1062 };
1063 let group = if call.pgid() == 0 {
1064 process
1065 } else {
1066 self.task_named_after_success(guest, Sysno::setpgid, call.pgid())
1067 .await?
1068 };
1069 let _ = set_process_group(guest, process, group).await;
1070 Ok(res)
1071 }
1072
1073 async fn mirror_group_change<G: Guest<Self>>(
1082 &self,
1083 guest: &mut G,
1084 call: reverie::syscalls::Syscall,
1085 ) -> Result<(), Error> {
1086 let result = guest.inject(call).await;
1087 let refusal = result
1090 .is_err()
1091 .then(|| guest.take_unsupported_refusal())
1092 .flatten();
1093 match result {
1094 Ok(_) => Ok(()),
1095 Err(_)
1096 if refusal.is_some_and(|refusal| {
1097 refusal.operation == reverie::UnsupportedOperation::ProcessGroups
1098 }) =>
1099 {
1100 Ok(())
1101 }
1102 Err(errno) => {
1103 let sysno = reverie::syscalls::SyscallInfo::number(&call);
1104 tracing::error!(
1105 "[detcore, dtid {}] {} was accepted by Detcore's process-group model but the backend answered {}: its process groups disagree with the model's (slice B3a-4c of https://github.com/rrnewton/hermit/issues/4266)",
1106 guest.thread_state().dettid,
1107 sysno,
1108 errno,
1109 );
1110 self.refuse_unserviceable_operation(guest, sysno, errno)
1111 .await
1112 .map(|_| ())
1113 }
1114 }
1115 }
1116
1117 async fn task_named_after_success<G: Guest<Self>>(
1122 &self,
1123 guest: &mut G,
1124 sysno: Sysno,
1125 id: libc::pid_t,
1126 ) -> Result<DetPid, Error> {
1127 if !guest.thread_state().nested_pid_namespace {
1128 return Ok(DetPid::from_raw(id));
1129 }
1130 let reason = match crate::tool_global::guest_pid(guest, id).await {
1131 crate::scheduler::pid_namespaces::GuestPidAnswer::Task { task, .. } => return Ok(task),
1132 crate::scheduler::pid_namespaces::GuestPidAnswer::NoSuchPid => {
1133 format!(
1134 "the kernel accepted id {id}, which no recorded task of the caller's namespace has"
1135 )
1136 }
1137 crate::scheduler::pid_namespaces::GuestPidAnswer::Untranslatable(reason) => reason,
1138 };
1139 tracing::error!(
1140 "[detcore, dtid {}] {} from a nested PID namespace is not supported: {} (https://github.com/rrnewton/hermit/issues/4155)",
1141 guest.thread_state().dettid,
1142 sysno,
1143 reason,
1144 );
1145 match self
1146 .refuse_unserviceable_operation(guest, sysno, Errno::ENOSYS)
1147 .await
1148 {
1149 Err(error) => Err(error),
1150 Ok(_) => Err(Errno::ENOSYS.into()),
1151 }
1152 }
1153
1154 pub async fn handle_membarrier<G: Guest<Self>>(
1165 &self,
1166 guest: &mut G,
1167 call: syscalls::Membarrier,
1168 ) -> Result<i64, Error> {
1169 const MEMBARRIER_CMD_QUERY: i32 = 0;
1171 const MEMBARRIER_CMD_GLOBAL: i32 = 1 << 0;
1172 const MEMBARRIER_CMD_GLOBAL_EXPEDITED: i32 = 1 << 1;
1173 const MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED: i32 = 1 << 2;
1174 const MEMBARRIER_CMD_PRIVATE_EXPEDITED: i32 = 1 << 3;
1175 const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED: i32 = 1 << 4;
1176 const SUPPORTED: i32 = MEMBARRIER_CMD_GLOBAL
1177 | MEMBARRIER_CMD_GLOBAL_EXPEDITED
1178 | MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED
1179 | MEMBARRIER_CMD_PRIVATE_EXPEDITED
1180 | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED;
1181
1182 let cmd = call.cmd();
1183 if cmd == MEMBARRIER_CMD_QUERY {
1184 detlog!(
1185 "[dtid {}] membarrier(QUERY) => reporting emulated commands {:#x}",
1186 guest.thread_state().dettid,
1187 SUPPORTED,
1188 );
1189 Ok(SUPPORTED as i64)
1190 } else {
1191 detlog!(
1192 "[dtid {}] membarrier(cmd={}) no-op (threads are serialized on one CPU)",
1193 guest.thread_state().dettid,
1194 cmd,
1195 );
1196 Ok(0)
1197 }
1198 }
1199
1200 pub async fn handle_getcpu<G: Guest<Self>>(
1205 &self,
1206 guest: &mut G,
1207 call: syscalls::Getcpu,
1208 ) -> Result<i64, Error> {
1209 let current = match self.cfg.virtual_cpus {
1210 Some(cpus) => self.virtual_cpu(guest, cpus.get()).await,
1211 None => 0,
1212 };
1213 if let Some(cpu) = call.cpu() {
1214 guest.memory().write_value(cpu, ¤t)?;
1215 }
1216
1217 if let Some(node) = call.node() {
1219 guest.memory().write_value(node, &0)?;
1220 }
1221
1222 Ok(0)
1223 }
1224
1225 pub async fn handle_getresuid<G: Guest<Self>>(
1235 &self,
1236 guest: &mut G,
1237 call: syscalls::Getresuid,
1238 ) -> Result<i64, Error> {
1239 if let Some(ruid) = call.ruid() {
1240 guest.memory().write_value(ruid, &0)?;
1241 }
1242 if let Some(euid) = call.euid() {
1243 guest.memory().write_value(euid, &0)?;
1244 }
1245 if let Some(suid) = call.suid() {
1246 guest.memory().write_value(suid, &0)?;
1247 }
1248 Ok(0)
1249 }
1250
1251 pub async fn handle_getresgid<G: Guest<Self>>(
1258 &self,
1259 guest: &mut G,
1260 call: syscalls::Getresgid,
1261 ) -> Result<i64, Error> {
1262 if let Some(rgid) = call.rgid() {
1263 guest.memory().write_value(rgid, &0)?;
1264 }
1265 if let Some(egid) = call.egid() {
1266 guest.memory().write_value(egid, &0)?;
1267 }
1268 if let Some(sgid) = call.sgid() {
1269 guest.memory().write_value(sgid, &0)?;
1270 }
1271 Ok(0)
1272 }
1273
1274 pub async fn handle_get_mempolicy<G: Guest<Self>>(
1308 &self,
1309 guest: &mut G,
1310 call: syscalls::GetMempolicy,
1311 ) -> Result<i64, Error> {
1312 const MPOL_F_NODE: u64 = 1;
1313 const MPOL_F_ADDR: u64 = 2;
1314 const MPOL_F_MEMS_ALLOWED: u64 = 4;
1315 let page = crate::page::page_size();
1317 let mask = call.nodemask();
1318 if mask.is_some() && call.maxnode() < 1 {
1319 return Err(Errno::EINVAL.into());
1320 }
1321 let flags = crate::arch::syscall_arguments(&guest.regs().await)[4];
1323 if flags & !(MPOL_F_NODE | MPOL_F_ADDR | MPOL_F_MEMS_ALLOWED) != 0 {
1324 return Err(Errno::EINVAL.into());
1325 }
1326 let mems_allowed = flags & MPOL_F_MEMS_ALLOWED != 0;
1327 if mems_allowed {
1328 if flags & (MPOL_F_NODE | MPOL_F_ADDR) != 0 {
1329 return Err(Errno::EINVAL.into());
1330 }
1331 } else if flags & MPOL_F_ADDR == 0 && call.addr().is_some() {
1332 return Err(Errno::EINVAL.into());
1333 }
1334 if let Some(policy) = call.policy() {
1335 let policy = AddrMut::<u8>::from_raw(policy.as_raw()).ok_or(Errno::EFAULT)?;
1336 write_get_mempolicy_output(&mut guest.memory(), policy, &0i32.to_ne_bytes())?;
1337 }
1338 if let Some(mask) = mask {
1339 let bytes = (call.maxnode() - 1).div_ceil(64) * 8;
1340 if bytes > page.max(8) {
1341 return Err(Errno::EINVAL.into());
1342 }
1343 let mut words = vec![0u8; bytes as usize];
1344 if mems_allowed && !words.is_empty() {
1345 words[0] = 1;
1346 }
1347 if !words.is_empty() {
1348 let mask = AddrMut::<u8>::from_raw(mask.as_raw()).ok_or(Errno::EFAULT)?;
1349 write_get_mempolicy_output(&mut guest.memory(), mask, &words)?;
1350 }
1351 }
1352 Ok(0)
1353 }
1354
1355 pub async fn handle_move_pages<G: Guest<Self>>(
1362 &self,
1363 guest: &mut G,
1364 call: syscalls::MovePages,
1365 ) -> Result<i64, Error> {
1366 if let Some(status) = call.status() {
1369 let count = call.nr_pages() as usize;
1370 let zeros = vec![0i32; count];
1371 guest.memory().write_values(status, &zeros)?;
1372 }
1373 Ok(0)
1374 }
1375}
1376
1377#[cfg(test)]
1378mod tests {
1379 #[test]
1383 fn prctl_set_name_reads_exactly_up_to_15_bytes_and_the_first_nul() {
1384 let memory = reverie::syscalls::LocalMemory::new();
1385 let name = |address: usize| {
1386 super::prctl_task_name(&memory, address as libc::c_ulong)
1387 .map(|comm| comm.as_bytes().to_vec())
1388 };
1389 let text = |bytes: &[u8]| name(bytes.as_ptr() as usize);
1390 assert_eq!(text(b"worker\0junk"), Some(b"worker".to_vec()));
1391 assert_eq!(
1392 text(b"0123456789abcdefghij\0"),
1393 Some(b"0123456789abcde".to_vec())
1394 );
1395 assert_eq!(text(b"\0"), Some(Vec::new()));
1396 assert_eq!(text(b"\xff\n\\L\0"), Some(b"\xff\n\\L".to_vec()));
1397 const PAGE: usize = 4096;
1401 let base = unsafe {
1403 libc::mmap(
1404 std::ptr::null_mut(),
1405 2 * PAGE,
1406 libc::PROT_READ | libc::PROT_WRITE,
1407 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
1408 -1,
1409 0,
1410 )
1411 };
1412 assert_ne!(base, libc::MAP_FAILED);
1413 let base = base as usize;
1414 unsafe {
1417 *((base + PAGE - 2) as *mut u8) = b'x';
1418 *((base + PAGE - 1) as *mut u8) = 0;
1419 assert_eq!(
1420 libc::mprotect((base + PAGE) as *mut _, PAGE, libc::PROT_NONE),
1421 0
1422 );
1423 }
1424 assert_eq!(name(base + PAGE - 2), Some(b"x".to_vec()));
1425 unsafe { *((base + PAGE - 1) as *mut u8) = b'y' };
1427 assert_eq!(name(base + PAGE - 2), None);
1428 assert_eq!(unsafe { libc::munmap(base as *mut _, 2 * PAGE) }, 0);
1430 }
1431
1432 use super::*;
1433
1434 #[test]
1441 fn parent_death_signals_are_held_refused_or_the_kernels() {
1442 use ParentDeathSignalRoute::*;
1443 for signal in 0..=66_u64 {
1444 let held = signal == 0 || detcore_sends_parent_death_signal(signal);
1445 let in_guest = parent_death_signal_route(signal, true, true);
1446 let ptrace = parent_death_signal_route(signal, true, false);
1447 let other = parent_death_signal_route(signal, false, false);
1448 assert_eq!(other, Kernel, "{signal}");
1449 assert_eq!(ptrace, if held { Hold } else { Kernel }, "{signal}");
1450 let expected = if held {
1451 Hold
1452 } else if signal == libc::SIGKILL as u64 || signal > 64 {
1453 Kernel
1454 } else {
1455 Refuse
1456 };
1457 assert_eq!(in_guest, expected, "{signal}");
1458 }
1459 for (signal, route) in [
1461 (libc::SIGUSR1, Hold),
1462 (libc::SIGUSR2, Hold),
1463 (0, Hold),
1464 (libc::SIGKILL, Kernel),
1465 (libc::SIGSTOP, Refuse),
1466 (libc::SIGALRM, Refuse),
1467 (32, Refuse),
1468 (64, Refuse),
1469 (65, Kernel),
1470 ] {
1471 assert_eq!(
1472 parent_death_signal_route(signal as u64, true, true),
1473 route,
1474 "{signal}"
1475 );
1476 }
1477 }
1478
1479 #[test]
1483 fn holding_a_parent_death_signal_is_split_from_delivering_it() {
1484 let ptrace = crate::config::Config {
1485 sequentialize_threads: true,
1486 backend: BackendCapabilities::PTRACE,
1487 ..Default::default()
1488 };
1489 assert!(delivers_parent_death_signal(&ptrace));
1490 assert!(holds_parent_death_signal(&ptrace));
1491 let unsequentialized = crate::config::Config {
1492 sequentialize_threads: false,
1493 ..ptrace.clone()
1494 };
1495 assert!(!delivers_parent_death_signal(&unsequentialized));
1496 assert!(!holds_parent_death_signal(&unsequentialized));
1497 let in_guest = crate::config::Config {
1498 sequentialize_threads: true,
1499 backend: BackendCapabilities::LITEINST_IN_GUEST,
1500 in_guest_refuses_host_timed_producers: true,
1501 ..Default::default()
1502 };
1503 assert!(!delivers_parent_death_signal(&in_guest));
1504 assert!(holds_parent_death_signal(&in_guest));
1505 let off = crate::config::Config {
1506 in_guest_refuses_host_timed_producers: false,
1507 ..in_guest
1508 };
1509 assert!(!holds_parent_death_signal(&off));
1510 }
1511
1512 mod group_mirror {
1513 use std::sync::Mutex;
1514
1515 use reverie::GlobalRPC;
1516 use reverie::GlobalTool;
1517 use reverie::Tid;
1518 use reverie::Tool;
1519 use reverie::syscalls::Addr;
1520 use reverie::syscalls::AddrMut;
1521 use reverie::syscalls::SyscallInfo;
1522
1523 use super::*;
1524 use crate::config::Config;
1525 use crate::tool_global::GlobalState;
1526
1527 struct MirrorGuest<'a> {
1530 config: &'a Config,
1531 global: &'a GlobalState,
1532 thread: crate::ThreadState<()>,
1533 sender: Tid,
1534 answer: Result<i64, Errno>,
1535 refusal: Option<reverie::UnsupportedOperation>,
1536 recorded: Option<reverie::UnsupportedRefusal>,
1537 injections: Mutex<usize>,
1538 expected: Sysno,
1540 }
1541
1542 struct NoStack;
1543 struct NoStackGuard;
1544 impl Drop for NoStackGuard {
1545 fn drop(&mut self) {}
1546 }
1547 impl reverie::Stack for NoStack {
1548 type StackGuard = NoStackGuard;
1549 fn size(&self) -> usize {
1550 panic!("a mirror must not use the guest stack")
1551 }
1552 fn capacity(&self) -> usize {
1553 panic!("a mirror must not use the guest stack")
1554 }
1555 fn push<'stack, T>(&mut self, _: T) -> Addr<'stack, T> {
1556 panic!("a mirror must not use the guest stack")
1557 }
1558 fn reserve<'stack, T>(&mut self) -> AddrMut<'stack, T> {
1559 panic!("a mirror must not use the guest stack")
1560 }
1561 fn commit(self) -> Result<Self::StackGuard, Errno> {
1562 panic!("a mirror must not use the guest stack")
1563 }
1564 }
1565
1566 #[reverie::tool]
1567 impl GlobalRPC<GlobalState> for MirrorGuest<'_> {
1568 async fn send_rpc(
1569 &self,
1570 message: <GlobalState as GlobalTool>::Request,
1571 ) -> <GlobalState as GlobalTool>::Response {
1572 self.global.receive_rpc(self.sender, message).await
1573 }
1574 fn config(&self) -> &Config {
1575 self.config
1576 }
1577 }
1578
1579 #[reverie::tool]
1580 impl Guest<Detcore> for MirrorGuest<'_> {
1581 type Memory = reverie::syscalls::LocalMemory;
1582 type Stack = NoStack;
1583
1584 fn tid(&self) -> Tid {
1585 self.sender
1586 }
1587 fn pid(&self) -> Tid {
1588 self.sender
1589 }
1590 fn ppid(&self) -> Option<Tid> {
1591 None
1592 }
1593 fn memory(&self) -> Self::Memory {
1594 reverie::syscalls::LocalMemory::new()
1595 }
1596 fn thread_state(&self) -> &crate::ThreadState<()> {
1597 &self.thread
1598 }
1599 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
1600 &mut self.thread
1601 }
1602 async fn regs(&mut self) -> libc::user_regs_struct {
1603 panic!("a mirror must not read registers")
1604 }
1605 async fn stack(&mut self) -> Self::Stack {
1606 panic!("a mirror must not use a guest stack")
1607 }
1608 async fn daemonize(&mut self) {
1609 panic!("a mirror must not daemonize")
1610 }
1611 async fn inject<S: SyscallInfo>(&mut self, call: S) -> Result<i64, Errno> {
1612 assert_eq!(call.number(), self.expected);
1613 *self.injections.lock().unwrap() += 1;
1614 if let Err(errno) = self.answer {
1615 self.recorded = self.refusal.map(|operation| {
1616 reverie::UnsupportedRefusal::new(operation, errno, "test backend")
1617 });
1618 }
1619 self.answer
1620 }
1621 fn take_unsupported_refusal(&mut self) -> Option<reverie::UnsupportedRefusal> {
1622 self.recorded.take()
1623 }
1624 async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
1625 panic!("a mirror must not retire the guest")
1626 }
1627 fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
1628 panic!("a mirror must not set a timer")
1629 }
1630 fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
1631 panic!("a mirror must not set a timer")
1632 }
1633 fn read_clock(&mut self) -> Result<u64, Error> {
1634 panic!("a mirror must not read the clock")
1635 }
1636 }
1637
1638 #[tokio::test]
1645 async fn a_failed_mirror_stands_only_with_the_typed_process_group_refusal() {
1646 use reverie::UnsupportedOperation::Flock;
1647 use reverie::UnsupportedOperation::ProcessGroups;
1648 let process = DetPid::from_raw(23);
1649 for (answer, refusal, accepted) in [
1650 (Ok(23), None, true),
1651 (Err(Errno::ENOSYS), Some(ProcessGroups), true),
1652 (Err(Errno::ENOSYS), None, false),
1653 (Err(Errno::EPERM), None, false),
1654 (Err(Errno::ENOSYS), Some(Flock), false),
1655 ] {
1656 let config = Config {
1657 panic_on_unsupported_syscalls: true,
1658 exit_on_unsupported_syscall: true,
1659 shutdown_on_unsupported_syscall: false,
1660 ..Config::default()
1661 };
1662 let global = GlobalState::init_global_state(&config).await;
1663 let tool = Detcore::new(Tid::from_raw(process.as_raw()), &config);
1664 let mut thread = crate::ThreadState::new(process, &config, ());
1665 thread.detpid = Some(process);
1666 let mut guest = MirrorGuest {
1667 config: &config,
1668 global: &global,
1669 thread,
1670 sender: Tid::from_raw(process.as_raw()),
1671 answer,
1672 refusal,
1673 recorded: None,
1674 injections: Mutex::new(0),
1675 expected: Sysno::setsid,
1676 };
1677 let result = tool
1678 .mirror_group_change(
1679 &mut guest,
1680 reverie::syscalls::Syscall::Setsid(reverie::syscalls::Setsid::new()),
1681 )
1682 .await;
1683 match result {
1684 Ok(()) if accepted => {}
1685 Err(Error::Tool(error)) if !accepted => assert_eq!(
1686 error
1687 .downcast_ref::<crate::UnsupportedSyscallError>()
1688 .unwrap()
1689 .0,
1690 Sysno::setsid,
1691 "{answer:?} {refusal:?}"
1692 ),
1693 result => panic!("wrong mirror policy for {answer:?} {refusal:?}: {result:?}"),
1694 }
1695 assert_eq!(*guest.injections.lock().unwrap(), 1);
1696 assert_eq!(guest.recorded, None, "a reported refusal must be taken");
1697 }
1698 }
1699
1700 #[tokio::test]
1709 #[should_panic(expected = "HERMIT_INTERNAL_FAILURE class=tool-decided-injection")]
1710 async fn an_injected_tool_decided_call_ends_the_run() {
1711 let process = DetPid::from_raw(23);
1712 let config = Config::default();
1713 let global = GlobalState::init_global_state(&config).await;
1714 let tool = Detcore::new(Tid::from_raw(process.as_raw()), &config);
1715 let mut thread = crate::ThreadState::new(process, &config, ());
1716 thread.detpid = Some(process);
1717 thread.nested_pid_namespace = true;
1718 let mut guest = MirrorGuest {
1719 config: &config,
1720 global: &global,
1721 thread,
1722 sender: Tid::from_raw(process.as_raw()),
1723 answer: Err(Errno::ENOSYS),
1724 refusal: Some(reverie::UnsupportedOperation::ToolDecided),
1725 recorded: None,
1726 injections: Mutex::new(0),
1727 expected: Sysno::getpgrp,
1728 };
1729 let result = tool
1730 .handle_group_or_session_getter(
1731 &mut guest,
1732 reverie::syscalls::Syscall::Getpgrp(reverie::syscalls::Getpgrp::new()),
1733 )
1734 .await;
1735 assert_eq!(*guest.injections.lock().unwrap(), 1);
1736 assert!(
1737 matches!(result, Err(Error::Errno(Errno::ENOSYS))),
1738 "{result:?}"
1739 );
1740 crate::syscalls::helpers::fail_on_tool_decided_injection::<
1741 crate::record_or_replay::NoopTool,
1742 _,
1743 >(&mut guest, Sysno::getpgrp);
1744 }
1745 }
1746
1747 struct ScatterMemory {
1748 bytes: Vec<u8>,
1749 outcomes: std::collections::VecDeque<Result<usize, Errno>>,
1750 writes: usize,
1751 }
1752
1753 impl ScatterMemory {
1754 fn new(size: usize, outcomes: impl IntoIterator<Item = Result<usize, Errno>>) -> Self {
1755 Self {
1756 bytes: vec![0xa5; size],
1757 outcomes: outcomes.into_iter().collect(),
1758 writes: 0,
1759 }
1760 }
1761 }
1762
1763 impl MemoryAccess for ScatterMemory {
1764 fn read_vectored(
1765 &self,
1766 _: &[std::io::IoSlice],
1767 _: &mut [std::io::IoSliceMut],
1768 ) -> Result<usize, Errno> {
1769 panic!("canonical scatter must not read guest bytes")
1770 }
1771 fn write_vectored(
1772 &mut self,
1773 _: &[std::io::IoSlice],
1774 _: &mut [std::io::IoSliceMut],
1775 ) -> Result<usize, Errno> {
1776 panic!("canonical scatter must use the user-access capability")
1777 }
1778 fn write_with_user_access(
1779 &mut self,
1780 address: AddrMut<u8>,
1781 bytes: &[u8],
1782 ) -> Result<usize, Errno> {
1783 self.writes += 1;
1784 let count = self.outcomes.pop_front().unwrap_or(Ok(bytes.len()))?;
1785 assert!(count <= bytes.len());
1786 let offset = address.as_raw() - 0x1000;
1787 self.bytes[offset..offset + count].copy_from_slice(&bytes[..count]);
1788 Ok(count)
1789 }
1790 }
1791
1792 fn assert_copy_failure(error: Error, expected: Errno) {
1793 let Error::Tool(error) = error else {
1794 panic!("copy failure became a guest errno: {error:?}")
1795 };
1796 assert_eq!(
1797 error
1798 .downcast_ref::<crate::random::RandomCopyFailure>()
1799 .expect("typed copy failure")
1800 .errno(),
1801 expected
1802 );
1803 }
1804
1805 fn scatter(memory: &mut ScatterMemory, lengths: &[usize], offset: u64) -> Result<usize, Error> {
1806 let mut base = 0x1000;
1807 let vectors: Vec<_> = lengths
1808 .iter()
1809 .map(|&len| {
1810 let vector = crate::iovecs::ImportedIovec { base, len };
1811 base += len;
1812 vector
1813 })
1814 .collect();
1815 fill_canonical_random_iovecs(memory, &vectors, 0, offset, &mut DefaultHasher::new())
1816 }
1817
1818 #[test]
1819 fn get_mempolicy_output_faults_are_efault_and_backend_errors_are_tool_errors() {
1820 let address = AddrMut::<u8>::from_raw(0x1000).unwrap();
1821 let write = |outcome| {
1822 write_get_mempolicy_output(&mut ScatterMemory::new(8, [outcome]), address, &[1; 8])
1823 };
1824 assert!(write(Ok(8)).is_ok());
1825 for fault in [Ok(0), Ok(3), Err(Errno::EFAULT)] {
1826 assert!(
1827 matches!(write(fault), Err(Error::Errno(Errno::EFAULT))),
1828 "{fault:?}"
1829 );
1830 }
1831 for errno in [Errno::ENOSYS, Errno::ESRCH, Errno::EIO] {
1832 assert!(
1833 matches!(write(Err(errno)), Err(Error::Tool(_))),
1834 "{errno:?}"
1835 );
1836 }
1837 }
1838
1839 #[test]
1840 fn random_scatter_distinguishes_fault_prefixes_from_backend_errors() {
1841 for lengths in [vec![3, 5], vec![RANDOM_FILL_CHUNK_BYTES + 5]] {
1843 let prefix = if lengths.len() == 2 {
1844 3
1845 } else {
1846 RANDOM_FILL_CHUNK_BYTES
1847 };
1848 for error in [Errno::EFAULT, Errno::EIO, Errno::ENOMEM] {
1849 let mut memory = ScatterMemory::new(prefix + 5, [Ok(prefix), Err(error)]);
1850 let result = scatter(&mut memory, &lengths, 7);
1851 match error {
1852 Errno::EFAULT => assert_eq!(result.unwrap(), prefix),
1853 _ => assert_copy_failure(result.unwrap_err(), error),
1854 }
1855 let expected: Vec<_> = (7..7 + prefix as u64)
1856 .map(|index| canonical_random_device_byte(0, index))
1857 .collect();
1858 assert_eq!(&memory.bytes[..prefix], expected);
1859 assert_eq!(&memory.bytes[prefix..], &[0xa5; 5]);
1860 assert_eq!(memory.writes, 2);
1861 }
1862 }
1863 }
1864
1865 #[test]
1866 fn random_scatter_short_or_zero_copy_stops_without_touching_later_segments() {
1867 for first in [0, 2] {
1868 let mut memory = ScatterMemory::new(10, [Ok(first)]);
1869 let result = scatter(&mut memory, &[5, 5], 0);
1870 if first == 0 {
1871 assert!(matches!(result, Err(Error::Errno(Errno::EFAULT))));
1872 } else {
1873 assert_eq!(result.unwrap(), first);
1874 assert_eq!(&memory.bytes[..2], &[41, 114]);
1875 }
1876 assert!(memory.bytes[first..].iter().all(|&byte| byte == 0xa5));
1877 assert_eq!(memory.writes, 1);
1878 }
1879 let mut memory = ScatterMemory::new(10, [Ok(5), Ok(0)]);
1880 assert_eq!(scatter(&mut memory, &[5, 5], 0).unwrap(), 5);
1881 assert!(memory.bytes[5..].iter().all(|&byte| byte == 0xa5));
1882 assert_eq!(memory.writes, 2);
1883 }
1884
1885 #[test]
1886 fn random_scatter_saturated_bytes_match_partitioned_calls() {
1887 for (offset, expected) in [(u64::MAX - 2, [78, 151, 224, 224]), (u64::MAX, [224; 4])] {
1888 let mut single = ScatterMemory::new(4, []);
1889 assert_eq!(scatter(&mut single, &[4], offset).unwrap(), 4);
1890 let mut partitioned = ScatterMemory::new(4, []);
1891 let mut hash = DefaultHasher::new();
1892 assert_eq!(
1893 fill_canonical_random_iovecs(
1894 &mut partitioned,
1895 &[crate::iovecs::ImportedIovec {
1896 base: 0x1000,
1897 len: 1
1898 }],
1899 0,
1900 offset,
1901 &mut hash
1902 )
1903 .unwrap(),
1904 1
1905 );
1906 assert_eq!(
1907 fill_canonical_random_iovecs(
1908 &mut partitioned,
1909 &[crate::iovecs::ImportedIovec {
1910 base: 0x1001,
1911 len: 3
1912 }],
1913 0,
1914 offset.saturating_add(1),
1915 &mut hash
1916 )
1917 .unwrap(),
1918 3
1919 );
1920 assert_eq!(single.bytes, expected);
1921 assert_eq!(partitioned.bytes, expected);
1922 }
1923 }
1924
1925 #[test]
1926 fn random_scatter_backend_error_rolls_back_shared_cursor_after_eight_byte_prefix() {
1927 let fd = crate::fd::DetFd::new(
1928 3,
1929 nix::fcntl::OFlag::O_RDONLY,
1930 crate::fd::FdType::Rng,
1931 crate::types::OpenFileId::new(crate::types::DetTid::from_raw(1), 0),
1932 );
1933 for error in [Errno::EFAULT, Errno::EIO] {
1934 let mut memory = ScatterMemory::new(8, [Ok(4), Err(error)]);
1935 let result = fd.with_random_device_stream(|offset| scatter(&mut memory, &[8], offset));
1936 if error == Errno::EFAULT {
1937 assert_eq!(result.unwrap(), 4);
1938 } else {
1939 assert_copy_failure(result.unwrap_err(), Errno::EIO);
1940 }
1941 assert_eq!(fd.random_device_offset(), 4);
1944 assert_eq!(&memory.bytes[4..], &[0xa5; 4]);
1945 assert_eq!(memory.writes, 2);
1946 }
1947 }
1948
1949 #[test]
1950 fn prctl_support_covers_deterministic_controls() {
1951 for option in [
1952 libc::PR_SET_NAME,
1953 libc::PR_GET_NAME,
1954 libc::PR_SET_THP_DISABLE,
1955 libc::PR_GET_THP_DISABLE,
1956 libc::PR_SET_DUMPABLE,
1958 libc::PR_GET_DUMPABLE,
1959 libc::PR_SET_KEEPCAPS,
1961 libc::PR_GET_KEEPCAPS,
1962 libc::PR_SET_PDEATHSIG,
1964 libc::PR_GET_PDEATHSIG,
1965 ] {
1966 assert!(is_supported_prctl_option(option));
1967 }
1968
1969 assert!(!is_supported_prctl_option(libc::PR_SET_NO_NEW_PRIVS));
1970 assert!(!is_supported_prctl_option(libc::PR_SET_SECCOMP));
1973 assert!(!is_supported_prctl_option(libc::PR_SET_TIMERSLACK));
1974 assert!(!is_supported_prctl_option(libc::PR_GET_TIMERSLACK));
1975 }
1976
1977 #[test]
1984 fn child_subreaper_prctls_are_admitted_only_where_detcore_models_them() {
1985 for backend in [BackendCapabilities::PTRACE, BackendCapabilities::E9PATCH] {
1986 assert!(models_child_subreapers(true, &backend), "{backend:?}");
1987 assert!(!models_child_subreapers(false, &backend), "{backend:?}");
1988 }
1989 for backend in [
1990 BackendCapabilities::LITEINST_IN_GUEST,
1991 BackendCapabilities::SABRE,
1992 BackendCapabilities::DBT,
1993 BackendCapabilities::KVM,
1994 ] {
1995 assert!(!models_child_subreapers(true, &backend), "{backend:?}");
1996 assert!(!models_child_subreapers(false, &backend), "{backend:?}");
1997 }
1998 assert!(!is_supported_prctl_option(libc::PR_SET_CHILD_SUBREAPER));
1999 assert!(!is_supported_prctl_option(libc::PR_GET_CHILD_SUBREAPER));
2000 }
2001
2002 #[test]
2003 fn capability_set_prctls_are_supported_on_every_backend() {
2004 for option in [libc::PR_CAPBSET_DROP, libc::PR_CAP_AMBIENT] {
2005 assert!(is_supported_prctl_option(option));
2006 }
2007 assert!(!is_supported_prctl_option(libc::PR_SET_SECUREBITS));
2008 }
2009
2010 #[test]
2014 fn set_ptracer_reaches_the_kernel() {
2015 assert_eq!(libc::PR_SET_PTRACER, 0x5961_6d61);
2016 assert!(is_supported_prctl_option(libc::PR_SET_PTRACER));
2017 }
2018
2019 #[test]
2020 fn getrandom_accepts_linux_flags() {
2021 for flags in [
2022 0,
2023 libc::GRND_NONBLOCK as usize,
2024 libc::GRND_RANDOM as usize,
2025 (libc::GRND_NONBLOCK | libc::GRND_RANDOM) as usize,
2026 libc::GRND_INSECURE as usize,
2027 (libc::GRND_NONBLOCK | libc::GRND_INSECURE) as usize,
2028 1_usize << 32,
2029 ] {
2030 assert!(
2031 validate_getrandom_flags(flags).is_ok(),
2032 "valid flags rejected: {flags:#x}"
2033 );
2034 }
2035 }
2036
2037 #[test]
2038 fn getrandom_rejects_invalid_flags() {
2039 for flags in [
2040 0x8000_0000,
2041 (1_usize << 32) | 0x8000_0000,
2042 (libc::GRND_RANDOM | libc::GRND_INSECURE) as usize,
2043 ] {
2044 assert_eq!(validate_getrandom_flags(flags), Err(Errno::EINVAL));
2045 }
2046 }
2047
2048 #[test]
2049 fn getrandom_caps_requests_at_linux_max_rw_count() {
2050 assert_eq!(getrandom_request_len(16), 16);
2051 assert_eq!(
2052 getrandom_request_len(usize::MAX),
2053 crate::page::max_rw_count()
2054 );
2055 }
2056
2057 #[test]
2058 fn canonical_random_device_stream_matches_kvm_root_contract() {
2059 let first: Vec<_> = (0..8)
2060 .map(|index| canonical_random_device_byte(0, index))
2061 .collect();
2062 assert_eq!(first, [41, 114, 187, 4, 77, 150, 223, 40]);
2063
2064 let continued: Vec<_> = (8..16)
2065 .map(|index| canonical_random_device_byte(0, index))
2066 .collect();
2067 assert_eq!(continued, [113, 186, 3, 76, 149, 222, 39, 112]);
2068
2069 let seeded: Vec<_> = (0..16)
2070 .map(|index| canonical_random_device_byte(17, index))
2071 .collect();
2072 assert_eq!(
2073 seeded,
2074 [
2075 56, 114, 187, 4, 77, 150, 223, 40, 96, 186, 3, 76, 149, 222, 39, 112
2076 ]
2077 );
2078 assert_ne!(seeded, [first, continued].concat());
2079 }
2080
2081 #[test]
2086 fn random_device_streams_are_distinct_per_open_file_and_pinned() {
2087 let named = |creator, sequence| RandomDeviceStream {
2088 creator: DetPid::from_raw(creator),
2089 sequence,
2090 };
2091 assert_eq!(
2092 random_device_stream_seed(0, named(3, 0)),
2093 0x0581_cd8e_1945_b98c
2094 );
2095 assert_eq!(
2096 random_device_stream_seed(0, named(3, 1)),
2097 0xbd1c_08a3_03e1_67c7
2098 );
2099 assert_eq!(
2100 random_device_stream_seed(17, named(3, 0)),
2101 0x149b_5f6d_a3c3_0dbc
2102 );
2103 let first_bytes = |run_seed, stream| -> Vec<u8> {
2104 let seed = random_device_stream_seed(run_seed, stream);
2105 (0..8)
2106 .map(|index| canonical_random_device_byte(seed, index))
2107 .collect()
2108 };
2109 assert_eq!(
2110 first_bytes(0, named(3, 0)),
2111 [165, 203, 254, 29, 195, 91, 94, 45]
2112 );
2113 let mut seen = std::collections::BTreeSet::new();
2114 for run_seed in [0, 1, 17] {
2115 for creator in [3, 5, 7] {
2116 for sequence in 0..4 {
2117 assert!(
2118 seen.insert(first_bytes(run_seed, named(creator, sequence))),
2119 "seed {run_seed}, creator {creator}, sequence {sequence}"
2120 );
2121 }
2122 }
2123 }
2124 }
2125
2126 #[test]
2127 fn getpriority_reports_default_nice_for_every_target() {
2128 for which in [libc::PRIO_PROCESS, libc::PRIO_PGRP, libc::PRIO_USER] {
2133 assert_eq!(getpriority_result(which as i32), Ok(20));
2134 }
2135 }
2136
2137 #[test]
2138 fn setpriority_accepts_any_change_for_valid_target() {
2139 for which in [libc::PRIO_PROCESS, libc::PRIO_PGRP, libc::PRIO_USER] {
2142 assert_eq!(setpriority_result(which as i32), Ok(0));
2143 }
2144 }
2145
2146 #[test]
2147 fn get_and_set_priority_reject_unknown_which_with_einval() {
2148 for which in [-1, 3, 42] {
2150 assert_eq!(getpriority_result(which), Err(Errno::EINVAL));
2151 assert_eq!(setpriority_result(which), Err(Errno::EINVAL));
2152 }
2153 }
2154
2155 #[test]
2156 fn seccomp_tsync_null_probe_matches_linux_validation() {
2157 assert_eq!(
2158 seccomp_result(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, false,),
2159 Err(Errno::EFAULT)
2160 );
2161 assert_eq!(
2162 seccomp_result(SECCOMP_SET_MODE_FILTER, 1 << 31, false),
2163 Err(Errno::EINVAL)
2164 );
2165 assert_eq!(
2166 seccomp_result(SECCOMP_SET_MODE_FILTER, 0, true),
2167 Err(Errno::EOPNOTSUPP)
2168 );
2169 }
2170
2171 #[test]
2172 fn process_madvise_is_rejected_deterministically() {
2173 assert!(matches!(
2174 Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(
2175 (-10_000_i32) as usize,
2176 0
2177 ),
2178 Err(Error::Errno(Errno::EBADF))
2179 ));
2180 assert!(matches!(
2181 Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(3, 1),
2182 Err(Error::Errno(Errno::EINVAL))
2183 ));
2184 assert!(matches!(
2185 Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(3, 0),
2186 Err(Error::Errno(Errno::EPERM))
2187 ));
2188 }
2189
2190 #[test]
2191 fn syslog_exposes_an_empty_log_and_validates_actions() {
2192 for action in 0..=7 {
2193 assert_eq!(deterministic_syslog_result(action, 0), Ok(0));
2194 }
2195 for action in [9, 10] {
2196 assert_eq!(deterministic_syslog_result(action, 0), Ok(0));
2197 }
2198 assert_eq!(deterministic_syslog_result(8, 1), Ok(0));
2199 assert_eq!(deterministic_syslog_result(8, 8), Ok(0));
2200 assert_eq!(deterministic_syslog_result(8, 0), Err(Errno::EINVAL));
2201 assert_eq!(deterministic_syslog_result(8, 9), Err(Errno::EINVAL));
2202 assert_eq!(deterministic_syslog_result(11, 0), Err(Errno::EINVAL));
2203 }
2204}