use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use reverie::Error;
use reverie::Guest;
use reverie::syscalls;
use reverie::syscalls::AddrMut;
use reverie::syscalls::ArchPrctlCmd;
use reverie::syscalls::Errno;
use reverie::syscalls::MemoryAccess;
use crate::consts::DEFAULT_HOSTNAME;
use crate::detlog;
#[cfg(test)]
use crate::random::GETRANDOM_MAX_BYTES;
use crate::random::RANDOM_FILL_CHUNK_BYTES;
#[cfg(test)]
use crate::random::getrandom_request_len;
#[cfg(test)]
use crate::random::validate_getrandom_flags;
use crate::random::write_random_chunk;
use crate::record_or_replay::RecordOrReplay;
use crate::tool_global::create_session;
use crate::tool_global::set_process_group;
use crate::tool_local::Detcore;
use crate::types::DetPid;
const ARCH_GET_XCOMP_SUPP: libc::c_int = 0x1021;
const ARCH_GET_XCOMP_PERM: libc::c_int = 0x1022;
const ARCH_REQ_XCOMP_PERM: libc::c_int = 0x1023;
const ARCH_GET_XCOMP_GUEST_PERM: libc::c_int = 0x1024;
const ARCH_REQ_XCOMP_GUEST_PERM: libc::c_int = 0x1025;
const ARCH_SHSTK_ENABLE: libc::c_int = 0x5001;
const ARCH_SHSTK_DISABLE: libc::c_int = 0x5002;
const ARCH_SHSTK_LOCK: libc::c_int = 0x5003;
const ARCH_SHSTK_UNLOCK: libc::c_int = 0x5004;
const ARCH_SHSTK_STATUS: libc::c_int = 0x5005;
const ARCH_SHSTK_VALID_MASK: usize = 0b11;
const SECCOMP_SET_MODE_STRICT: u32 = 0;
const SECCOMP_SET_MODE_FILTER: u32 = 1;
const SECCOMP_GET_ACTION_AVAIL: u32 = 2;
const SECCOMP_GET_NOTIF_SIZES: u32 = 3;
const SECCOMP_FILTER_FLAG_TSYNC: u32 = 1;
fn seccomp_result(op: u32, flags: u32, has_args: bool) -> Result<i64, Errno> {
if op > SECCOMP_GET_NOTIF_SIZES {
return Err(Errno::EINVAL);
}
if op == SECCOMP_SET_MODE_STRICT && (flags != 0 || has_args) {
return Err(Errno::EINVAL);
}
if op == SECCOMP_SET_MODE_FILTER && flags & !SECCOMP_FILTER_FLAG_TSYNC != 0 {
return Err(Errno::EINVAL);
}
if matches!(
op,
SECCOMP_SET_MODE_FILTER | SECCOMP_GET_ACTION_AVAIL | SECCOMP_GET_NOTIF_SIZES
) && !has_args
{
return Err(Errno::EFAULT);
}
Err(Errno::EOPNOTSUPP)
}
fn is_supported_prctl_option(option: libc::c_int) -> bool {
matches!(
option,
libc::PR_SET_NAME
| libc::PR_GET_NAME
| libc::PR_SET_THP_DISABLE
| libc::PR_GET_THP_DISABLE
| libc::PR_SET_DUMPABLE
| libc::PR_GET_DUMPABLE
| libc::PR_SET_KEEPCAPS
| libc::PR_GET_KEEPCAPS
| libc::PR_SET_PDEATHSIG
| libc::PR_GET_PDEATHSIG
)
}
fn is_backend_virtualized_capability_prctl(option: libc::c_int) -> bool {
matches!(option, libc::PR_CAPBSET_DROP | libc::PR_CAP_AMBIENT)
}
fn is_valid_prio_which(which: i32) -> bool {
which == libc::PRIO_PROCESS as i32
|| which == libc::PRIO_PGRP as i32
|| which == libc::PRIO_USER as i32
}
fn getpriority_result(which: i32) -> Result<i64, Errno> {
if is_valid_prio_which(which) {
Ok(20)
} else {
Err(Errno::EINVAL)
}
}
fn setpriority_result(which: i32) -> Result<i64, Errno> {
if is_valid_prio_which(which) {
Ok(0)
} else {
Err(Errno::EINVAL)
}
}
fn deterministic_syslog_result(action: i32, len: usize) -> Result<i64, Errno> {
const SYSLOG_ACTION_CONSOLE_LEVEL: i32 = 8;
match action {
0..=7 | 9..=10 => Ok(0),
SYSLOG_ACTION_CONSOLE_LEVEL if (1..=8).contains(&len) => Ok(0),
_ => Err(Errno::EINVAL),
}
}
fn from_str(s: &str) -> [i8; 65] {
let mut ret: [i8; 65] = [0; 65];
for (i, ch) in s.bytes().take(64).enumerate() {
ret[i] = ch as i8;
}
ret
}
const RANDOM_DEVICE_BYTE_STRIDE: u8 = 73;
const RANDOM_DEVICE_FIRST_BYTE: u8 = 41;
fn canonical_random_device_byte(seed: u64, index: u64) -> u8 {
let seed_byte = seed.rotate_right(((index % 8) * 8) as u32) as u8;
(index as u8)
.wrapping_mul(RANDOM_DEVICE_BYTE_STRIDE)
.wrapping_add(RANDOM_DEVICE_FIRST_BYTE)
^ seed_byte
}
fn fill_canonical_random_iovecs(
memory: &mut impl MemoryAccess,
iovecs: &[crate::iovecs::ImportedIovec],
seed: u64,
stream_offset: u64,
hasher: &mut DefaultHasher,
) -> Result<usize, Error> {
let mut local_words = [0_u64; RANDOM_FILL_CHUNK_BYTES / std::mem::size_of::<u64>()];
let mut written = 0_usize;
for iov in iovecs {
let mut segment_written = 0;
while segment_written < iov.len {
let remote_chunk = match iov
.base
.checked_add(segment_written)
.and_then(AddrMut::<u8>::from_raw)
{
Some(address) => address,
None if written == 0 => return Err(Errno::EFAULT.into()),
None => return Ok(written),
};
let chunk_len = (iov.len - segment_written).min(RANDOM_FILL_CHUNK_BYTES);
let local_buf = unsafe {
std::slice::from_raw_parts_mut(local_words.as_mut_ptr().cast::<u8>(), chunk_len)
};
for (index, byte) in local_buf.iter_mut().enumerate() {
*byte = canonical_random_device_byte(
seed,
stream_offset
.saturating_add(written as u64)
.saturating_add(index as u64),
);
}
let n = match write_random_chunk(memory, remote_chunk, local_buf) {
Ok(n) => n,
Err(Errno::EFAULT) if written > 0 => return Ok(written),
Err(error) => return Err(crate::random::copy_error(error)),
};
if n == 0 && written == 0 {
return Err(Errno::EFAULT.into());
}
if cfg!(debug_assertions) {
Hash::hash_slice(&local_buf[..n], hasher);
}
written += n;
segment_written += n;
if n < chunk_len {
return Ok(written);
}
}
}
Ok(written)
}
impl<T: RecordOrReplay> Detcore<T> {
pub async fn handle_seccomp<G: Guest<Self>>(
&self,
_guest: &mut G,
call: syscalls::Seccomp,
) -> Result<i64, Error> {
seccomp_result(call.op(), call.flags(), call.args().is_some()).map_err(Into::into)
}
fn write_arch_prctl_u64<G: Guest<Self>>(
&self,
guest: &mut G,
raw_addr: usize,
value: u64,
) -> Result<i64, Error> {
let addr = AddrMut::<u64>::from_raw(raw_addr).ok_or(Errno::EFAULT)?;
guest.memory().write_value(addr, &value)?;
Ok(0)
}
pub async fn handle_arch_prctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::ArchPrctl,
) -> Result<i64, Error> {
let cpuid_uses_backend_policy =
self.cfg.virtualize_cpuid && self.cfg.cpuid_virtualized_by_backend;
let cpuid_uses_faulting = self.cfg.virtualize_cpuid && guest.has_cpuid_interception();
match call.cmd() {
ArchPrctlCmd::ARCH_SET_FS(_)
| ArchPrctlCmd::ARCH_SET_GS(_)
| ArchPrctlCmd::ARCH_GET_FS(_)
| ArchPrctlCmd::ARCH_GET_GS(_) => Ok(guest.inject(call).await?),
ArchPrctlCmd::ARCH_GET_CPUID(_) if cpuid_uses_backend_policy => Ok(1),
ArchPrctlCmd::ARCH_SET_CPUID(value) if cpuid_uses_backend_policy => {
if value == 0 {
Err(Errno::EPERM.into())
} else {
Ok(0)
}
}
ArchPrctlCmd::ARCH_GET_CPUID(_) if cpuid_uses_faulting => Ok(0),
ArchPrctlCmd::ARCH_SET_CPUID(value) if cpuid_uses_faulting => {
if value == 0 {
Ok(0)
} else {
Err(Errno::EPERM.into())
}
}
ArchPrctlCmd::ARCH_GET_CPUID(_) if !self.cfg.virtualize_cpuid => Ok(1),
ArchPrctlCmd::ARCH_SET_CPUID(value) if !self.cfg.virtualize_cpuid => {
if value == 0 {
Err(Errno::EPERM.into())
} else {
Ok(0)
}
}
ArchPrctlCmd::ARCH_GET_CPUID(_) | ArchPrctlCmd::ARCH_SET_CPUID(_) => {
Ok(guest.inject(call).await?)
}
ArchPrctlCmd::Other(
ARCH_GET_XCOMP_SUPP | ARCH_GET_XCOMP_PERM | ARCH_GET_XCOMP_GUEST_PERM,
addr,
) => self.write_arch_prctl_u64(guest, addr, 0),
ArchPrctlCmd::Other(ARCH_REQ_XCOMP_PERM | ARCH_REQ_XCOMP_GUEST_PERM, _) => {
Err(Errno::EINVAL.into())
}
ArchPrctlCmd::Other(ARCH_SHSTK_STATUS, addr) => {
self.write_arch_prctl_u64(guest, addr, 0)
}
ArchPrctlCmd::Other(ARCH_SHSTK_DISABLE, features)
if features != 0 && features & !ARCH_SHSTK_VALID_MASK == 0 =>
{
Ok(0)
}
ArchPrctlCmd::Other(ARCH_SHSTK_DISABLE, _)
| ArchPrctlCmd::Other(ARCH_SHSTK_ENABLE | ARCH_SHSTK_LOCK | ARCH_SHSTK_UNLOCK, _) => {
Err(Errno::EINVAL.into())
}
ArchPrctlCmd::Other(_, _) => Err(Errno::EINVAL.into()),
}
}
pub async fn handle_prctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Prctl,
) -> Result<i64, Error> {
match call.option() {
libc::PR_CAPBSET_READ => Ok(self.record_or_replay(guest, call).await?),
libc::PR_SET_TIMERSLACK => {
let requested = call.arg2();
let state = guest.thread_state_mut();
state.timer_slack_ns = if requested == 0 {
state.default_timer_slack_ns
} else {
requested
};
Ok(0)
}
libc::PR_GET_TIMERSLACK => Ok(guest.thread_state().timer_slack_ns as i64),
option
if guest.config().backend_virtualizes_capability_prctls
&& is_backend_virtualized_capability_prctl(option) =>
{
self.passthrough(guest, call.into()).await
}
option if is_supported_prctl_option(option) => {
self.passthrough(guest, call.into()).await
}
_ => Err(Errno::ENOSYS.into()),
}
}
pub async fn handle_getpriority<G: Guest<Self>>(
&self,
_guest: &mut G,
call: syscalls::Getpriority,
) -> Result<i64, Error> {
Ok(getpriority_result(call.which())?)
}
pub async fn handle_syslog<G: Guest<Self>>(
&self,
_guest: &mut G,
call: syscalls::Syslog,
) -> Result<i64, Error> {
Ok(deterministic_syslog_result(call.priority(), call.len())?)
}
pub async fn handle_setpriority<G: Guest<Self>>(
&self,
_guest: &mut G,
call: syscalls::Setpriority,
) -> Result<i64, Error> {
Ok(setpriority_result(call.which())?)
}
pub fn handle_process_madvise(pidfd: usize, flags: usize) -> Result<i64, Error> {
if flags != 0 {
return Err(Errno::EINVAL.into());
}
if (pidfd as libc::c_int) < 0 {
Err(Errno::EBADF.into())
} else {
Err(Errno::EPERM.into())
}
}
pub(super) fn fill_random_device_bytes<G: Guest<Self>>(
&self,
guest: &mut G,
remote_buf: AddrMut<u8>,
len: usize,
stream_offset: u64,
) -> Result<usize, Error> {
self.fill_random_device_iovecs(
guest,
&[crate::iovecs::ImportedIovec {
base: remote_buf.as_raw(),
len,
}],
stream_offset,
)
}
pub(super) fn fill_random_device_iovecs<G: Guest<Self>>(
&self,
guest: &mut G,
iovecs: &[crate::iovecs::ImportedIovec],
stream_offset: u64,
) -> Result<usize, Error> {
let seed = guest.config().rng_seed();
let mut hasher = DefaultHasher::new();
let written = fill_canonical_random_iovecs(
&mut guest.memory(),
iovecs,
seed,
stream_offset,
&mut hasher,
)?;
if cfg!(debug_assertions) {
detlog!(
"[dtid {}] USER RAND [/dev/[u]random] Filled guest memory with {} canonical random bytes at offset {}, hash of bytes: {}",
guest.thread_state().dettid,
written,
stream_offset,
hasher.finish()
);
}
Ok(written)
}
pub async fn handle_uname<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Uname,
) -> Result<i64, Error> {
let ret = self.record_or_replay(guest, call).await?;
if let Some(buf) = call.buf() {
let mut un = guest.memory().read_value(buf)?;
let epoch = guest.config().epoch;
if !guest.config().has_uts_namespace {
un.nodename = from_str(DEFAULT_HOSTNAME);
un.domainname = from_str(DEFAULT_HOSTNAME.split('.').next_back().unwrap_or(""));
}
un.release = from_str("5.2.0");
un.version = from_str(&format!("#1 SMP {}", epoch.format("%a %b %d %T %Z %Y")));
guest.memory().write_value(buf, &un)?;
}
Ok(ret)
}
pub async fn handle_getrandom<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Getrandom,
) -> Result<i64, Error> {
let memory = guest.memory();
let dettid = guest.thread_state().dettid;
crate::random::getrandom(guest.thread_state_mut().thread_prng(), memory, dettid, call)
}
pub async fn handle_setsid<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Setsid,
) -> Result<i64, Error> {
let res = guest.inject(call).await?;
let process = guest.thread_state().detpid.expect("detpid unset");
let _ = create_session(guest, process).await;
if guest.config().kill_daemons {
guest.daemonize().await;
}
Ok(res)
}
pub async fn handle_setpgid<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Setpgid,
) -> Result<i64, Error> {
let res = guest.inject(call).await?;
let caller = guest.thread_state().detpid.expect("detpid unset");
let process = if call.pid() == 0 {
caller
} else {
DetPid::from_raw(call.pid())
};
let group = if call.pgid() == 0 {
process
} else {
DetPid::from_raw(call.pgid())
};
let _ = set_process_group(guest, process, group).await;
Ok(res)
}
pub async fn handle_membarrier<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Membarrier,
) -> Result<i64, Error> {
const MEMBARRIER_CMD_QUERY: i32 = 0;
const MEMBARRIER_CMD_GLOBAL: i32 = 1 << 0;
const MEMBARRIER_CMD_GLOBAL_EXPEDITED: i32 = 1 << 1;
const MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED: i32 = 1 << 2;
const MEMBARRIER_CMD_PRIVATE_EXPEDITED: i32 = 1 << 3;
const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED: i32 = 1 << 4;
const SUPPORTED: i32 = MEMBARRIER_CMD_GLOBAL
| MEMBARRIER_CMD_GLOBAL_EXPEDITED
| MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED
| MEMBARRIER_CMD_PRIVATE_EXPEDITED
| MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED;
let cmd = call.cmd();
if cmd == MEMBARRIER_CMD_QUERY {
detlog!(
"[dtid {}] membarrier(QUERY) => reporting emulated commands {:#x}",
guest.thread_state().dettid,
SUPPORTED,
);
Ok(SUPPORTED as i64)
} else {
detlog!(
"[dtid {}] membarrier(cmd={}) no-op (threads are serialized on one CPU)",
guest.thread_state().dettid,
cmd,
);
Ok(0)
}
}
pub async fn handle_getcpu<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Getcpu,
) -> Result<i64, Error> {
if let Some(cpu) = call.cpu() {
guest.memory().write_value(cpu, &0)?;
}
if let Some(node) = call.node() {
guest.memory().write_value(node, &0)?;
}
Ok(0)
}
pub async fn handle_getresuid<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Getresuid,
) -> Result<i64, Error> {
if let Some(ruid) = call.ruid() {
guest.memory().write_value(ruid, &0)?;
}
if let Some(euid) = call.euid() {
guest.memory().write_value(euid, &0)?;
}
if let Some(suid) = call.suid() {
guest.memory().write_value(suid, &0)?;
}
Ok(0)
}
pub async fn handle_getresgid<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::Getresgid,
) -> Result<i64, Error> {
if let Some(rgid) = call.rgid() {
guest.memory().write_value(rgid, &0)?;
}
if let Some(egid) = call.egid() {
guest.memory().write_value(egid, &0)?;
}
if let Some(sgid) = call.sgid() {
guest.memory().write_value(sgid, &0)?;
}
Ok(0)
}
pub async fn handle_get_mempolicy<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::GetMempolicy,
) -> Result<i64, Error> {
if let Some(policy) = call.policy() {
guest.memory().write_value(policy, &0)?;
}
Ok(0)
}
pub async fn handle_move_pages<G: Guest<Self>>(
&self,
guest: &mut G,
call: syscalls::MovePages,
) -> Result<i64, Error> {
if let Some(status) = call.status() {
let count = call.nr_pages() as usize;
let zeros = vec![0i32; count];
guest.memory().write_values(status, &zeros)?;
}
Ok(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct ScatterMemory {
bytes: Vec<u8>,
outcomes: std::collections::VecDeque<Result<usize, Errno>>,
writes: usize,
}
impl ScatterMemory {
fn new(size: usize, outcomes: impl IntoIterator<Item = Result<usize, Errno>>) -> Self {
Self {
bytes: vec![0xa5; size],
outcomes: outcomes.into_iter().collect(),
writes: 0,
}
}
}
impl MemoryAccess for ScatterMemory {
fn read_vectored(
&self,
_: &[std::io::IoSlice],
_: &mut [std::io::IoSliceMut],
) -> Result<usize, Errno> {
panic!("canonical scatter must not read guest bytes")
}
fn write_vectored(
&mut self,
_: &[std::io::IoSlice],
_: &mut [std::io::IoSliceMut],
) -> Result<usize, Errno> {
panic!("canonical scatter must use the user-access capability")
}
fn write_with_user_access(
&mut self,
address: AddrMut<u8>,
bytes: &[u8],
) -> Result<usize, Errno> {
self.writes += 1;
let count = self.outcomes.pop_front().unwrap_or(Ok(bytes.len()))?;
assert!(count <= bytes.len());
let offset = address.as_raw() - 0x1000;
self.bytes[offset..offset + count].copy_from_slice(&bytes[..count]);
Ok(count)
}
}
fn assert_copy_failure(error: Error, expected: Errno) {
let Error::Tool(error) = error else {
panic!("copy failure became a guest errno: {error:?}")
};
assert_eq!(
error
.downcast_ref::<crate::random::RandomCopyFailure>()
.expect("typed copy failure")
.errno(),
expected
);
}
fn scatter(memory: &mut ScatterMemory, lengths: &[usize], offset: u64) -> Result<usize, Error> {
let mut base = 0x1000;
let vectors: Vec<_> = lengths
.iter()
.map(|&len| {
let vector = crate::iovecs::ImportedIovec { base, len };
base += len;
vector
})
.collect();
fill_canonical_random_iovecs(memory, &vectors, 0, offset, &mut DefaultHasher::new())
}
#[test]
fn random_scatter_distinguishes_fault_prefixes_from_backend_errors() {
for lengths in [vec![3, 5], vec![RANDOM_FILL_CHUNK_BYTES + 5]] {
let prefix = if lengths.len() == 2 {
3
} else {
RANDOM_FILL_CHUNK_BYTES
};
for error in [Errno::EFAULT, Errno::EIO, Errno::ENOMEM] {
let mut memory = ScatterMemory::new(prefix + 5, [Ok(prefix), Err(error)]);
let result = scatter(&mut memory, &lengths, 7);
match error {
Errno::EFAULT => assert_eq!(result.unwrap(), prefix),
_ => assert_copy_failure(result.unwrap_err(), error),
}
let expected: Vec<_> = (7..7 + prefix as u64)
.map(|index| canonical_random_device_byte(0, index))
.collect();
assert_eq!(&memory.bytes[..prefix], expected);
assert_eq!(&memory.bytes[prefix..], &[0xa5; 5]);
assert_eq!(memory.writes, 2);
}
}
}
#[test]
fn random_scatter_short_or_zero_copy_stops_without_touching_later_segments() {
for first in [0, 2] {
let mut memory = ScatterMemory::new(10, [Ok(first)]);
let result = scatter(&mut memory, &[5, 5], 0);
if first == 0 {
assert!(matches!(result, Err(Error::Errno(Errno::EFAULT))));
} else {
assert_eq!(result.unwrap(), first);
assert_eq!(&memory.bytes[..2], &[41, 114]);
}
assert!(memory.bytes[first..].iter().all(|&byte| byte == 0xa5));
assert_eq!(memory.writes, 1);
}
let mut memory = ScatterMemory::new(10, [Ok(5), Ok(0)]);
assert_eq!(scatter(&mut memory, &[5, 5], 0).unwrap(), 5);
assert!(memory.bytes[5..].iter().all(|&byte| byte == 0xa5));
assert_eq!(memory.writes, 2);
}
#[test]
fn random_scatter_saturated_bytes_match_partitioned_calls() {
for (offset, expected) in [(u64::MAX - 2, [78, 151, 224, 224]), (u64::MAX, [224; 4])] {
let mut single = ScatterMemory::new(4, []);
assert_eq!(scatter(&mut single, &[4], offset).unwrap(), 4);
let mut partitioned = ScatterMemory::new(4, []);
let mut hash = DefaultHasher::new();
assert_eq!(
fill_canonical_random_iovecs(
&mut partitioned,
&[crate::iovecs::ImportedIovec {
base: 0x1000,
len: 1
}],
0,
offset,
&mut hash
)
.unwrap(),
1
);
assert_eq!(
fill_canonical_random_iovecs(
&mut partitioned,
&[crate::iovecs::ImportedIovec {
base: 0x1001,
len: 3
}],
0,
offset.saturating_add(1),
&mut hash
)
.unwrap(),
3
);
assert_eq!(single.bytes, expected);
assert_eq!(partitioned.bytes, expected);
}
}
#[test]
fn random_scatter_backend_error_rolls_back_shared_cursor_after_eight_byte_prefix() {
let fd = crate::fd::DetFd::new(
3,
nix::fcntl::OFlag::O_RDONLY,
crate::fd::FdType::Rng,
crate::types::OpenFileId::new(crate::types::DetTid::from_raw(1), 0),
);
for error in [Errno::EFAULT, Errno::EIO] {
let mut memory = ScatterMemory::new(8, [Ok(4), Err(error)]);
let result = fd.with_random_device_stream(|offset| scatter(&mut memory, &[8], offset));
if error == Errno::EFAULT {
assert_eq!(result.unwrap(), 4);
} else {
assert_copy_failure(result.unwrap_err(), Errno::EIO);
}
assert_eq!(fd.random_device_offset(), 4);
assert_eq!(&memory.bytes[4..], &[0xa5; 4]);
assert_eq!(memory.writes, 2);
}
}
#[test]
fn prctl_support_covers_deterministic_controls() {
for option in [
libc::PR_SET_NAME,
libc::PR_GET_NAME,
libc::PR_SET_THP_DISABLE,
libc::PR_GET_THP_DISABLE,
libc::PR_SET_DUMPABLE,
libc::PR_GET_DUMPABLE,
libc::PR_SET_KEEPCAPS,
libc::PR_GET_KEEPCAPS,
libc::PR_SET_PDEATHSIG,
libc::PR_GET_PDEATHSIG,
] {
assert!(is_supported_prctl_option(option));
}
assert!(!is_supported_prctl_option(libc::PR_SET_NO_NEW_PRIVS));
assert!(!is_supported_prctl_option(libc::PR_SET_TIMERSLACK));
assert!(!is_supported_prctl_option(libc::PR_GET_TIMERSLACK));
}
#[test]
fn backend_virtualized_prctl_support_is_capability_scoped() {
for option in [libc::PR_CAPBSET_DROP, libc::PR_CAP_AMBIENT] {
assert!(is_backend_virtualized_capability_prctl(option));
}
for option in [libc::PR_SET_KEEPCAPS, libc::PR_SET_SECUREBITS] {
assert!(!is_backend_virtualized_capability_prctl(option));
}
}
#[test]
fn getrandom_accepts_linux_flags() {
for flags in [
0,
libc::GRND_NONBLOCK as usize,
libc::GRND_RANDOM as usize,
(libc::GRND_NONBLOCK | libc::GRND_RANDOM) as usize,
libc::GRND_INSECURE as usize,
(libc::GRND_NONBLOCK | libc::GRND_INSECURE) as usize,
1_usize << 32,
] {
assert!(
validate_getrandom_flags(flags).is_ok(),
"valid flags rejected: {flags:#x}"
);
}
}
#[test]
fn getrandom_rejects_invalid_flags() {
for flags in [
0x8000_0000,
(1_usize << 32) | 0x8000_0000,
(libc::GRND_RANDOM | libc::GRND_INSECURE) as usize,
] {
assert_eq!(validate_getrandom_flags(flags), Err(Errno::EINVAL));
}
}
#[test]
fn getrandom_caps_requests_at_linux_max_rw_count() {
assert_eq!(getrandom_request_len(16), 16);
assert_eq!(getrandom_request_len(usize::MAX), GETRANDOM_MAX_BYTES);
}
#[test]
fn canonical_random_device_stream_matches_kvm_root_contract() {
let first: Vec<_> = (0..8)
.map(|index| canonical_random_device_byte(0, index))
.collect();
assert_eq!(first, [41, 114, 187, 4, 77, 150, 223, 40]);
let continued: Vec<_> = (8..16)
.map(|index| canonical_random_device_byte(0, index))
.collect();
assert_eq!(continued, [113, 186, 3, 76, 149, 222, 39, 112]);
let seeded: Vec<_> = (0..16)
.map(|index| canonical_random_device_byte(17, index))
.collect();
assert_eq!(
seeded,
[
56, 114, 187, 4, 77, 150, 223, 40, 96, 186, 3, 76, 149, 222, 39, 112
]
);
assert_ne!(seeded, [first, continued].concat());
}
#[test]
fn getpriority_reports_default_nice_for_every_target() {
for which in [libc::PRIO_PROCESS, libc::PRIO_PGRP, libc::PRIO_USER] {
assert_eq!(getpriority_result(which as i32), Ok(20));
}
}
#[test]
fn setpriority_accepts_any_change_for_valid_target() {
for which in [libc::PRIO_PROCESS, libc::PRIO_PGRP, libc::PRIO_USER] {
assert_eq!(setpriority_result(which as i32), Ok(0));
}
}
#[test]
fn get_and_set_priority_reject_unknown_which_with_einval() {
for which in [-1, 3, 42] {
assert_eq!(getpriority_result(which), Err(Errno::EINVAL));
assert_eq!(setpriority_result(which), Err(Errno::EINVAL));
}
}
#[test]
fn seccomp_tsync_null_probe_matches_linux_validation() {
assert_eq!(
seccomp_result(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, false,),
Err(Errno::EFAULT)
);
assert_eq!(
seccomp_result(SECCOMP_SET_MODE_FILTER, 1 << 31, false),
Err(Errno::EINVAL)
);
assert_eq!(
seccomp_result(SECCOMP_SET_MODE_FILTER, 0, true),
Err(Errno::EOPNOTSUPP)
);
}
#[test]
fn process_madvise_is_rejected_deterministically() {
assert!(matches!(
Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(
(-10_000_i32) as usize,
0
),
Err(Error::Errno(Errno::EBADF))
));
assert!(matches!(
Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(3, 1),
Err(Error::Errno(Errno::EINVAL))
));
assert!(matches!(
Detcore::<crate::record_or_replay::NoopTool>::handle_process_madvise(3, 0),
Err(Error::Errno(Errno::EPERM))
));
}
#[test]
fn syslog_exposes_an_empty_log_and_validates_actions() {
for action in 0..=7 {
assert_eq!(deterministic_syslog_result(action, 0), Ok(0));
}
for action in [9, 10] {
assert_eq!(deterministic_syslog_result(action, 0), Ok(0));
}
assert_eq!(deterministic_syslog_result(8, 1), Ok(0));
assert_eq!(deterministic_syslog_result(8, 8), Ok(0));
assert_eq!(deterministic_syslog_result(8, 0), Err(Errno::EINVAL));
assert_eq!(deterministic_syslog_result(8, 9), Err(Errno::EINVAL));
assert_eq!(deterministic_syslog_result(11, 0), Err(Errno::EINVAL));
}
}