#![cfg(any(target_os = "linux", target_os = "android"))]
use minidump_writer::ptrace_dumper::PtraceDumper;
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
use nix::sys::signal::Signal;
use std::convert::TryInto;
use std::io::{BufRead, BufReader};
use std::mem::size_of;
use std::os::unix::process::ExitStatusExt;
mod common;
use common::*;
#[test]
fn test_setup() {
spawn_child("setup", &[]);
}
#[test]
fn test_thread_list_from_child() {
spawn_child("thread_list", &[]);
}
#[test]
fn test_thread_list_from_parent() {
let num_of_threads = 5;
let mut child = start_child_and_wait_for_threads(num_of_threads);
let pid = child.id() as i32;
let mut dumper = PtraceDumper::new(pid, minidump_writer::minidump_writer::STOP_TIMEOUT)
.expect("Couldn't init dumper");
assert_eq!(dumper.threads.len(), num_of_threads);
dumper.suspend_threads().expect("Could not suspend threads");
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);
}
dumper.resume_threads().expect("Failed to resume threads");
child.kill().expect("Failed to kill process");
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);
}
#[cfg(not(target_arch = "mips"))]
#[test]
fn test_mappings_include_linux_gate() {
spawn_child("mappings_include_linux_gate", &[]);
}
#[test]
fn test_linux_gate_mapping_id() {
if std::env::var("CI").is_ok() {
println!("disabled on CI, but works locally");
return;
}
spawn_child("linux_gate_mapping_id", &[]);
}
#[test]
fn test_merged_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: &'static str = std::env!("CARGO_BIN_EXE_test");
let file = std::fs::File::open(path).unwrap();
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;
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,
page_size.get().try_into().unwrap(), )
};
spawn_child(
"merged_mappings",
&[path, &format!("{mapped}"), &format!("{map_size}")],
);
}
#[test]
fn test_file_id() {
spawn_child("file_id", &[]);
}
#[test]
fn test_find_mapping() {
spawn_child(
"find_mappings",
&[
&format!("{}", libc::printf as *const () as usize),
&format!("{}", String::new as *const () as usize),
],
);
}
#[test]
fn test_copy_from_process_self() {
if std::env::var("CI").is_ok() {
println!("disabled on CI, but works locally");
return;
}
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),
],
);
}
#[test]
fn test_sanitize_stack_copy() {
let num_of_threads = 1;
let mut child = start_child_and_return(&["spawn_alloc_wait"]);
let pid = child.id() as i32;
let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout"));
let mut buf = String::new();
let _ = f
.read_line(&mut buf)
.expect("Couldn't read address provided by child");
let mut 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");
let mut dumper = PtraceDumper::new(pid, minidump_writer::minidump_writer::STOP_TIMEOUT)
.expect("Couldn't init dumper");
assert_eq!(dumper.threads.len(), num_of_threads);
dumper.suspend_threads().expect("Could not suspend threads");
let thread_info = dumper
.get_thread_info_by_index(0)
.expect("Couldn't find thread_info");
let defaced;
#[cfg(target_pointer_width = "64")]
{
defaced = 0x0defaced0defacedusize.to_ne_bytes()
}
#[cfg(target_pointer_width = "32")]
{
defaced = 0x0defacedusize.to_ne_bytes()
};
let mut simulated_stack = vec![0xffu8; 2 * size_of::<usize>()];
simulated_stack[size_of::<usize>()..].copy_from_slice(&thread_info.stack_pointer.to_ne_bytes());
dumper
.sanitize_stack_copy(
&mut simulated_stack,
thread_info.stack_pointer,
size_of::<usize>(),
)
.expect("Could not sanitize stack");
assert!(simulated_stack[size_of::<usize>()..] != defaced);
assert_eq!(
&simulated_stack[0..size_of::<usize>()],
vec![0u8; size_of::<usize>()].as_slice()
);
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);
}
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());
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);
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);
simulated_stack = vec![0u8; 2 * size_of::<usize>()];
simulated_stack[0..size_of::<usize>()].copy_from_slice(&heap_addr.to_ne_bytes());
dumper
.sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0)
.expect("Failed to sanitize with heap addr");
assert_eq!(simulated_stack[0..size_of::<usize>()], defaced);
dumper.resume_threads().expect("Failed to resume threads");
child.kill().expect("Failed to kill process");
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);
}