From d20215f533ea22df39538cda53f5fc755cf99b17 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 25 Apr 2024 13:40:37 +0200 Subject: [PATCH 01/11] Checkpoint --- Cargo.toml | 1 + src/bin/test.rs | 6 +- src/linux.rs | 2 + src/linux/dso_debug.rs | 31 +--- src/linux/errors.rs | 24 ++- src/linux/mem_reader.rs | 203 +++++++++++++++++++++++ src/linux/minidump_writer.rs | 4 +- src/linux/ptrace_dumper.rs | 91 ++-------- src/linux/sections/app_memory.rs | 2 +- src/linux/sections/thread_list_stream.rs | 4 +- src/linux/thread_info.rs | 4 +- src/linux/thread_info/aarch64.rs | 3 +- src/linux/thread_info/arm.rs | 4 +- src/linux/thread_info/mips.rs | 3 +- src/linux/thread_info/x86.rs | 4 +- tests/linux_minidump_writer.rs | 4 +- 16 files changed, 265 insertions(+), 125 deletions(-) create mode 100644 src/linux/mem_reader.rs diff --git a/Cargo.toml b/Cargo.toml index 5e4212bd..648f4e49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ nix = { version = "0.28", default-features = false, features = [ "process", "ptrace", "signal", + "uio", "user", ] } # Used for parsing procfs info. diff --git a/src/bin/test.rs b/src/bin/test.rs index 82c31d31..aeb13ef0 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -52,7 +52,8 @@ mod linux { let ppid = getppid().as_raw(); let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?; dumper.suspend_threads()?; - let stack_res = PtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?; + let stack_res = + PtraceDumper::copy_from_process(ppid, stack_var, std::mem::size_of::())?; let expected_stack: libc::c_long = 0x11223344; test!( @@ -60,7 +61,8 @@ mod linux { "stack var not correct" )?; - let heap_res = PtraceDumper::copy_from_process(ppid, heap_var as *mut libc::c_void, 1)?; + let heap_res = + PtraceDumper::copy_from_process(ppid, heap_var, std::mem::size_of::())?; let expected_heap: libc::c_long = 0x55667788; test!( heap_res == expected_heap.to_ne_bytes(), diff --git a/src/linux.rs b/src/linux.rs index d1f666d9..febdeabe 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -10,6 +10,7 @@ mod dso_debug; mod dumper_cpu_info; pub mod errors; pub mod maps_reader; +pub mod mem_reader; pub mod minidump_writer; pub mod module_reader; pub mod ptrace_dumper; @@ -17,3 +18,4 @@ pub(crate) mod sections; pub mod thread_info; pub use maps_reader::LINUX_GATE_LIBRARY_NAME; +pub type Pid = i32; diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index c2f873b0..ef27dd5a 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -85,11 +85,7 @@ pub fn write_dso_debug_stream( .get_program_header_address() .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; - let ph = PtraceDumper::copy_from_process( - blamed_thread, - phdr as *mut libc::c_void, - SIZEOF_PHDR * phnum_max, - )?; + let ph = PtraceDumper::copy_from_process(blamed_thread, phdr, SIZEOF_PHDR * phnum_max)?; let program_headers; #[cfg(target_pointer_width = "64")] { @@ -137,7 +133,7 @@ pub fn write_dso_debug_stream( loop { let dyn_data = PtraceDumper::copy_from_process( blamed_thread, - (dyn_addr as usize + dynamic_length) as *mut libc::c_void, + dyn_addr as usize + dynamic_length, dyn_size, )?; dynamic_length += dyn_size; @@ -163,11 +159,8 @@ pub fn write_dso_debug_stream( // See for a more detailed discussion of the how the dynamic // loader communicates with debuggers. - let debug_entry_data = PtraceDumper::copy_from_process( - blamed_thread, - r_debug as *mut libc::c_void, - std::mem::size_of::(), - )?; + let debug_entry_data = + PtraceDumper::copy_from_process(blamed_thread, r_debug, std::mem::size_of::())?; // goblin::elf::Dyn doesn't have padding bytes let (head, body, _tail) = unsafe { debug_entry_data.align_to::() }; @@ -180,7 +173,7 @@ pub fn write_dso_debug_stream( while curr_map != 0 { let link_map_data = PtraceDumper::copy_from_process( blamed_thread, - curr_map as *mut libc::c_void, + curr_map, std::mem::size_of::(), )?; @@ -204,11 +197,8 @@ pub fn write_dso_debug_stream( for (idx, map) in dso_vec.iter().enumerate() { let mut filename = String::new(); if map.l_name > 0 { - let filename_data = PtraceDumper::copy_from_process( - blamed_thread, - map.l_name as *mut libc::c_void, - 256, - )?; + let filename_data = + PtraceDumper::copy_from_process(blamed_thread, map.l_name, 256)?; // C - string is NULL-terminated if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() { @@ -243,11 +233,8 @@ pub fn write_dso_debug_stream( }; dirent.location.data_size += dynamic_length as u32; - let dso_debug_data = PtraceDumper::copy_from_process( - blamed_thread, - dyn_addr as *mut libc::c_void, - dynamic_length, - )?; + let dso_debug_data = + PtraceDumper::copy_from_process(blamed_thread, dyn_addr as usize, dynamic_length)?; MemoryArrayWriter::write_bytes(buffer, &dso_debug_data); Ok(dirent) diff --git a/src/linux/errors.rs b/src/linux/errors.rs index e94c0cce..e8f652a3 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -1,8 +1,6 @@ -use crate::auxv::AuxvError; -use crate::dir_section::FileWriterError; -use crate::maps_reader::MappingInfo; -use crate::mem_writer::MemoryWriterError; -use crate::thread_info::Pid; +use crate::{ + dir_section::FileWriterError, maps_reader::MappingInfo, mem_writer::MemoryWriterError, Pid, +}; use goblin; use nix::errno::Errno; use std::ffi::OsString; @@ -86,6 +84,16 @@ pub enum AndroidError { NoRelFound, } +#[derive(Debug, Error)] +#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")] +pub struct CopyFromProcessError { + pub child: Pid, + pub src: usize, + pub offset: usize, + pub length: usize, + pub source: nix::Error, +} + #[derive(Debug, Error)] pub enum DumperError { #[error("Failed to get PAGE_SIZE from system")] @@ -96,8 +104,8 @@ pub enum DumperError { PtraceAttachError(Pid, #[source] nix::Error), #[error("nix::ptrace::detach(Pid={0}) failed")] PtraceDetachError(Pid, #[source] nix::Error), - #[error("Copy from process {0} failed (source {1}, offset: {2}, length: {3})")] - CopyFromProcessError(Pid, usize, usize, usize, #[source] nix::Error), + #[error(transparent)] + CopyFromProcessError(#[from] CopyFromProcessError), #[error("Skipped thread {0} due to it being part of the seccomp sandbox's trusted code")] DetachSkippedThread(Pid), #[error("No threads left to suspend out of {0}")] @@ -249,7 +257,7 @@ pub enum ModuleReaderError { offset: u64, length: u64, #[source] - error: std::io::Error, + error: nix::Error, }, #[error("failed to parse ELF memory: {0}")] Parsing(#[from] goblin::error::Error), diff --git a/src/linux/mem_reader.rs b/src/linux/mem_reader.rs new file mode 100644 index 00000000..0c5822d0 --- /dev/null +++ b/src/linux/mem_reader.rs @@ -0,0 +1,203 @@ +//! Functionality for reading a remote process's memory + +use crate::{errors::CopyFromProcessError, ptrace_dumper::PtraceDumper, Pid}; + +enum Style { + /// Uses [`process_vm_readv`](https://linux.die.net/man/2/process_vm_readv) + /// to read the memory. + /// + /// This is not available on old <3.2 (really, ancient) kernels, and requires + /// the same permissions as ptrace + VirtualMem, + /// Reads the memory from `/proc//mem` + /// + /// Available on basically all versions of Linux, but could fail if the process + /// has insufficient priveleges, ie ptrace + File(std::fs::File), + /// Reads the memory with [ptrace (`PTRACE_PEEKDATA`)](https://man7.org/linux/man-pages/man2/ptrace.2.html) + /// + /// Reads data one word at a time, so slow, but fairly reliable, as long as + /// the process can be ptraced + Ptrace, + /// No methods succeeded, generally there isn't a case where failing a syscall + /// will work if called again + Unavailable { + vmem: nix::Error, + file: nix::Error, + ptrace: nix::Error, + }, +} + +pub struct MemReader { + /// The pid of the child to read + pid: nix::unistd::Pid, + style: Option