use std::{
collections::hash_map::Entry,
option::Option,
sync::{
atomic::{AtomicUsize, Ordering},
Condvar, Mutex,
},
};
use libc::c_long;
use nix::{
errno::Errno,
sys::signal::{SigSet, Signal},
unistd::{gettid, Pid},
};
use crate::{
cache::{
ptrace_map_new, signal_map_new, sys_interrupt_map_new, sys_result_map_new, unix_map_new,
PtraceMap, SighandleInfo, SignalMap, SigreturnTrampolineIP, SysInterrupt, SysInterruptMap,
SysResultMap, UnixMap, SIG_NEST_MAX,
},
confine::ScmpNotifReq,
fs::{block_signal, sigtimedpoll, unblock_signal},
proc::proc_tgid,
retry::retry_on_eintr,
sigset::SydSigSet,
workers::aes::AesLock,
};
pub(crate) mod aes;
pub(crate) mod int;
pub(crate) mod out;
pub(crate) mod ipc;
pub(crate) mod emu;
pub(crate) mod gdb;
pub(crate) struct WorkerCache {
pub(crate) signal_map: SignalMap,
pub(crate) sysint_map: SysInterruptMap,
pub(crate) sysres_map: SysResultMap,
pub(crate) unix_map: UnixMap,
pub(crate) ptrace_map: PtraceMap,
pub(crate) crypt_map: Option<AesLock>,
}
impl WorkerCache {
pub(crate) fn new(crypt_map: Option<AesLock>) -> Self {
Self {
signal_map: signal_map_new(),
sysint_map: sys_interrupt_map_new(),
sysres_map: sys_result_map_new(),
unix_map: unix_map_new(),
ptrace_map: ptrace_map_new(),
crypt_map,
}
}
pub(crate) fn push_sig_handle(&self, tid: Pid) -> Result<(), Errno> {
let mut map = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner());
let info = map.entry(tid).or_insert_with(|| SighandleInfo {
depth: 0,
frames: [None; SIG_NEST_MAX],
in_sigreturn: false,
in_singlestep: false,
trampoline_ip: None,
});
let depth = usize::from(info.depth);
if depth >= SIG_NEST_MAX {
info.frames.copy_within(1..SIG_NEST_MAX, 0);
info.frames[SIG_NEST_MAX - 1] = Some(());
} else {
info.depth = info.depth.checked_add(1).ok_or(Errno::ENOSPC)?;
info.frames[depth] = Some(());
}
Ok(())
}
pub(crate) fn get_sig_trampoline_ip(&self, tid: Pid) -> Option<SigreturnTrampolineIP> {
self.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get(&tid)
.and_then(|info| info.trampoline_ip)
}
pub(crate) fn get_sig_in_singlestep(&self, tid: Pid) -> bool {
self.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get(&tid)
.is_some_and(|info| info.in_singlestep)
}
pub(crate) fn set_sig_in_singlestep(&self, tid: Pid, state: bool) {
if let Some(info) = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get_mut(&tid)
{
info.in_singlestep = state;
}
}
pub(crate) fn set_sig_trampoline_ip(&self, tid: Pid, ip: SigreturnTrampolineIP) {
if let Some(info) = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get_mut(&tid)
{
info.in_singlestep = false;
info.trampoline_ip = Some(ip);
}
}
pub(crate) fn del_sig_trampoline_ip(&self, tid: Pid) {
if let Some(info) = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get_mut(&tid)
{
info.in_singlestep = false;
info.trampoline_ip = None;
}
}
pub(crate) fn depth_sig_handle(&self, tid: Pid) -> u8 {
self.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.get(&tid)
.map_or(0, |info| info.depth)
}
pub(crate) fn has_sig_handle(&self, tid: Pid) -> bool {
let map = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner());
map.get(&tid).is_some_and(|info| info.in_sigreturn)
}
pub(crate) fn enter_sig_handle(&self, tid: Pid) -> bool {
let mut map = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner());
let info = match map.get_mut(&tid) {
Some(info) => info,
None => return false,
};
if info.depth == 0 {
return false;
}
info.in_sigreturn = true;
true
}
pub(crate) fn exit_sig_handle(&self, tid: Pid) -> bool {
let mut map = self
.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner());
let mut entry = match map.entry(tid) {
Entry::Occupied(entry) => entry,
Entry::Vacant(_) => return false,
};
let info = entry.get_mut();
if !info.in_sigreturn || info.depth == 0 {
return false;
}
info.in_sigreturn = false;
let depth = info.depth.saturating_sub(1);
info.frames[usize::from(depth)] = None;
info.depth = depth;
if info.depth == 0 {
entry.remove();
}
true
}
pub(crate) fn retire_sig_handle(&self, tid: Pid) {
self.signal_map
.sig_handle
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove(&tid);
}
pub(crate) fn retire_ptrace_tgid(&self, tgid: Pid) {
let mut map = self
.ptrace_map
.write()
.unwrap_or_else(|err| err.into_inner());
map.retain(|_, &mut pid| pid != tgid)
}
pub(crate) fn retire_ptrace_tid(&self, tid: Pid) {
self.ptrace_map
.write()
.unwrap_or_else(|err| err.into_inner())
.remove(&tid);
}
pub(crate) fn add_chdir(&self, pid: Pid, scno: c_long) {
self.sysres_map
.trace_chdir
.lock()
.unwrap_or_else(|err| err.into_inner())
.insert(pid, scno);
}
pub(crate) fn get_chdir(&self, pid: Pid) -> Option<c_long> {
self.sysres_map
.trace_chdir
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove(&pid)
}
pub(crate) fn add_mmap(&self, pid: Pid, scno: c_long, args: [u64; 6]) {
self.sysres_map
.trace_mmap
.lock()
.unwrap_or_else(|err| err.into_inner())
.insert(pid, (scno, args));
}
pub(crate) fn get_mmap(&self, pid: Pid) -> Option<(c_long, [u64; 6])> {
self.sysres_map
.trace_mmap
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove(&pid)
}
pub(crate) fn add_error(&self, pid: Pid, errno: Option<Errno>) {
self.sysres_map
.trace_error
.lock()
.unwrap_or_else(|err| err.into_inner())
.insert(pid, errno);
}
pub(crate) fn get_error(&self, pid: Pid) -> Option<(Pid, Option<Errno>)> {
self.sysres_map
.trace_error
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove_entry(&pid)
}
pub(crate) fn add_sig_restart(&self, request_tgid: Pid, sig: libc::c_int) -> Result<(), Errno> {
let mut map = self
.sysint_map
.sig_restart
.lock()
.unwrap_or_else(|err| err.into_inner());
if let Some(set) = map.get_mut(&request_tgid) {
set.add(sig);
return Ok(());
}
let mut set = SydSigSet::new(0);
set.add(sig);
map.try_reserve(1).or(Err(Errno::ENOMEM))?;
map.insert(request_tgid, set);
Ok(())
}
pub(crate) fn del_sig_restart(&self, request_tgid: Pid, sig: libc::c_int) {
let mut map = self
.sysint_map
.sig_restart
.lock()
.unwrap_or_else(|err| err.into_inner());
let set_nil = if let Some(set) = map.get_mut(&request_tgid) {
set.del(sig);
set.is_empty()
} else {
return;
};
if set_nil {
map.remove(&request_tgid);
}
}
pub(crate) fn retire_sig_restart(&self, tgid: Pid) {
self.sysint_map
.sig_restart
.lock()
.unwrap_or_else(|err| err.into_inner())
.remove(&tgid);
}
pub(crate) fn add_sys_block(
&self,
request: ScmpNotifReq,
ignore_restart: bool,
) -> Result<(), Errno> {
let handler_tid = gettid();
let tgid = proc_tgid(request.pid())?;
let interrupt = SysInterrupt::new(request, handler_tid, tgid, ignore_restart)?;
let (ref lock, ref cvar) = *self.sysint_map.sys_block;
let mut map = lock.lock().unwrap_or_else(|err| err.into_inner());
map.retain_mut(|interrupt| handler_tid != interrupt.handler || interrupt.delete());
map.try_reserve(1).or(Err(Errno::ENOMEM))?;
map.push(interrupt);
cvar.notify_one();
let mut mask = SigSet::empty();
mask.add(Signal::SIGALRM);
let _ = retry_on_eintr(|| sigtimedpoll(&mask, None));
unblock_signal(Signal::SIGALRM)
}
pub(crate) fn del_sys_block(&self, request_id: u64) -> Result<(), Errno> {
block_signal(Signal::SIGALRM)?;
let (ref lock, ref _cvar) = *self.sysint_map.sys_block;
let mut map = lock.lock().unwrap_or_else(|err| err.into_inner());
map.retain_mut(|interrupt| request_id != interrupt.request.id || interrupt.delete());
Ok(())
}
pub(crate) fn retire_unix_map(&self, pid: Pid) {
self.unix_map
.write()
.unwrap_or_else(|err| err.into_inner())
.retain(|_, val| val.pid != pid);
}
pub(crate) fn del_tid(&self, tid: Pid) {
self.retire_sig_handle(tid);
self.retire_ptrace_tid(tid);
self.retire_unix_map(tid);
let _ = self.get_error(tid);
let _ = self.get_chdir(tid);
}
pub(crate) fn del_tgid(&self, tgid: Pid) {
self.retire_sig_restart(tgid);
self.retire_ptrace_tgid(tgid);
self.del_tid(tgid);
}
}
const MAX_SIZE: usize = (1 << (usize::BITS / 2)) - 1;
const WORKER_BUSY_MASK: usize = MAX_SIZE;
const INCREMENT_TOTAL: usize = 1 << (usize::BITS / 2);
const INCREMENT_BUSY: usize = 1;
#[derive(Default)]
pub(crate) struct WorkerData {
pub(crate) counter: AtomicUsize,
pub(crate) mon_signal: (Mutex<bool>, Condvar),
}
impl WorkerData {
pub(crate) fn decrement_both(&self) -> (usize, usize) {
let old_val = self
.counter
.fetch_sub(INCREMENT_TOTAL | INCREMENT_BUSY, Ordering::Relaxed);
Self::split(old_val)
}
pub(crate) fn increment_worker_total(&self) -> usize {
let old_val = self.counter.fetch_add(INCREMENT_TOTAL, Ordering::Relaxed);
Self::total(old_val)
}
pub(crate) fn decrement_worker_total(&self) -> usize {
let old_val = self.counter.fetch_sub(INCREMENT_TOTAL, Ordering::Relaxed);
self.notify_monitor();
Self::total(old_val)
}
pub(crate) fn increment_worker_busy(&self) -> usize {
let old_val = self.counter.fetch_add(INCREMENT_BUSY, Ordering::Relaxed);
let (total, old_busy) = Self::split(old_val);
if old_busy.saturating_add(1) >= total {
self.notify_monitor();
}
Self::busy(old_val)
}
pub(crate) fn decrement_worker_busy(&self) -> usize {
let old_val = self.counter.fetch_sub(INCREMENT_BUSY, Ordering::Relaxed);
Self::busy(old_val)
}
pub(crate) fn notify_monitor(&self) {
let (ref lock, ref cvar) = self.mon_signal;
let mut guard = lock.lock().unwrap_or_else(|err| err.into_inner());
*guard = true; cvar.notify_one();
}
pub(crate) fn split(val: usize) -> (usize, usize) {
let total_count = val >> (usize::BITS / 2);
let busy_count = val & WORKER_BUSY_MASK;
(total_count, busy_count)
}
fn total(val: usize) -> usize {
val >> (usize::BITS / 2)
}
fn busy(val: usize) -> usize {
val & WORKER_BUSY_MASK
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_worker_data_1() {
assert_eq!(WorkerData::total(0), 0);
}
#[test]
fn test_worker_data_2() {
assert_eq!(WorkerData::busy(0), 0);
}
#[test]
fn test_worker_data_3() {
let val = INCREMENT_TOTAL;
assert_eq!(WorkerData::total(val), 1);
assert_eq!(WorkerData::busy(val), 0);
}
#[test]
fn test_worker_data_4() {
let val = INCREMENT_BUSY;
assert_eq!(WorkerData::busy(val), 1);
assert_eq!(WorkerData::total(val), 0);
}
#[test]
fn test_worker_data_5() {
let val = INCREMENT_TOTAL | INCREMENT_BUSY;
assert_eq!(WorkerData::total(val), 1);
assert_eq!(WorkerData::busy(val), 1);
}
#[test]
fn test_worker_data_6() {
assert_eq!(WorkerData::busy(MAX_SIZE), MAX_SIZE);
assert_eq!(WorkerData::total(MAX_SIZE), 0);
}
}