From ea76e0f62dbdff618789dfc9a9d8604a89f61f13 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Fri, 24 Jun 2022 23:05:00 +0100 Subject: [PATCH] aya: parse complete entries from /proc/$pid/maps Signed-off-by: Dave Tucker Signed-off-by: Tamir Duberstein --- aya/src/programs/uprobe.rs | 369 +++++++++++++++++++++++++++++++++---- xtask/public-api/aya.txt | 42 +++++ 2 files changed, 378 insertions(+), 33 deletions(-) diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index e6cb0e6f..72844899 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -141,17 +141,25 @@ impl UProbe { } } -fn resolve_attach_path<'a, 'b, 'c>( +fn resolve_attach_path<'a, 'b, 'c, T>( target: &'a Path, - proc_map: Option<&'b ProcMap>, + proc_map: Option<&'b ProcMap>, ) -> Result<&'c Path, UProbeError> where 'a: 'c, 'b: 'c, + T: AsRef<[u8]>, { proc_map - .and_then(|proc_map| proc_map.find_lib(target)) - .map(Ok) + .and_then(|proc_map| { + proc_map + .find_library_path_by_name(target) + .map_err(|source| UProbeError::ProcMap { + pid: proc_map.pid, + source, + }) + .transpose() + }) .or_else(|| target.is_absolute().then(|| Ok(target))) .or_else(|| { LD_SO_CACHE @@ -182,11 +190,19 @@ fn test_resolve_attach_path() { // Now let's resolve the path to libc. It should exist in the current process's memory map and // then in the ld.so.cache. - let libc_path = resolve_attach_path("libc".as_ref(), Some(&proc_map)).unwrap(); - let libc_path = libc_path.to_str().unwrap(); + let libc_path = resolve_attach_path("libc".as_ref(), Some(&proc_map)).unwrap_or_else(|err| { + match err.source() { + Some(source) => panic!("{err}: {source}"), + None => panic!("{err}"), + } + }); // Make sure we got a path that contains libc. - assert!(libc_path.contains("libc"), "libc_path: {}", libc_path); + assert_matches::assert_matches!( + libc_path.to_str(), + Some(libc_path) if libc_path.contains("libc"), + "libc_path: {}", libc_path.display() + ); } define_link_wrapper!( @@ -260,47 +276,197 @@ pub enum UProbeError { #[source] io_error: io::Error, }, + + /// There was en error fetching the memory map for `pid`. + #[error("error fetching libs for {pid}")] + ProcMap { + /// The pid. + pid: i32, + /// The [`ProcMapError`] that caused the error. + #[source] + source: ProcMapError, + }, +} + +/// Error reading from /proc/pid/maps. +#[derive(Debug, Error)] +pub enum ProcMapError { + /// Unable to read /proc/pid/maps. + #[error(transparent)] + ReadFile(#[from] io::Error), + + /// Error parsing a line of /proc/pid/maps. + #[error("could not parse {:?}", OsStr::from_bytes(line))] + ParseLine { + /// The line that could not be parsed. + line: Vec, + }, +} + +/// A entry that has been parsed from /proc/`pid`/maps. +/// +/// This contains information about a mapped portion of memory +/// for the process, ranging from address to address_end. +#[derive(Debug)] +struct ProcMapEntry<'a> { + #[cfg_attr(not(test), expect(dead_code))] + address: u64, + #[cfg_attr(not(test), expect(dead_code))] + address_end: u64, + #[cfg_attr(not(test), expect(dead_code))] + perms: &'a OsStr, + #[cfg_attr(not(test), expect(dead_code))] + offset: u64, + #[cfg_attr(not(test), expect(dead_code))] + dev: &'a OsStr, + #[cfg_attr(not(test), expect(dead_code))] + inode: u32, + path: Option<&'a Path>, +} + +impl<'a> ProcMapEntry<'a> { + fn parse(line: &'a [u8]) -> Result { + use std::os::unix::ffi::OsStrExt as _; + + let err = || ProcMapError::ParseLine { + line: line.to_vec(), + }; + + let mut parts = line + .split(|b| b.is_ascii_whitespace()) + .filter(|part| !part.is_empty()); + + let mut next = || parts.next().ok_or_else(err); + + let (start, end) = { + let addr = next()?; + let mut addr_parts = addr.split(|b| *b == b'-'); + let mut next = || { + addr_parts + .next() + .ok_or(()) + .and_then(|part| { + let s = + std::str::from_utf8(part).map_err(|std::str::Utf8Error { .. }| ())?; + let n = u64::from_str_radix(s, 16) + .map_err(|std::num::ParseIntError { .. }| ())?; + Ok(n) + }) + .map_err(|()| err()) + }; + let start = next()?; + let end = next()?; + if let Some(_part) = addr_parts.next() { + return Err(err()); + } + (start, end) + }; + + let perms = next()?; + let perms = OsStr::from_bytes(perms); + let offset = next()?; + let offset = std::str::from_utf8(offset).map_err(|std::str::Utf8Error { .. }| err())?; + let offset = + u64::from_str_radix(offset, 16).map_err(|std::num::ParseIntError { .. }| err())?; + let dev = next()?; + let dev = OsStr::from_bytes(dev); + let inode = next()?; + let inode = std::str::from_utf8(inode).map_err(|std::str::Utf8Error { .. }| err())?; + let inode = inode + .parse() + .map_err(|std::num::ParseIntError { .. }| err())?; + + let path = parts + .next() + .and_then(|path| match path { + [b'[', .., b']'] => None, + path => { + let path = Path::new(OsStr::from_bytes(path)); + if !path.is_absolute() { + Some(Err(err())) + } else { + Some(Ok(path)) + } + } + }) + .transpose()?; + + if let Some(_part) = parts.next() { + return Err(err()); + } + + Ok(Self { + address: start, + address_end: end, + perms, + offset, + dev, + inode, + path, + }) + } } -struct ProcMap { - data: Vec, +/// The memory maps of a process. +/// +/// This is read from /proc/`pid`/maps. +/// +/// The information here may be used to resolve addresses to paths. +struct ProcMap { + pid: pid_t, + data: T, } -impl ProcMap { +impl ProcMap> { fn new(pid: pid_t) -> Result { let filename = PathBuf::from(format!("/proc/{pid}/maps")); let data = fs::read(&filename) .map_err(|io_error| UProbeError::FileError { filename, io_error })?; - Ok(Self { data }) + Ok(Self { pid, data }) } +} - fn libs(&self) -> impl Iterator { - let Self { data } = self; +impl> ProcMap { + fn libs(&self) -> impl Iterator, ProcMapError>> { + let Self { pid: _, data } = self; - data.split(|&b| b == b'\n').filter_map(|line| { - line.split(|b| b.is_ascii_whitespace()) - .filter(|p| !p.is_empty()) - .next_back() - .and_then(|path| { - let path = Path::new(OsStr::from_bytes(path)); - path.is_absolute() - .then_some(()) - .and_then(|()| path.file_name()) - .map(|file_name| (file_name, path)) - }) - }) + data.as_ref() + .split(|&b| b == b'\n') + .map(ProcMapEntry::parse) } - fn find_lib(&self, lib: &Path) -> Option<&Path> { + // Find the full path of a library by its name. + // + // This isn't part of the public API since it's really only useful for + // attaching uprobes. + fn find_library_path_by_name(&self, lib: &Path) -> Result, ProcMapError> { let lib = lib.as_os_str(); let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib); - self.libs().find_map(|(file_name, path)| { - file_name.strip_prefix(lib).and_then(|suffix| { - (suffix.starts_with(OsStr::new(".so")) || suffix.starts_with(OsStr::new("-"))) - .then_some(path) - }) - }) + for entry in self.libs() { + let ProcMapEntry { + address: _, + address_end: _, + perms: _, + offset: _, + dev: _, + inode: _, + path, + } = entry?; + if let Some(path) = path { + if let Some(filename) = path.file_name() { + if let Some(suffix) = filename.strip_prefix(lib) { + if suffix.is_empty() + || suffix.starts_with(OsStr::new(".so")) + || suffix.starts_with(OsStr::new("-")) + { + return Ok(Some(path)); + } + } + } + } + } + Ok(None) } } @@ -569,7 +735,7 @@ fn resolve_symbol(path: &Path, symbol: &str) -> Result #[cfg(test)] mod tests { - + use assert_matches::assert_matches; use object::{Architecture, BinaryFormat, Endianness, write::SectionKind}; use super::*; @@ -736,4 +902,141 @@ mod tests { Err(ResolveSymbolError::BuildIdMismatch(_)) )); } + + #[test] + fn test_parse_proc_map_entry_shared_lib() { + assert_matches!( + ProcMapEntry::parse(b"7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]"), + Ok(ProcMapEntry { + address: 0x7ffd6fbea000, + address_end: 0x7ffd6fbec000, + perms, + offset: 0, + dev, + inode: 0, + path: None, + }) if perms == "r-xp" && dev == "00:00" + ); + } + + #[test] + fn test_parse_proc_map_entry_absolute_path() { + assert_matches!( + ProcMapEntry::parse(b"7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2"), + Ok(ProcMapEntry { + address: 0x7f1bca83a000, + address_end: 0x7f1bca83c000, + perms, + offset: 0x00036000, + dev, + inode: 2895508, + path: Some(path), + }) if perms == "rw-p" && dev == "fd:01" && path == Path::new("/usr/lib64/ld-linux-x86-64.so.2") + ); + } + + #[test] + fn test_parse_proc_map_entry_all_zeros() { + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0"), + Ok(ProcMapEntry { + address: 0x7f1bca5f9000, + address_end: 0x7f1bca601000, + perms, + offset: 0, + dev, + inode: 0, + path: None, + }) if perms == "rw-p" && dev == "00:00" + ); + } + + #[test] + fn test_parse_proc_map_entry_parse_errors() { + assert_matches!( + ProcMapEntry::parse(b"zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp zzzz 00:00 0 [vdso]"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp 00000000 00:00 zzzz [vdso]"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f90007ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp 00000000"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000-deadbeef rw-p 00000000 00:00 0"), + Err(ProcMapError::ParseLine { line: _ }) + ); + + assert_matches!( + ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0 deadbeef"), + Err(ProcMapError::ParseLine { line: _ }) + ); + } + + #[test] + fn test_proc_map_find_lib_by_name() { + let proc_map_libs = ProcMap { + pid: 0xdead, + data: b"7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9", + }; + + assert_matches!( + proc_map_libs.find_library_path_by_name(Path::new("libcrypto.so.3.0.9")), + Ok(Some(path)) if path == Path::new("/usr/lib64/libcrypto.so.3.0.9") + ); + } + + #[test] + fn test_proc_map_find_lib_by_partial_name() { + let proc_map_libs = ProcMap { + pid: 0xdead, + data: b"7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9", + }; + + assert_matches!( + proc_map_libs.find_library_path_by_name(Path::new("libcrypto")), + Ok(Some(path)) if path == Path::new("/usr/lib64/libcrypto.so.3.0.9") + ); + } + + #[test] + fn test_proc_map_with_multiple_lib_entries() { + let proc_map_libs = ProcMap { + pid: 0xdead, + data: br#" +7f372868000-7f3722869000 r--p 00000000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2 +7f3722869000-7f372288f000 r-xp 00001000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2 +7f372288f000-7f3722899000 r--p 00027000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2 +7f3722899000-7f372289b000 r--p 00030000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2 +7f372289b000-7f372289d000 rw-p 00032000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2 +"# + .trim_ascii(), + }; + + assert_matches!( + proc_map_libs.find_library_path_by_name(Path::new("ld-linux-x86-64.so.2")), + Ok(Some(path)) if path == Path::new("/usr/lib64/ld-linux-x86-64.so.2") + ); + } } diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 473968a3..390001fb 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -6581,6 +6581,42 @@ pub fn aya::programs::trace_point::TracePointLinkId::borrow_mut(&mut self) -> &m impl core::convert::From for aya::programs::trace_point::TracePointLinkId pub fn aya::programs::trace_point::TracePointLinkId::from(t: T) -> T pub mod aya::programs::uprobe +pub enum aya::programs::uprobe::ProcMapError +pub aya::programs::uprobe::ProcMapError::ParseLine +pub aya::programs::uprobe::ProcMapError::ParseLine::line: alloc::vec::Vec +pub aya::programs::uprobe::ProcMapError::ReadFile(std::io::error::Error) +impl core::convert::From for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::from(source: std::io::error::Error) -> Self +impl core::error::Error for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> +impl core::fmt::Debug for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for aya::programs::uprobe::ProcMapError +impl core::marker::Send for aya::programs::uprobe::ProcMapError +impl core::marker::Sync for aya::programs::uprobe::ProcMapError +impl core::marker::Unpin for aya::programs::uprobe::ProcMapError +impl !core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::ProcMapError +impl !core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::ProcMapError +impl core::convert::Into for aya::programs::uprobe::ProcMapError where U: core::convert::From +pub fn aya::programs::uprobe::ProcMapError::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::ProcMapError where U: core::convert::Into +pub type aya::programs::uprobe::ProcMapError::Error = core::convert::Infallible +pub fn aya::programs::uprobe::ProcMapError::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::ProcMapError where U: core::convert::TryFrom +pub type aya::programs::uprobe::ProcMapError::Error = >::Error +pub fn aya::programs::uprobe::ProcMapError::try_into(self) -> core::result::Result>::Error> +impl alloc::string::ToString for aya::programs::uprobe::ProcMapError where T: core::fmt::Display + ?core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::to_string(&self) -> alloc::string::String +impl core::any::Any for aya::programs::uprobe::ProcMapError where T: 'static + ?core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::ProcMapError where T: ?core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::ProcMapError where T: ?core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::from(t: T) -> T pub enum aya::programs::uprobe::UProbeAttachLocation<'a> pub aya::programs::uprobe::UProbeAttachLocation::AbsoluteOffset(u64) pub aya::programs::uprobe::UProbeAttachLocation::Symbol(&'a str) @@ -6619,6 +6655,9 @@ pub aya::programs::uprobe::UProbeError::InvalidLdSoCache pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error pub aya::programs::uprobe::UProbeError::InvalidTarget pub aya::programs::uprobe::UProbeError::InvalidTarget::path: std::path::PathBuf +pub aya::programs::uprobe::UProbeError::ProcMap +pub aya::programs::uprobe::UProbeError::ProcMap::pid: i32 +pub aya::programs::uprobe::UProbeError::ProcMap::source: aya::programs::uprobe::ProcMapError pub aya::programs::uprobe::UProbeError::SymbolError pub aya::programs::uprobe::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::uprobe::UProbeError::SymbolError::symbol: alloc::string::String @@ -7928,6 +7967,9 @@ pub aya::programs::UProbeError::InvalidLdSoCache pub aya::programs::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error pub aya::programs::UProbeError::InvalidTarget pub aya::programs::UProbeError::InvalidTarget::path: std::path::PathBuf +pub aya::programs::UProbeError::ProcMap +pub aya::programs::UProbeError::ProcMap::pid: i32 +pub aya::programs::UProbeError::ProcMap::source: aya::programs::uprobe::ProcMapError pub aya::programs::UProbeError::SymbolError pub aya::programs::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::UProbeError::SymbolError::symbol: alloc::string::String