/* * Copyright (c) Meta Platforms, Inc. and its affiliates. * * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ // signal handling related tests. use reverie::{ syscalls::{ExitGroup, Syscall, SyscallInfo}, Error, Guest, Tool, }; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Default, Clone)] struct LocalState; #[derive(Debug, Serialize, Deserialize, Default)] struct ThreadState; #[reverie::tool] impl Tool for LocalState { type ThreadState = ThreadState; async fn handle_syscall_event>( &self, guest: &mut T, syscall: Syscall, ) -> Result { let exit_failure = ExitGroup::new().with_status(1); match syscall { // glibc should wrap signalfd -> signalfd4(2). Syscall::Signalfd(_) => guest.tail_inject(exit_failure).await, Syscall::Signalfd4(_) => { let (_, args) = syscall.into_parts(); assert_eq!(args.arg2, 8); assert_eq!(args.arg3, libc::SFD_CLOEXEC as usize); guest.tail_inject(syscall).await } _ => guest.tail_inject(syscall).await, } } } #[cfg(all(not(sanitized), test))] mod tests { use super::*; use nix::sys::signal::Signal; use reverie_ptrace::testing::check_fn; use std::{ fs::File, io::{self, Read}, mem::{self, MaybeUninit}, os::unix::io::FromRawFd, }; // kernel_sigset_t used by naked syscall #[derive(Clone, Copy, PartialEq, Eq, Debug)] struct KernelSigset(u64); impl From<&[Signal]> for KernelSigset { fn from(signals: &[Signal]) -> Self { let mut set: u64 = 0; for &sig in signals { set |= 1u64 << (sig as usize - 1); } KernelSigset(set) } } #[allow(dead_code)] unsafe fn unblock_signals(signals: &[Signal]) -> io::Result { let set = KernelSigset::from(signals); let mut oldset: MaybeUninit = MaybeUninit::uninit(); if libc::syscall( libc::SYS_rt_sigprocmask, libc::SIG_UNBLOCK, &set as *const _, oldset.as_mut_ptr(), 8, ) != 0 { Err(io::Error::last_os_error()) } else { Ok(KernelSigset(oldset.assume_init())) } } #[test] // The actual test is in `handle_syscall_event`. To test we can get // pending signals from tracee, by injecting rt_sigpending. fn signalfd_sanity_check() { check_fn::(|| { assert!(unsafe { unblock_signals(&[Signal::SIGVTALRM, Signal::SIGALRM]) }.is_ok()); let mut sigset: MaybeUninit = MaybeUninit::uninit(); let sigset = unsafe { libc::sigemptyset(sigset.as_mut_ptr()); libc::sigaddset(sigset.as_mut_ptr(), libc::SIGALRM); libc::sigaddset(sigset.as_mut_ptr(), libc::SIGVTALRM); sigset.assume_init() }; let fd = unsafe { libc::signalfd(-1, &sigset as *const _, libc::SFD_CLOEXEC) }; assert!(fd > 0); let mut file = unsafe { File::from_raw_fd(fd) }; let mut siginfo = [0; mem::size_of::()]; unsafe { libc::alarm(1) }; assert!(file.read_exact(&mut siginfo).is_ok()); let siginfo: libc::signalfd_siginfo = unsafe { mem::transmute(siginfo) }; assert_eq!(siginfo.ssi_signo, libc::SIGALRM as u32); unsafe { libc::syscall(libc::SYS_exit_group, 0) }; }); } }