use std::collections::HashMap;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use crate::error::SandlockError;
pub(crate) fn find_vdso_range(pid: i32) -> io::Result<(u64, u64)> {
let path = format!("/proc/{}/maps", pid);
let file = File::open(&path)?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
if line.ends_with("[vdso]") {
let space = line.find(' ').unwrap_or(line.len());
let range = &line[..space];
if let Some(dash_pos) = range.find('-') {
let start = u64::from_str_radix(&range[..dash_pos], 16).map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("bad vDSO start: {}", e))
})?;
let end = u64::from_str_radix(&range[dash_pos + 1..], 16).map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("bad vDSO end: {}", e))
})?;
return Ok((start, end - start));
}
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"vDSO mapping not found",
))
}
pub(crate) fn find_vdso_base(pid: i32) -> io::Result<u64> {
find_vdso_range(pid).map(|(base, _)| base)
}
fn read_proc_mem(pid: i32, addr: u64, len: usize) -> io::Result<Vec<u8>> {
let mut file = File::open(format!("/proc/{}/mem", pid))?;
file.seek(SeekFrom::Start(addr))?;
let mut buf = vec![0u8; len];
file.read_exact(&mut buf)?;
Ok(buf)
}
fn parse_vdso_symbols(vdso_bytes: &[u8]) -> HashMap<String, u64> {
let mut symbols = HashMap::new();
if let Ok(elf) = goblin::elf::Elf::parse(vdso_bytes) {
for sym in elf.dynsyms.iter() {
if sym.st_value != 0 {
if let Some(name) = elf.dynstrtab.get_at(sym.st_name) {
if !name.is_empty() {
symbols.insert(name.to_string(), sym.st_value);
}
}
}
}
}
symbols
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
fn push_insn(stub: &mut Vec<u8>, insn: u32) {
stub.extend_from_slice(&insn.to_le_bytes());
}
#[cfg(target_arch = "aarch64")]
fn arm64_b_insn(from: u64, to: u64) -> Result<u32, SandlockError> {
let delta = to as i64 - from as i64;
if delta % 4 != 0 {
return Err(SandlockError::MemoryProtect(format!(
"arm64 B target {:#x} not 4-byte aligned from {:#x}",
to, from
)));
}
let offset = delta / 4;
if !(-(1i64 << 25)..(1i64 << 25)).contains(&offset) {
return Err(SandlockError::MemoryProtect(format!(
"arm64 B {:#x}->{:#x} out of ±128 MiB range",
from, to
)));
}
Ok(0x14000000u32 | ((offset as u32) & 0x03FF_FFFF))
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
fn vdso_tramp_start(vdso_bytes: &[u8]) -> Option<u64> {
let elf = goblin::elf::Elf::parse(vdso_bytes).ok()?;
let highest_end = elf
.dynsyms
.iter()
.filter(|s| s.st_value != 0)
.map(|s| s.st_value + s.st_size)
.max()?;
Some((highest_end + 15) & !15)
}
#[cfg(target_arch = "aarch64")]
fn movz_x(reg: u32, imm16: u16, shift: u32) -> u32 {
0xD280_0000 | (((shift / 16) & 0x3) << 21) | ((imm16 as u32) << 5) | reg
}
#[cfg(target_arch = "aarch64")]
fn movk_x(reg: u32, imm16: u16, shift: u32) -> u32 {
0xF280_0000 | (((shift / 16) & 0x3) << 21) | ((imm16 as u32) << 5) | reg
}
#[cfg(target_arch = "aarch64")]
fn load_imm64(stub: &mut Vec<u8>, reg: u32, value: u64) {
push_insn(stub, movz_x(reg, (value & 0xffff) as u16, 0));
push_insn(stub, movk_x(reg, ((value >> 16) & 0xffff) as u16, 16));
push_insn(stub, movk_x(reg, ((value >> 32) & 0xffff) as u16, 32));
push_insn(stub, movk_x(reg, ((value >> 48) & 0xffff) as u16, 48));
}
#[cfg(target_arch = "x86_64")]
fn simple_stub(syscall_nr: u32) -> Vec<u8> {
let mut stub = Vec::new();
stub.push(0xB8); stub.extend_from_slice(&syscall_nr.to_le_bytes()); stub.extend_from_slice(&[0x0F, 0x05]); stub.push(0xC3); stub }
#[cfg(target_arch = "aarch64")]
fn simple_stub(syscall_nr: u32) -> Vec<u8> {
let mut stub = Vec::new();
push_insn(&mut stub, movz_x(8, syscall_nr as u16, 0)); push_insn(&mut stub, 0xD400_0001); push_insn(&mut stub, 0xD65F_03C0); stub
}
#[cfg(target_arch = "x86_64")]
fn offset_stub_clock_gettime(offset_secs: i64) -> Vec<u8> {
let mut stub = Vec::new();
stub.push(0x57); stub.push(0x56); stub.extend_from_slice(&[0xB8, 0xE4, 0x00, 0x00, 0x00]); stub.extend_from_slice(&[0x0F, 0x05]); stub.push(0x5E); stub.push(0x5F); stub.extend_from_slice(&[0x83, 0xFF, 0x00]); stub.push(0x74); let jump_to_movabs: u8 = 3 + 2;
stub.push(jump_to_movabs);
stub.extend_from_slice(&[0x83, 0xFF, 0x05]); stub.push(0x75); let jump_to_ret: u8 = 10 + 3;
stub.push(jump_to_ret);
stub.extend_from_slice(&[0x48, 0xB9]); stub.extend_from_slice(&offset_secs.to_le_bytes()); stub.extend_from_slice(&[0x48, 0x01, 0x0E]); stub.push(0xC3); stub
}
#[cfg(target_arch = "aarch64")]
fn offset_stub_clock_gettime(offset_secs: i64) -> Vec<u8> {
let mut stub = Vec::new();
push_insn(&mut stub, 0xAA00_03E9); push_insn(&mut stub, 0xAA01_03EA); push_insn(&mut stub, movz_x(8, libc::SYS_clock_gettime as u16, 0));
push_insn(&mut stub, 0xD400_0001); push_insn(&mut stub, 0x7100_013F); push_insn(&mut stub, 0x5400_0060); push_insn(&mut stub, 0x7100_153F); push_insn(&mut stub, 0x5400_0101); load_imm64(&mut stub, 11, offset_secs as u64); push_insn(&mut stub, 0xF940_014C); push_insn(&mut stub, 0x8B0B_018C); push_insn(&mut stub, 0xF900_014C); push_insn(&mut stub, 0xD65F_03C0); stub
}
#[cfg(target_arch = "x86_64")]
fn offset_stub_gettimeofday(offset_secs: i64) -> Vec<u8> {
let mut stub = Vec::new();
stub.extend_from_slice(&[0x57, 0x56]); stub.extend_from_slice(&[0xB8, 0x60, 0x00, 0x00, 0x00]); stub.extend_from_slice(&[0x0F, 0x05]); stub.extend_from_slice(&[0x5E, 0x5F]); stub.extend_from_slice(&[0x48, 0x85, 0xFF]); stub.extend_from_slice(&[0x74, 0x0D]); stub.extend_from_slice(&[0x48, 0xB9]); stub.extend_from_slice(&offset_secs.to_le_bytes());
stub.extend_from_slice(&[0x48, 0x01, 0x0F]); stub.push(0xC3); stub
}
#[cfg(target_arch = "aarch64")]
fn offset_stub_gettimeofday(offset_secs: i64) -> Vec<u8> {
let mut stub = Vec::new();
push_insn(&mut stub, 0xAA00_03EA); push_insn(&mut stub, movz_x(8, libc::SYS_gettimeofday as u16, 0));
push_insn(&mut stub, 0xD400_0001); push_insn(&mut stub, 0xB400_010A); load_imm64(&mut stub, 11, offset_secs as u64); push_insn(&mut stub, 0xF940_014C); push_insn(&mut stub, 0x8B0B_018C); push_insn(&mut stub, 0xF900_014C); push_insn(&mut stub, 0xD65F_03C0); stub
}
#[cfg(target_arch = "x86_64")]
fn vdso_targets() -> Vec<(&'static str, &'static str, u32)> {
vec![
("clock_gettime", "__vdso_clock_gettime", libc::SYS_clock_gettime as u32),
("gettimeofday", "__vdso_gettimeofday", libc::SYS_gettimeofday as u32),
("time", "__vdso_time", libc::SYS_time as u32),
]
}
#[cfg(target_arch = "aarch64")]
fn vdso_targets() -> Vec<(&'static str, &'static str, u32)> {
vec![
("clock_gettime", "__kernel_clock_gettime", libc::SYS_clock_gettime as u32),
("gettimeofday", "__kernel_gettimeofday", libc::SYS_gettimeofday as u32),
]
}
#[cfg(target_arch = "riscv64")]
fn riscv_li_a7(stub: &mut Vec<u8>, value: u32) {
const A7: u32 = 17;
if value < 2048 {
push_insn(stub, (value << 20) | (A7 << 7) | 0x13);
} else {
let lo12 = value & 0xfff;
let hi20 = if lo12 & 0x800 != 0 {
(value >> 12).wrapping_add(1) & 0xf_ffff
} else {
(value >> 12) & 0xf_ffff
};
push_insn(stub, (hi20 << 12) | (A7 << 7) | 0x37); push_insn(stub, ((lo12 & 0xfff) << 20) | (A7 << 15) | (A7 << 7) | 0x1b); }
}
#[cfg(target_arch = "riscv64")]
fn riscv_j_insn(from: u64, to: u64) -> Result<u32, SandlockError> {
let delta = to as i64 - from as i64;
if delta % 2 != 0 {
return Err(SandlockError::MemoryProtect(format!(
"riscv64 J target {:#x} not 2-byte aligned from {:#x}",
to, from
)));
}
if !(-(1i64 << 20)..(1i64 << 20)).contains(&delta) {
return Err(SandlockError::MemoryProtect(format!(
"riscv64 J {:#x}->{:#x} out of ±1 MiB range",
from, to
)));
}
let imm = delta as u32;
let b20 = (imm >> 20) & 0x1;
let b10_1 = (imm >> 1) & 0x3ff;
let b11 = (imm >> 11) & 0x1;
let b19_12 = (imm >> 12) & 0xff;
Ok((b20 << 31) | (b10_1 << 21) | (b11 << 20) | (b19_12 << 12) | 0x6f)
}
#[cfg(target_arch = "riscv64")]
mod rv {
pub const X0: u32 = 0;
pub const T0: u32 = 5;
pub const T1: u32 = 6;
pub const T2: u32 = 7;
pub const A0: u32 = 10;
pub const A1: u32 = 11;
pub const A7: u32 = 17;
pub const T3: u32 = 28;
pub const T4: u32 = 29;
pub const T5: u32 = 30;
pub const T6: u32 = 31;
pub const ECALL: u32 = 0x0000_0073;
pub const RET: u32 = 0x0000_8067;
pub fn addi(rd: u32, rs1: u32, imm: i32) -> u32 {
((imm as u32 & 0xfff) << 20) | (rs1 << 15) | (rd << 7) | 0x13
}
pub fn mv(rd: u32, rs: u32) -> u32 {
addi(rd, rs, 0)
}
pub fn auipc(rd: u32, imm20: u32) -> u32 {
(imm20 << 12) | (rd << 7) | 0x17
}
pub fn lwu(rd: u32, rs1: u32, imm: i32) -> u32 {
((imm as u32 & 0xfff) << 20) | (rs1 << 15) | (6 << 12) | (rd << 7) | 0x03
}
pub fn lw(rd: u32, rs1: u32, imm: i32) -> u32 {
((imm as u32 & 0xfff) << 20) | (rs1 << 15) | (2 << 12) | (rd << 7) | 0x03
}
pub fn ld(rd: u32, rs1: u32, imm: i32) -> u32 {
((imm as u32 & 0xfff) << 20) | (rs1 << 15) | (3 << 12) | (rd << 7) | 0x03
}
pub fn sd(rs2: u32, rs1: u32, imm: i32) -> u32 {
let i = imm as u32;
((i >> 5 & 0x7f) << 25) | (rs2 << 20) | (rs1 << 15) | (3 << 12) | ((i & 0x1f) << 7) | 0x23
}
pub fn slli(rd: u32, rs1: u32, shamt: u32) -> u32 {
((shamt & 0x3f) << 20) | (rs1 << 15) | (1 << 12) | (rd << 7) | 0x13
}
pub fn or(rd: u32, rs1: u32, rs2: u32) -> u32 {
(rs2 << 20) | (rs1 << 15) | (6 << 12) | (rd << 7) | 0x33
}
pub fn add(rd: u32, rs1: u32, rs2: u32) -> u32 {
(rs2 << 20) | (rs1 << 15) | (rd << 7) | 0x33
}
pub fn beq(rs1: u32, rs2: u32, imm: i32) -> u32 {
branch(0, rs1, rs2, imm)
}
pub fn bne(rs1: u32, rs2: u32, imm: i32) -> u32 {
branch(1, rs1, rs2, imm)
}
fn branch(funct3: u32, rs1: u32, rs2: u32, imm: i32) -> u32 {
let i = imm as u32;
((i >> 12 & 1) << 31)
| ((i >> 5 & 0x3f) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (funct3 << 12)
| ((i >> 1 & 0xf) << 8)
| ((i >> 11 & 1) << 7)
| 0x63
}
}
#[cfg(target_arch = "riscv64")]
fn simple_stub(syscall_nr: u32) -> Vec<u8> {
let mut stub = Vec::new();
riscv_li_a7(&mut stub, syscall_nr);
push_insn(&mut stub, 0x0000_0073); push_insn(&mut stub, 0x0000_8067); stub
}
#[cfg(target_arch = "riscv64")]
fn offset_stub_clock_gettime(offset_secs: i64) -> Vec<u8> {
use rv::*;
const DOFF: i32 = 36; let nr = libc::SYS_clock_gettime as i32;
let insns: [u32; 16] = [
mv(T0, A0), mv(T4, A1), addi(A7, X0, nr), ECALL, beq(T0, X0, 12), addi(T1, X0, 5), bne(T0, T1, 36), auipc(T2, 0), lwu(T5, T2, DOFF), lw(T6, T2, DOFF + 4), slli(T6, T6, 32),
or(T2, T5, T6), ld(T3, T4, 0), add(T3, T3, T2), sd(T3, T4, 0), RET, ];
let mut stub = Vec::with_capacity(insns.len() * 4 + 8);
for insn in insns {
push_insn(&mut stub, insn);
}
stub.extend_from_slice(&offset_secs.to_le_bytes());
stub
}
#[cfg(target_arch = "riscv64")]
fn offset_stub_gettimeofday(offset_secs: i64) -> Vec<u8> {
use rv::*;
const DOFF: i32 = 36;
let nr = libc::SYS_gettimeofday as i32;
let insns: [u32; 13] = [
mv(T4, A0), addi(A7, X0, nr), ECALL,
beq(T4, X0, 36), auipc(T2, 0),
lwu(T5, T2, DOFF),
lw(T6, T2, DOFF + 4),
slli(T6, T6, 32),
or(T2, T5, T6),
ld(T3, T4, 0), add(T3, T3, T2),
sd(T3, T4, 0),
RET, ];
let mut stub = Vec::with_capacity(insns.len() * 4 + 8);
for insn in insns {
push_insn(&mut stub, insn);
}
stub.extend_from_slice(&offset_secs.to_le_bytes());
stub
}
#[cfg(target_arch = "riscv64")]
fn vdso_targets() -> Vec<(&'static str, &'static str, u32)> {
vec![
("clock_gettime", "__vdso_clock_gettime", libc::SYS_clock_gettime as u32),
("gettimeofday", "__vdso_gettimeofday", libc::SYS_gettimeofday as u32),
]
}
pub(crate) fn patch(
pid: i32,
time_offset_secs: Option<i64>,
_patch_for_random: bool,
) -> Result<(), SandlockError> {
let (base, mapping_size) = find_vdso_range(pid).map_err(|e| {
SandlockError::MemoryProtect(format!("failed to find vDSO range: {}", e))
})?;
let read_size = std::cmp::min(mapping_size as usize, 0x4000);
let vdso_bytes = read_proc_mem(pid, base, read_size).map_err(|e| {
SandlockError::MemoryProtect(format!("failed to read vDSO memory: {}", e))
})?;
let symbols = parse_vdso_symbols(&vdso_bytes);
let mut mem = OpenOptions::new()
.write(true)
.open(format!("/proc/{}/mem", pid))
.map_err(|e| {
SandlockError::MemoryProtect(format!("failed to open /proc/{}/mem: {}", pid, e))
})?;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
let mut tramp_offset = vdso_tramp_start(&vdso_bytes).unwrap_or(0);
for (name, alt_name, syscall_nr) in vdso_targets() {
if let Some(&offset) = symbols.get(name).or_else(|| symbols.get(alt_name)) {
let entry_addr = base + offset;
let stub = match (time_offset_secs, name) {
(Some(off), "clock_gettime") => offset_stub_clock_gettime(off),
(Some(off), "gettimeofday") => offset_stub_gettimeofday(off),
_ => simple_stub(syscall_nr),
};
#[cfg(target_arch = "x86_64")]
{
mem.seek(SeekFrom::Start(entry_addr)).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to seek to {} at {:#x}: {}",
name, entry_addr, e
))
})?;
mem.write_all(&stub).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to write {} stub at {:#x}: {}",
name, entry_addr, e
))
})?;
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
{
if tramp_offset + stub.len() as u64 > mapping_size {
return Err(SandlockError::MemoryProtect(format!(
"vDSO trampoline area exhausted: need {} bytes at offset {:#x}, mapping ends at {:#x}",
stub.len(), tramp_offset, mapping_size
)));
}
let tramp_addr = base + tramp_offset;
mem.seek(SeekFrom::Start(tramp_addr)).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to seek to {} trampoline at {:#x}: {}",
name, tramp_addr, e
))
})?;
mem.write_all(&stub).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to write {} trampoline at {:#x}: {}",
name, tramp_addr, e
))
})?;
#[cfg(target_arch = "aarch64")]
let b_insn = arm64_b_insn(entry_addr, tramp_addr)?;
#[cfg(target_arch = "riscv64")]
let b_insn = riscv_j_insn(entry_addr, tramp_addr)?;
mem.seek(SeekFrom::Start(entry_addr)).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to seek to {} entry at {:#x}: {}",
name, entry_addr, e
))
})?;
mem.write_all(&b_insn.to_le_bytes()).map_err(|e| {
SandlockError::MemoryProtect(format!(
"failed to write {} branch at {:#x}: {}",
name, entry_addr, e
))
})?;
tramp_offset = (tramp_offset + stub.len() as u64 + 3) & !3;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_vdso_self() {
let base = find_vdso_base(std::process::id() as i32).unwrap();
assert!(base > 0);
}
#[test]
fn test_parse_vdso_symbols_self() {
let pid = std::process::id() as i32;
let base = find_vdso_base(pid).unwrap();
let bytes = read_proc_mem(pid, base, 0x2000).unwrap();
let symbols = parse_vdso_symbols(&bytes);
assert!(
symbols.contains_key("clock_gettime")
|| symbols.contains_key("__vdso_clock_gettime")
|| symbols.contains_key("__kernel_clock_gettime"),
"Expected clock_gettime in vDSO symbols, found: {:?}",
symbols.keys().collect::<Vec<_>>()
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_simple_stub_size() {
let stub = simple_stub(228);
assert_eq!(stub.len(), 8);
assert_eq!(stub[0], 0xB8); }
#[test]
#[cfg(target_arch = "aarch64")]
fn test_simple_stub_size() {
let stub = simple_stub(228);
assert_eq!(stub.len(), 12);
}
#[test]
#[cfg(target_arch = "riscv64")]
fn test_simple_stub_size() {
let stub = simple_stub(228);
assert_eq!(stub.len(), 12);
assert_eq!(&stub[4..8], &0x0000_0073u32.to_le_bytes());
assert_eq!(&stub[8..12], &0x0000_8067u32.to_le_bytes());
}
#[test]
#[cfg(target_arch = "riscv64")]
fn test_offset_stub_riscv_layout() {
let off: i64 = -86400; let cg = offset_stub_clock_gettime(off);
assert_eq!(cg.len(), 72);
assert_eq!(&cg[64..72], &off.to_le_bytes(), "offset stored at tail");
assert_eq!(&cg[12..16], &0x0000_0073u32.to_le_bytes(), "ecall");
assert_eq!(&cg[60..64], &0x0000_8067u32.to_le_bytes(), "ret");
let gtod = offset_stub_gettimeofday(off);
assert_eq!(gtod.len(), 60);
assert_eq!(>od[52..60], &off.to_le_bytes(), "offset stored at tail");
assert_eq!(>od[48..52], &0x0000_8067u32.to_le_bytes(), "ret");
}
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
const EXEC_PAGE: usize = 4096;
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
fn map_executable(code: &[u8]) -> *mut libc::c_void {
use std::ptr;
assert!(code.len() <= EXEC_PAGE);
let page = unsafe {
libc::mmap(
ptr::null_mut(),
EXEC_PAGE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
assert_ne!(page, libc::MAP_FAILED, "mmap exec page");
unsafe {
ptr::copy_nonoverlapping(code.as_ptr(), page as *mut u8, code.len());
assert_eq!(
libc::mprotect(page, EXEC_PAGE, libc::PROT_READ | libc::PROT_EXEC),
0,
"mprotect r-x"
);
}
#[cfg(target_arch = "riscv64")]
unsafe {
std::arch::asm!("fence.i");
}
page
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
fn offset_stub_clock_gettime_executes_and_shifts_realtime() {
const OFFSET: i64 = -86_400; let page = map_executable(&offset_stub_clock_gettime(OFFSET));
let stub_fn: extern "C" fn(libc::clockid_t, *mut libc::timespec) -> libc::c_int =
unsafe { std::mem::transmute(page) };
let mut real = libc::timespec { tv_sec: 0, tv_nsec: 0 };
let mut stubbed = libc::timespec { tv_sec: 0, tv_nsec: 0 };
assert_eq!(unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut real) }, 0);
assert_eq!(stub_fn(libc::CLOCK_REALTIME, &mut stubbed), 0, "stub returns 0");
let shift = real.tv_sec - stubbed.tv_sec; assert!(
(shift - (-OFFSET)).abs() <= 2,
"CLOCK_REALTIME should be shifted by {OFFSET}s, observed real-stub={shift}s"
);
let mut mono_stub = libc::timespec { tv_sec: 0, tv_nsec: 0 };
let mut mono_real = libc::timespec { tv_sec: 0, tv_nsec: 0 };
assert_eq!(stub_fn(libc::CLOCK_MONOTONIC, &mut mono_stub), 0);
assert_eq!(unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut mono_real) }, 0);
assert!(
(mono_real.tv_sec - mono_stub.tv_sec).abs() <= 2,
"CLOCK_MONOTONIC must be unshifted, stub={} real={}",
mono_stub.tv_sec,
mono_real.tv_sec
);
unsafe {
libc::munmap(page, EXEC_PAGE);
}
}
#[test]
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
fn offset_stub_gettimeofday_executes_and_shifts_tv_sec() {
const OFFSET: i64 = -86_400; let page = map_executable(&offset_stub_gettimeofday(OFFSET));
let stub_fn: extern "C" fn(*mut libc::timeval, *mut libc::c_void) -> libc::c_int =
unsafe { std::mem::transmute(page) };
let mut real = libc::timeval { tv_sec: 0, tv_usec: 0 };
let mut stubbed = libc::timeval { tv_sec: 0, tv_usec: 0 };
assert_eq!(unsafe { libc::gettimeofday(&mut real, std::ptr::null_mut()) }, 0);
assert_eq!(stub_fn(&mut stubbed, std::ptr::null_mut()), 0, "stub returns 0");
let shift = real.tv_sec - stubbed.tv_sec; assert!(
(shift - (-OFFSET)).abs() <= 2,
"gettimeofday tv_sec should be shifted by {OFFSET}s, observed real-stub={shift}s"
);
assert_eq!(
stub_fn(std::ptr::null_mut(), std::ptr::null_mut()),
0,
"NULL timeval handled without fault"
);
unsafe {
libc::munmap(page, EXEC_PAGE);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_offset_stub_contains_offset() {
let offset: i64 = -86400; let stub = offset_stub_clock_gettime(offset);
let offset_bytes = offset.to_le_bytes();
assert!(stub.windows(8).any(|w| w == offset_bytes));
}
#[test]
#[cfg(target_arch = "aarch64")]
fn test_offset_stub_contains_offset() {
let offset: i64 = -86400;
let stub = offset_stub_clock_gettime(offset);
let raw = offset as u64;
for shift in 0..4 {
let chunk = ((raw >> (shift * 16)) & 0xFFFF) as u32;
if chunk == 0 {
continue; }
let found = stub.chunks_exact(4).any(|insn| {
let word = u32::from_le_bytes(insn.try_into().unwrap());
((word >> 5) & 0xFFFF) == chunk
});
assert!(found, "chunk {:#06x} for shift {} not encoded in stub", chunk, shift);
}
}
}