use std::{
io::{Seek, Write},
time::Instant,
};
use libseccomp::ScmpArch;
use nix::errno::Errno;
use crate::{
config::*,
cookie::safe_memfd_create,
fd::{seal_memfd_all, SafeOwnedFd},
rng::{fillrandom_pod, randint},
};
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct sysinfo32 {
uptime: i32,
loads: [u32; 3],
totalram: u32,
freeram: u32,
sharedram: u32,
bufferram: u32,
totalswap: u32,
freeswap: u32,
procs: u16,
pad: u16,
totalhigh: u32,
freehigh: u32,
mem_unit: u32,
_f: [u8; 8],
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct sysinfo64 {
uptime: i64,
loads: [u64; 3],
totalram: u64,
freeram: u64,
sharedram: u64,
bufferram: u64,
totalswap: u64,
freeswap: u64,
procs: u16,
pad: u16,
totalhigh: u64,
freehigh: u64,
mem_unit: u32,
_f: [u8; 0],
}
pub(crate) enum SysInfo {
S32(sysinfo32),
S64(sysinfo64),
}
macro_rules! init_sysinfo {
($info:ident, $U:ty, $S:ty) => {{
fillrandom_pod(&mut $info)?;
$info.mem_unit = 1;
$info.totalhigh = 0 as $U;
$info.freehigh = 0 as $U;
$info.totalswap = 0 as $U;
$info.freeswap = 0 as $U;
const MIN_RAM: $U = 0x0080_0000 as $U; const MAX_RAM: $U = 0xFFFF_FFFF as $U;
let mut totalram: $U = $info.totalram % (MAX_RAM - MIN_RAM + 1 as $U) + MIN_RAM;
totalram = if totalram.is_power_of_two() {
totalram
} else {
totalram.checked_next_power_of_two().unwrap_or(MAX_RAM) >> 1
};
totalram = totalram.clamp(MIN_RAM, MAX_RAM);
$info.totalram = totalram;
let mut freeram: $U = $info.freeram % ($info.totalram + 1 as $U);
freeram = if freeram.is_power_of_two() {
freeram
} else {
freeram
.checked_next_power_of_two()
.unwrap_or($info.totalram)
>> 1
};
$info.freeram = freeram.min($info.totalram);
let mut sharedram: $U = $info.sharedram % ($info.totalram + 1 as $U);
sharedram = if sharedram.is_power_of_two() {
sharedram
} else {
sharedram
.checked_next_power_of_two()
.unwrap_or($info.totalram)
>> 1
};
$info.sharedram = sharedram.min($info.totalram);
let mut bufferram: $U = $info.bufferram % ($info.totalram + 1 as $U);
bufferram = if bufferram.is_power_of_two() {
bufferram
} else {
bufferram
.checked_next_power_of_two()
.unwrap_or($info.totalram)
>> 1
};
$info.bufferram = bufferram.min($info.totalram);
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
{
$info.uptime = RAND_TIMER().uptime() as $S;
}
const LOAD_SCALE: $U = 0x1_0000 as $U;
const MAX_LOAD: $U = (LOAD_SCALE * 16) as $U;
for ld in &mut $info.loads {
*ld %= MAX_LOAD + 1 as $U;
}
const MIN_PROCS: u16 = 2;
const MAX_PROCS: u16 = 0x8000;
$info.procs = ($info.procs % (MAX_PROCS - MIN_PROCS + 1)) + MIN_PROCS;
Ok::<(), Errno>(())
}};
}
impl SysInfo {
#[inline]
pub fn new(arch: ScmpArch) -> Result<Self, Errno> {
match arch {
ScmpArch::X8664
| ScmpArch::X32 | ScmpArch::Aarch64
| ScmpArch::Mips64
| ScmpArch::Mipsel64
| ScmpArch::Parisc64
| ScmpArch::Ppc64
| ScmpArch::Ppc64Le
| ScmpArch::Riscv64
| ScmpArch::S390X
| ScmpArch::Loongarch64 => Self::new64(),
ScmpArch::X86
| ScmpArch::Arm
| ScmpArch::M68k
| ScmpArch::Mips
| ScmpArch::Mipsel
| ScmpArch::Mips64N32 | ScmpArch::Mipsel64N32 | ScmpArch::Ppc
| ScmpArch::Parisc
| ScmpArch::Sh
| ScmpArch::Sheb
| ScmpArch::S390 => Self::new32(),
_ => Err(Errno::ENOSYS),
}
}
pub(crate) fn new32() -> Result<Self, Errno> {
let mut info: sysinfo32 = unsafe { std::mem::zeroed() };
init_sysinfo!(info, u32, i32)?;
Ok(SysInfo::S32(info))
}
pub fn new64() -> Result<Self, Errno> {
let mut info: sysinfo64 = unsafe { std::mem::zeroed() };
init_sysinfo!(info, u64, i64)?;
Ok(SysInfo::S64(info))
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::S32(s) => unsafe {
std::slice::from_raw_parts((s as *const _) as *const u8, size_of::<sysinfo32>())
},
Self::S64(s) => unsafe {
std::slice::from_raw_parts((s as *const _) as *const u8, size_of::<sysinfo64>())
},
}
}
}
pub struct RandTimer {
pub start: Instant,
pub uptime_offset: u64,
pub idle_offset: u64,
}
impl RandTimer {
pub fn new(timens: bool) -> Result<Self, Errno> {
Ok(Self {
start: Instant::now(),
uptime_offset: if timens { 0 } else { randint(1..=0xFF_FFFF)? },
idle_offset: if timens { 0 } else { randint(1..=0xFF_FFFF)? },
})
}
pub fn uptime(&self) -> u64 {
let elapsed = self.start.elapsed().as_secs();
elapsed.wrapping_add(self.uptime_offset)
}
pub fn idle(&self) -> u64 {
let elapsed = self.start.elapsed().as_secs();
elapsed.wrapping_add(self.idle_offset)
}
pub fn proc(&self) -> String {
let elapsed = self.start.elapsed().as_secs();
format!(
"{}.{:02} {}.{:02}\n",
elapsed.wrapping_add(self.uptime_offset),
self.uptime_offset % 100,
elapsed.wrapping_add(self.idle_offset),
self.idle_offset % 100
)
}
pub fn proc_fd(&self) -> Result<SafeOwnedFd, Errno> {
let repr = self.proc();
let data = repr.as_bytes();
let mut fd = safe_memfd_create(c"syd/proc/uptime", *SAFE_MFD_FLAGS)?;
fd.write_all(data).or(Err(Errno::EIO))?;
fd.rewind().or(Err(Errno::EIO))?;
seal_memfd_all(&fd)?;
Ok(fd)
}
}
#[cfg(test)]
mod tests {
use std::{thread, time::Duration};
use super::RandTimer;
#[test]
fn test_rand_timer_1() {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
let _ = rt.uptime();
let _ = rt.idle();
}
#[test]
fn test_rand_timer_2() {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
let before_uptime = rt.uptime();
let before_idle = rt.idle();
thread::sleep(Duration::from_millis(10));
let after_uptime = rt.uptime();
let after_idle = rt.idle();
assert!(
after_uptime >= before_uptime,
"Uptime decreased from {} to {}",
before_uptime,
after_uptime
);
assert!(
after_idle >= before_idle,
"Idle time decreased from {} to {}",
before_idle,
after_idle
);
}
#[test]
fn test_rand_timer_3() {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
for _ in 0..10_000 {
let _ = rt.uptime();
let _ = rt.idle();
}
}
#[test]
fn test_rand_timer_4() {
for _ in 0..1000 {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
assert_ne!(
rt.uptime(),
0,
"Uptime offset might be zero too often, suspicious RNG."
);
assert_ne!(
rt.idle(),
0,
"Idle offset might be zero too often, suspicious RNG."
);
}
}
#[test]
fn test_rand_timer_5() {
let threads = 8;
let iterations = 2000;
let mut handles = Vec::new();
for _ in 0..threads {
handles.push(thread::spawn(move || {
for _ in 0..iterations {
let rt = RandTimer::new(false).unwrap();
let _ = rt.uptime();
let _ = rt.idle();
}
}));
}
for handle in handles {
handle.join().expect("Thread panic in concurrency test");
}
}
#[test]
fn test_rand_timer_6() {
let iterations = 30;
let mut offsets = Vec::new();
for _ in 0..iterations {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
let elapsed = rt.start.elapsed().as_secs();
let offset_guess = rt.uptime().wrapping_sub(elapsed);
offsets.push(offset_guess);
}
let all_same = offsets.windows(2).all(|w| w[0] == w[1]);
assert!(
!all_same,
"All uptime offsets identical over {} RandTimer creations, suspicious RNG!",
iterations
);
}
#[test]
fn test_rand_timer_7() {
let iterations = 30;
let mut offsets = Vec::new();
for _ in 0..iterations {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
let elapsed = rt.start.elapsed().as_secs();
let offset_guess = rt.idle().wrapping_sub(elapsed);
offsets.push(offset_guess);
}
let all_same = offsets.windows(2).all(|w| w[0] == w[1]);
assert!(!all_same, "All idle offsets identical, suspicious RNG!");
}
#[test]
fn test_rand_timer_8() {
let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
rt.uptime_offset = u64::MAX - 1;
let before = rt.uptime();
thread::sleep(Duration::from_secs(1));
let after = rt.uptime();
assert!(
after != before,
"No change in uptime after forcing near-max offset + 1s sleep!"
);
}
#[test]
fn test_rand_timer_9() {
let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
rt.idle_offset = u64::MAX - 1;
let before = rt.idle();
thread::sleep(Duration::from_secs(1));
let after = rt.idle();
assert!(
after != before,
"No change in idle time after forcing near-max offset + 1s sleep!"
);
}
#[test]
fn test_rand_timer_10() {
let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
rt.uptime_offset = 0;
rt.idle_offset = 0;
let t1_up = rt.uptime();
let t1_idle = rt.idle();
thread::sleep(Duration::from_millis(5));
let t2_up = rt.uptime();
let t2_idle = rt.idle();
assert!(
t2_up >= t1_up,
"Uptime decreased with zero offset: {} to {}",
t1_up,
t2_up
);
assert!(
t2_idle >= t1_idle,
"Idle decreased with zero offset: {} to {}",
t1_idle,
t2_idle
);
}
#[test]
fn test_rand_timer_11() {
let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
rt.idle_offset = u64::MAX / 2;
let before = rt.idle();
thread::sleep(Duration::from_secs(1));
let after = rt.idle();
assert_ne!(
before, after,
"Idle unchanged after short sleep with large offset!"
);
}
#[test]
fn test_rand_timer_12() {
for i in 0..10_000 {
let rt = RandTimer::new(false).expect("RandTimer creation failed");
if i % 1000 == 0 {
let _ = rt.uptime();
let _ = rt.idle();
}
}
}
#[test]
fn test_rand_timer_13() {
let test_offsets = [
(1, 1),
(42, 999_999_999),
(0x0000FFFF_FFFFFFFF, 0xFFFFAAAA_FFFFFFFF),
(0xFFFFFFFF_FFFFFFFF, 0x55555555_55555555),
];
for &(u_off, i_off) in &test_offsets {
let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
rt.uptime_offset = u_off;
rt.idle_offset = i_off;
let up1 = rt.uptime();
let idle1 = rt.idle();
thread::sleep(Duration::from_millis(2));
let up2 = rt.uptime();
let idle2 = rt.idle();
assert!(
up2 >= up1,
"Uptime offset {} yields invalid progression: {} -> {}",
u_off,
up1,
up2
);
assert!(
idle2 >= idle1,
"Idle offset {} yields invalid progression: {} -> {}",
i_off,
idle1,
idle2
);
}
}
}