diff --git a/Cargo.lock b/Cargo.lock index 1ea525c3..25a09b95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1375,7 +1375,6 @@ dependencies = [ "minidump", "minidump-common", "minidump-unwind", - "nix", "process-backend", "procfs-core 0.18.0", "scroll 0.12.0", @@ -1430,18 +1429,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" diff --git a/Cargo.toml b/Cargo.toml index b9a9af08..8a1e594f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,14 +53,6 @@ memmap2 = "0.9" byteorder = "1.4" error-graph = { version = "0.1.1", features = ["serde"] } failspot = "0.2.0" -nix = { version = "0.30", default-features = false, features = [ - "mman", - "process", - "ptrace", - "signal", - "uio", - "user", -] } process-backend = { path = "crates/linux/process-backend", features = ["testing"] } # Used for parsing procfs info. # default-features is disabled since it pulls in chrono diff --git a/src/bin/test.rs b/src/bin/test.rs index 17deb223..6486a498 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -13,10 +13,7 @@ mod linux { LINUX_GATE_LIBRARY_NAME, minidump_writer::{MinidumpWriter, MinidumpWriterConfig}, }, - nix::{ - sys::mman::{MapFlags, ProtFlags, mmap_anonymous}, - unistd::getppid, - }, + std::ptr, }; macro_rules! test { @@ -36,12 +33,15 @@ mod linux { __result }}); + fn getppid() -> libc::pid_t { + unsafe { libc::getppid() } + } + fn test_setup() -> Result<()> { let ppid = getppid(); fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw()) - .build_for_testing(&mut soft_errors)? + MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? ); Ok(()) } @@ -50,17 +50,11 @@ mod linux { let ppid = getppid(); let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw()) - .build_for_testing(&mut soft_errors)? + MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? ); test!(!dumper.threads.is_empty(), "No threads"); test!( - dumper - .threads - .iter() - .filter(|x| x.tid == ppid.as_raw()) - .count() - == 1, + dumper.threads.iter().filter(|x| x.tid == ppid).count() == 1, "Thread found multiple times" ); @@ -78,7 +72,7 @@ mod linux { fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { use minidump_writer::process_reader::ProcessReader; - let ppid = getppid().as_raw(); + let ppid = getppid(); let mut dumper = fail_on_soft_error!( soft_errors, MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? @@ -155,8 +149,7 @@ mod linux { let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw()) - .build_for_testing(&mut soft_errors)? + MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? ); dumper .find_mapping(addr1) @@ -171,7 +164,7 @@ mod linux { } fn test_file_id() -> Result<()> { - let ppid = getppid().as_raw(); + let ppid = getppid(); let exe_link = format!("/proc/{ppid}/exe"); let exe_name = std::fs::read_link(exe_link)?.into_os_string(); @@ -201,8 +194,7 @@ mod linux { // Now check that PtraceDumper interpreted the mappings properly. let dumper = fail_on_soft_error!( soft_errors, - MinidumpWriterConfig::new(getppid().as_raw(), getppid().as_raw()) - .build_for_testing(&mut soft_errors)? + MinidumpWriterConfig::new(getppid(), getppid()).build_for_testing(&mut soft_errors)? ); let mut mapping_count = 0; for map in &dumper.mappings { @@ -224,7 +216,7 @@ mod linux { } fn test_linux_gate_mapping_id() -> Result<()> { - let ppid = getppid().as_raw(); + let ppid = getppid(); let mut dumper = fail_on_soft_error!( soft_errors, MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? @@ -247,7 +239,7 @@ mod linux { } fn test_mappings_include_linux_gate() -> Result<()> { - let ppid = getppid().as_raw(); + let ppid = getppid(); let dumper = fail_on_soft_error!( soft_errors, MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? @@ -314,28 +306,33 @@ mod linux { } fn spawn_mmap_wait() -> Result<()> { - let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); - let memory_size = std::num::NonZeroUsize::new(page_size.unwrap() as usize).unwrap(); + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + assert!(page_size > 0); + let memory_size = std::num::NonZeroUsize::new(page_size as usize).unwrap(); // Get some memory to be mapped by the child-process let mapped_mem = unsafe { - mmap_anonymous( - None, - memory_size, - ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, - MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON, - ) - .unwrap() + let ptr = libc::mmap( + ptr::null_mut(), + memory_size.into(), + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + assert!(ptr != libc::MAP_FAILED); + ptr }; - - println!("{} {}", mapped_mem.as_ptr() as usize, memory_size); + println!("{} {}", mapped_mem as usize, memory_size); loop { std::thread::park(); } } fn spawn_alloc_wait() -> Result<()> { - let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); - let memory_size = page_size.unwrap() as usize; + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + assert!(page_size > 0); + + let memory_size = page_size as usize; let mut values = Vec::::with_capacity(memory_size); for idx in 0..memory_size { diff --git a/src/linux/dumper_cpu_info/mod.rs b/src/linux/dumper_cpu_info/mod.rs index f0f1d1ec..f97a9ab6 100644 --- a/src/linux/dumper_cpu_info/mod.rs +++ b/src/linux/dumper_cpu_info/mod.rs @@ -1,7 +1,10 @@ use { super::process_inspection::{self, ProcessInspector}, crate::{minidump_format::PlatformId, serializers::*}, - nix::sys::utsname::uname, + std::{ + ffi::{CStr, c_char}, + mem, + }, }; cfg_if::cfg_if! { @@ -57,39 +60,47 @@ pub fn os_information() -> (PlatformId, String) { // This is quite unfortunate, but the primary reason that uname could fail // would be if it failed to fill out the nodename (hostname) field, even // though we don't care about that particular field at all - let info = uname().map_or_else( - |_e| { - let os = if platform_id == PlatformId::Linux { - "Linux" - } else { - "Android" - }; + let info = (|| unsafe { + let mut uts_name = mem::zeroed(); + if libc::uname(&mut uts_name) == -1 { + return None; + } - let machine = if cfg!(target_arch = "x86_64") { - "x86_64" - } else if cfg!(target_arch = "x86") { - "x86" - } else if cfg!(target_arch = "aarch64") { - "aarch64" - } else if cfg!(target_arch = "arm") { - "arm" - } else { - "" - }; + fn to_str(b: &[c_char]) -> &str { + let cstr = unsafe { CStr::from_ptr(b.as_ptr().cast()) }; + cstr.to_str().unwrap_or("") + } - // TODO: Fallback to other sources of information, eg /etc/os-release - format!("{os} {machine}") - }, - |info| { - format!( - "{} {} {} {}", - info.sysname().to_str().unwrap_or(""), - info.release().to_str().unwrap_or(""), - info.version().to_str().unwrap_or(""), - info.machine().to_str().unwrap_or(""), - ) - }, - ); + Some(format!( + "{} {} {} {}", + to_str(&uts_name.sysname), + to_str(&uts_name.release), + to_str(&uts_name.version), + to_str(&uts_name.machine), + )) + })() + .unwrap_or_else(|| { + let os = if platform_id == PlatformId::Linux { + "Linux" + } else { + "Android" + }; + + let machine = if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "x86") { + "x86" + } else if cfg!(target_arch = "aarch64") { + "aarch64" + } else if cfg!(target_arch = "arm") { + "arm" + } else { + "" + }; + + // TODO: Fallback to other sources of information, eg /etc/os-release + format!("{os} {machine}") + }); (platform_id, info) } diff --git a/src/linux/minidump_writer/errors.rs b/src/linux/minidump_writer/errors.rs index e99428d2..722703ea 100644 --- a/src/linux/minidump_writer/errors.rs +++ b/src/linux/minidump_writer/errors.rs @@ -22,7 +22,7 @@ use { }, error_graph::ErrorList, procfs_core::ProcError, - std::ffi::OsString, + std::ffi::{OsString, c_int}, thiserror::Error, }; @@ -97,38 +97,12 @@ pub enum WriterError { #[serde(skip)] serde_json::Error, ), - #[error("nix::ptrace::attach(Pid={0}) failed")] - PtraceAttachError( - Pid, - #[source] - #[serde(serialize_with = "serialize_nix_error")] - nix::Error, - ), - #[error("nix::ptrace::detach(Pid={0}) failed")] - PtraceDetachError( - Pid, - #[source] - #[serde(serialize_with = "serialize_nix_error")] - nix::Error, - ), - #[error("wait::waitpid(Pid={0}) failed")] - WaitPidError( - Pid, - #[source] - #[serde(serialize_with = "serialize_nix_error")] - nix::Error, - ), + #[error("nix::ptrace::attach(Pid={0}) failed: {1}")] + PtraceAttachError(Pid, c_int), #[error("Skipped thread {0} due to it being part of the seccomp sandbox's trusted code")] DetachSkippedThread(Pid), #[error("Maps reader error")] MapsReaderError(#[from] MapsReaderError), - #[error("Failed to get PAGE_SIZE from system")] - SysConfError( - #[from] - #[serde(serialize_with = "serialize_nix_error")] - nix::Error, - ), - #[error("No mapping for stack pointer found")] NoStackPointerMapping, #[error("Failed slice conversion")] @@ -168,12 +142,6 @@ pub enum InitError { #[cfg(target_os = "android")] #[error("Failed Android specific late init")] AndroidLateInitError(#[from] AndroidError), - #[error("Failed to read the page size")] - PageSizeError( - #[from] - #[serde(serialize_with = "serialize_nix_error")] - nix::Error, - ), #[error("Ptrace does not function within the same process")] CannotPtraceSameProcess, #[error("Failed to stop the target process")] diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index 20c006e2..ee03d8b3 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -279,9 +279,11 @@ impl MinidumpWriter { soft_errors.push(InitError::EnumerateMappingsFailed(Box::new(e))); } - self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)? - .expect("page size apparently unlimited: doesn't make sense.") - as usize; + self.page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() }; + assert!( + self.page_size > 0, + "somehow we weren't able to get the page size - should never happen" + ); let threads_count = self.threads.len(); @@ -577,7 +579,7 @@ impl MinidumpWriter { self.threads_suspended = true; - failspot::failspot!(::SuspendThreads soft_errors.push(WriterError::PtraceAttachError(1234, nix::Error::EPERM))) + failspot::failspot!(::SuspendThreads soft_errors.push(WriterError::PtraceAttachError(1234, libc::EPERM))) } fn resume_threads(&mut self, mut soft_errors: impl WriteErrorList) { diff --git a/src/linux/serializers.rs b/src/linux/serializers.rs index 73b33e2e..ba4e17cf 100644 --- a/src/linux/serializers.rs +++ b/src/linux/serializers.rs @@ -10,13 +10,6 @@ pub fn serialize_goblin_error( ) -> Result { serialize_generic_error(error, serializer) } -/// Serialize [nix::Error] -pub fn serialize_nix_error( - error: &nix::Error, - serializer: S, -) -> Result { - serialize_generic_error(error, serializer) -} /// Serialize [procfs_core::ProcError] pub fn serialize_proc_error( error: &procfs_core::ProcError, diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index a1cf216f..39209322 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -13,7 +13,6 @@ use { minidump_writer::{MinidumpWriter, MinidumpWriterConfig, errors::WriterError}, module_reader::{self}, }, - nix::{errno::Errno, sys::signal::Signal}, procfs_core::process::MMPermissions, serde_json::json, std::{ @@ -47,8 +46,9 @@ fn get_ucontext() -> Result { let mut context = std::mem::MaybeUninit::uninit(); unsafe { let res = crash_context::crash_context_getcontext(context.as_mut_ptr()); - Errno::result(res)?; - + if res == -1 { + Err(std::io::Error::last_os_error())?; + } Ok(context.assume_init()) } } @@ -111,7 +111,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); assert!(meta.len() > 0); @@ -183,7 +183,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); let module_list: MinidumpModuleList = dump @@ -274,7 +274,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Read dump file and check its contents let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); @@ -336,7 +336,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Ensure the MozSoftErrors stream contains the expected errors let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump"); @@ -372,7 +372,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Read dump file and check its contents let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); @@ -446,7 +446,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Read dump file and check its contents. There should be a truncated minidump available let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); @@ -478,7 +478,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Read dump file and check its contents. There should be a truncated minidump available let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); @@ -523,7 +523,7 @@ contextual_test! { 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); + assert_eq!(status, libc::SIGKILL); // Read dump file and check its contents. There should be a truncated minidump available let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); @@ -692,7 +692,7 @@ fn minidump_size_limit() { 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); + assert_eq!(status, libc::SIGKILL); } #[test] @@ -745,7 +745,7 @@ fn with_deleted_binary() { 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); + assert_eq!(status, libc::SIGKILL); // Begin checks on dump let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); diff --git a/tests/linux_minidump_writer_soft_error.rs b/tests/linux_minidump_writer_soft_error.rs index 9b9aae3c..0b2bd1a0 100644 --- a/tests/linux_minidump_writer_soft_error.rs +++ b/tests/linux_minidump_writer_soft_error.rs @@ -58,7 +58,7 @@ fn soft_error_stream_content() { }" } ]}, - {"SuspendThreadsErrors": [{"PtraceAttachError": [1234, "EPERM"]}]} + {"SuspendThreadsErrors": [{"PtraceAttachError": [1234, libc::EPERM]}]} ]}), json!({"WriteSystemInfoErrors": [ {"WriteCpuInformationFailed": { diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index 01945a31..cdbb6f79 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -5,15 +5,13 @@ use { common::*, error_graph::ErrorList, minidump_writer::minidump_writer::MinidumpWriterConfig, - nix::{ - sys::mman::{MapFlags, ProtFlags, mmap}, - sys::signal::Signal, - }, std::{ convert::TryInto, + ffi::c_void, io::{BufRead, BufReader}, mem::size_of, os::unix::process::ExitStatusExt, + ptr, }, }; @@ -165,7 +163,7 @@ fn thread_list_from_parent() { 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); + assert_eq!(status, libc::SIGKILL); // 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, @@ -188,9 +186,10 @@ fn linux_gate_mapping_id() { #[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 page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + assert!(page_size > 0); + let page_size = usize::try_from(page_size).unwrap(); + let map_size = 3 * page_size; let path: String = if let Ok(p) = std::env::var("TEST_HELPER") { p @@ -202,30 +201,31 @@ fn merges_mappings() { // mmap two segments out of the helper binary, one // enclosed in the other, but with different protections. let mapped_mem = unsafe { - mmap( - None, + let ptr = libc::mmap( + ptr::null_mut(), map_size, - ProtFlags::PROT_READ, - MapFlags::MAP_SHARED, - &file, + libc::PROT_READ, + libc::MAP_SHARED, + std::os::fd::AsRawFd::as_raw_fd(&file), 0, - ) - .unwrap() + ); + assert!(ptr != libc::MAP_FAILED); + ptr }; - let mapped = mapped_mem.as_ptr() as usize; + let mapped = mapped_mem 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()), + libc::mmap( + (mapped + 2 * page_size) as *mut c_void, page_size, - ProtFlags::PROT_NONE, - MapFlags::MAP_SHARED | MapFlags::MAP_FIXED, - &file, + libc::PROT_NONE, + libc::MAP_SHARED | libc::MAP_FIXED, + std::os::fd::AsRawFd::as_raw_fd(&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 + page_size.try_into().unwrap(), ) }; @@ -392,5 +392,5 @@ fn sanitizes_stack_copies() { 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); + assert_eq!(status, libc::SIGKILL); }