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