/// These tests generally aren't consistent in resource-deprived environments like CI runners and /// android emulators.
macro_rules! disabled_on_ci_and_android {
() => { if std::env::var("CI").is_ok() || cfg!(target_os = "android") {
println!("disabled on CI and android, but otherwise works locally"); return;
}
};
}
#[test] fn thread_list_from_child() { // Child spawns and looks in the parent (== this process) for its own thread-ID
let (tx, rx) = std::sync::mpsc::sync_channel(1);
// We also spawn another thread that we send a SIGHUP to to ensure that the // ptracedumper correctly handles it let _thread = std::thread::Builder::new()
.name("sighup-thread".into())
.spawn(move || {
tx.send(unsafe { libc::pthread_self() as usize }).unwrap(); loop {
std::thread::sleep(std::time::Duration::from_secs(1));
}
})
.unwrap();
let thread_id = rx.recv().unwrap();
// Unfortunately we need to set a signal handler to ignore the SIGHUP we send // to the thread, as otherwise the default test harness fails unsafe { letmut act: libc::sigaction = std::mem::zeroed(); if libc::sigemptyset(&mut act.sa_mask) != 0 {
eprintln!( "unable to clear action mask: {:?}",
std::io::Error::last_os_error()
); return;
}
act.sa_flags = libc::SA_SIGINFO;
act.sa_sigaction = on_sig as *const () as _;
// Register the action with the signal handler if libc::sigaction(libc::SIGHUP, &act, std::ptr::null_mut()) != 0 {
eprintln!( "unable to register signal handler: {:?}",
std::io::Error::last_os_error()
);
}
}
unsafe {
libc::pthread_kill(thread_id as _, libc::SIGHUP);
}
spawn_child("thread_list", &[]);
}
#[test] fn thread_list_from_parent() { let num_of_threads = 5; letmut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32;
// let mut matching_threads = 0; for (idx, curr_thread) in dumper.threads.iter().enumerate() {
println!("curr_thread: {curr_thread:?}"); let info = dumper
.get_thread_info_by_index(idx)
.expect("Could not get thread info by index"); let (_valid_stack_ptr, stack_len) = dumper
.get_stack_info(info.stack_pointer)
.expect("Could not get stack_pointer");
assert!(stack_len > 0);
// TODO: I currently know of no way to write the thread_id into the registers using Rust, // so this check is deactivated for now, because it always fails /* // In the helper program, we stored a pointer to the thread id in a // specific register. Check that we can recover its value. #[cfg(target_arch="x86_64")] letprocess_tid_location=info.regs.rcx; #[cfg(target_arch="x86")] letprocess_tid_location=info.regs.ecx; #[cfg(target_arch="arm")] letprocess_tid_location=info.regs.uregs[3]; #[cfg(target_arch="aarch64")] letprocess_tid_location=info.regs.regs[3];
// Reap child let waitres = child.wait().expect("Failed to wait for child"); let status = waitres.signal().expect("Child did not die due to signal");
assert_eq!(waitres.code(), None);
assert_eq!(status, Signal::SIGKILL as i32);
// We clean up the child process before checking the final result // TODO: I currently know of no way to write the thread_id into the registers using Rust, // so this check is deactivated for now, because it always fails // assert_eq!(matching_threads, num_of_threads);
}
#[cfg(not(target_os = "android"))] #[test] // Ensure that the linux-gate VDSO is included in the mapping list. fn mappings_include_linux_gate() {
spawn_child("mappings_include_linux_gate", &[]);
}
#[test] fn merges_mappings() { let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); let page_size = std::num::NonZeroUsize::new(page_size.unwrap() as usize).unwrap(); let map_size = std::num::NonZeroUsize::new(3 * page_size.get()).unwrap();
let path: String = iflet Ok(p) = std::env::var("TEST_HELPER") {
p
} else {
std::env!("CARGO_BIN_EXE_test").into()
}; let file = std::fs::File::open(&path).unwrap();
// mmap two segments out of the helper binary, one // enclosed in the other, but with different protections. let mapped_mem = unsafe {
mmap(
None,
map_size,
ProtFlags::PROT_READ,
MapFlags::MAP_SHARED,
&file, 0,
)
.unwrap()
};
let mapped = mapped_mem.as_ptr() as usize;
// Carve a page out of the first mapping with different permissions. let _inside_mapping = unsafe {
mmap(
std::num::NonZeroUsize::new(mapped + 2 * page_size.get()),
page_size,
ProtFlags::PROT_NONE,
MapFlags::MAP_SHARED | MapFlags::MAP_FIXED,
&file, // Map a different offset just to // better test real-world conditions.
page_size.get().try_into().unwrap(), // try_into() in order to work for 32 and 64 bit
)
};
// Ensure that the linux-gate VDSO is included in the mapping list. #[test] fn file_id() {
disabled_on_ci_and_android!();
spawn_child("file_id", &[]);
}
#[cfg(not(target_os = "android"))] #[test] fn finds_mappings() {
spawn_child( "find_mappings",
&[
&format!("{}", libc::printf as *const () as usize),
&format!("{}", String::new as *const () as usize),
],
);
}
let stack_var: libc::c_long = 0x11223344; let heap_var: Box<libc::c_long> = Box::new(0x55667788);
spawn_child( "copy_from_process",
&[
&format!("{}", &stack_var as *const libc::c_long as usize),
&format!("{}", heap_var.as_ref() as *const libc::c_long as usize),
],
);
}
// Ensures that we sanitize the stack properly #[test] fn sanitizes_stack_copies() { let num_of_threads = 1; letmut child = start_child_and_return(&["spawn_alloc_wait"]); let pid = child.id() as i32;
letmut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); letmut buf = String::new(); let _ = f
.read_line(&mut buf)
.expect("Couldn't read address provided by child"); letmut output = buf.split_whitespace(); let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16)
.expect("unable to parse mmap_addr");
assert!(simulated_stack[size_of::<usize>()..] != defaced); // Memory prior to the stack pointer should be cleared.
assert_eq!(
&simulated_stack[0..size_of::<usize>()],
vec![0u8; size_of::<usize>()].as_slice()
);
// Small integers should not be sanitized. for ii in -4096..=4096isize {
simulated_stack = vec![0u8; 2 * size_of::<usize>()];
simulated_stack[0..size_of::<usize>()].copy_from_slice(&(ii as usize).to_ne_bytes());
dumper
.sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0)
.expect("Failed to sanitize with small integers");
assert!(simulated_stack[size_of::<usize>()..] != defaced);
}
// The instruction pointer definitely should point into an executable mapping. let instr_ptr = thread_info.get_instruction_pointer(); let mapping_info = dumper
.find_mapping_no_bias(instr_ptr)
.expect("Failed to find mapping info");
assert!(mapping_info.is_executable());
// Pointers to code shouldn't be sanitized.
simulated_stack = vec![0u8; 2 * size_of::<usize>()];
simulated_stack[size_of::<usize>()..].copy_from_slice(&instr_ptr.to_ne_bytes());
dumper
.sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0)
.expect("Failed to sanitize with instr_ptr");
assert!(simulated_stack[0..size_of::<usize>()] != defaced);
assert!(simulated_stack[size_of::<usize>()..] != defaced);
// String fragments should be sanitized. let junk = "abcdefghijklmnop".as_bytes();
simulated_stack.copy_from_slice(&junk[0..2 * size_of::<usize>()]);
dumper
.sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0)
.expect("Failed to sanitize with junk");
assert_eq!(simulated_stack[0..size_of::<usize>()], defaced);
assert_eq!(simulated_stack[size_of::<usize>()..], defaced);
// Heap pointers should be sanititzed.
// NOTE: The original test used the heap-address below, but here thread_info.regs.rcx // is the instruction pointer, and thus in direct conflict with the "instruction pointer" // testcase. // Instead we just allocate something on the heap in the child and pass that address to this test. // #[cfg(target_arch = "x86_64")] // let heap_addr = thread_info.regs.rcx as usize; // #[cfg(target_arch = "x86")] // let heap_addr = thread_info.regs.ecx as usize; // #[cfg(target_arch = "arm")] // let heap_addr = thread_info.regs.uregs[3] as usize; // #[cfg(target_arch = "aarch64")] // let heap_addr = thread_info.regs.regs[3] as usize;
// Reap child let waitres = child.wait().expect("Failed to wait for child"); let status = waitres.signal().expect("Child did not die due to signal");
assert_eq!(waitres.code(), None);
assert_eq!(status, Signal::SIGKILL as i32);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-08-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.