diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index 70375df26..edb0acc7d 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -1710,7 +1710,7 @@ mod tests { kernel_version: None, section: ProgramSection::KProbe { .. }, .. - } if license.to_string_lossy() == "GPL" + } if license.to_str().unwrap() == "GPL" ); assert_matches!( function_foo, @@ -1730,7 +1730,7 @@ mod tests { kernel_version: None, section: ProgramSection::KProbe { .. }, .. - } if license.to_string_lossy() == "GPL" + } if license.to_str().unwrap() == "GPL" ); assert_matches!( function_bar, diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 25ca21bed..39facc8bb 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -541,14 +541,14 @@ impl MapData { /// Loads a map from a pinned path in bpffs. pub fn from_pin>(path: P) -> Result { + use std::os::unix::ffi::OsStrExt as _; + let path_string = - CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| { - MapError::PinError { - name: None, - error: PinError::InvalidPinPath { - error: e.to_string(), - }, - } + CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| MapError::PinError { + name: None, + error: PinError::InvalidPinPath { + error: e.to_string(), + }, })?; let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { @@ -587,6 +587,8 @@ impl MapData { } pub(crate) fn pin>(&mut self, name: &str, path: P) -> Result<(), PinError> { + use std::os::unix::ffi::OsStrExt as _; + if self.pinned { return Err(PinError::AlreadyPinned { name: name.into() }); } @@ -594,7 +596,7 @@ impl MapData { let fd = self.fd.ok_or(PinError::NoFd { name: name.to_string(), })?; - let path_string = CString::new(map_path.to_string_lossy().into_owned()).map_err(|e| { + let path_string = CString::new(map_path.as_os_str().as_bytes()).map_err(|e| { PinError::InvalidPinPath { error: e.to_string(), } diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index f9cbb6678..c7d5d3cdd 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -68,7 +68,7 @@ impl KProbe { /// /// The returned value can be used to detach from the given function, see [KProbe::detach]. pub fn attach(&mut self, fn_name: &str, offset: u64) -> Result { - attach(&mut self.data, self.kind, fn_name, offset, None) + attach(&mut self.data, self.kind, Path::new(fn_name), offset, None) } /// Detaches the program. diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs index 52aa60de5..2852c691e 100644 --- a/aya/src/programs/links.rs +++ b/aya/src/programs/links.rs @@ -146,12 +146,13 @@ impl FdLink { /// # Ok::<(), Error>(()) /// ``` pub fn pin>(self, path: P) -> Result { - let path_string = - CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| { - PinError::InvalidPinPath { - error: e.to_string(), - } - })?; + use std::os::unix::ffi::OsStrExt as _; + + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| { + PinError::InvalidPinPath { + error: e.to_string(), + } + })?; bpf_pin_object(self.fd.as_raw_fd(), &path_string).map_err(|(_, io_error)| { SyscallError { call: "BPF_OBJ_PIN", @@ -203,7 +204,9 @@ impl PinnedLink { /// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs. pub fn from_pin>(path: P) -> Result { - let path_string = CString::new(path.as_ref().to_string_lossy().to_string()).unwrap(); + use std::os::unix::ffi::OsStrExt as _; + + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| { LinkError::SyscallError(SyscallError { call: "BPF_OBJ_GET", diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index eab30abe5..7ef4b6df7 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -481,8 +481,9 @@ impl ProgramData { path: P, verifier_log_level: VerifierLogLevel, ) -> Result, ProgramError> { - let path_string = - CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap(); + use std::os::unix::ffi::OsStrExt as _; + + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "bpf_obj_get", io_error, @@ -514,6 +515,8 @@ fn unload_program(data: &mut ProgramData) -> Result<(), ProgramError } fn pin_program>(data: &ProgramData, path: P) -> Result<(), PinError> { + use std::os::unix::ffi::OsStrExt as _; + let fd = data.fd.ok_or(PinError::NoFd { name: data .name @@ -521,7 +524,7 @@ fn pin_program>(data: &ProgramData, path: P) -> Resul .unwrap_or("") .to_string(), })?; - let path_string = CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| { + let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| { PinError::InvalidPinPath { error: e.to_string(), } diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index fa04d31df..bc785192b 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -51,14 +51,14 @@ pub(crate) struct ProbeEvent { pub(crate) fn attach>( program_data: &mut ProgramData, kind: ProbeKind, - fn_name: &str, + name: &Path, offset: u64, pid: Option, ) -> Result { // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // Use debugfs to create probe if KernelVersion::current().unwrap() < KernelVersion::new(4, 17, 0) { - let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?; + let (fd, event_alias) = create_as_trace_point(kind, name, offset, pid)?; let link = T::from(perf_attach_debugfs( program_data.fd_or_err()?, fd, @@ -67,7 +67,7 @@ pub(crate) fn attach>( return program_data.links.insert(link); }; - let fd = create_as_probe(kind, fn_name, offset, pid)?; + let fd = create_as_probe(kind, name, offset, pid)?; let link = T::from(perf_attach(program_data.fd_or_err()?, fd)?); program_data.links.insert(link) } @@ -92,7 +92,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> { fn create_as_probe( kind: ProbeKind, - fn_name: &str, + name: &Path, offset: u64, pid: Option, ) -> Result { @@ -117,7 +117,7 @@ fn create_as_probe( _ => None, }; - perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid).map_err(|(_code, io_error)| { + perf_event_open_probe(perf_ty, ret_bit, name, offset, pid).map_err(|(_code, io_error)| { SyscallError { call: "perf_event_open", io_error, @@ -128,7 +128,7 @@ fn create_as_probe( fn create_as_trace_point( kind: ProbeKind, - name: &str, + name: &Path, offset: u64, pid: Option, ) -> Result<(OwnedFd, String), ProgramError> { @@ -156,7 +156,7 @@ fn create_as_trace_point( fn create_probe_event( tracefs: &Path, kind: ProbeKind, - fn_name: &str, + fn_name: &Path, offset: u64, ) -> Result { use ProbeKind::*; @@ -167,6 +167,7 @@ fn create_probe_event( KRetProbe | URetProbe => 'r', }; + let fn_name = fn_name.to_string_lossy(); let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_"); let event_alias = format!( diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index e40bada26..3f4e05fc2 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -4,7 +4,7 @@ use object::{Object, ObjectSection, ObjectSymbol}; use std::{ borrow::Cow, error::Error, - ffi::CStr, + ffi::{CStr, OsStr, OsString}, fs, io::{self, BufRead, Cursor, Read}, mem, @@ -49,6 +49,39 @@ pub struct UProbe { pub(crate) kind: ProbeKind, } +trait OsStringExt { + fn starts_with(&self, needle: &OsStr) -> bool; + fn ends_with(&self, needle: &OsStr) -> bool; + fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>; + fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr>; +} + +impl OsStringExt for OsStr { + fn starts_with(&self, needle: &OsStr) -> bool { + use std::os::unix::ffi::OsStrExt as _; + self.as_bytes().starts_with(needle.as_bytes()) + } + + fn ends_with(&self, needle: &OsStr) -> bool { + use std::os::unix::ffi::OsStrExt as _; + self.as_bytes().ends_with(needle.as_bytes()) + } + + fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> { + use std::os::unix::ffi::OsStrExt as _; + self.as_bytes() + .strip_prefix(prefix.as_bytes()) + .map(OsStr::from_bytes) + } + + fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr> { + use std::os::unix::ffi::OsStrExt as _; + self.as_bytes() + .strip_suffix(suffix.as_bytes()) + .map(OsStr::from_bytes) + } +} + impl UProbe { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { @@ -127,16 +160,15 @@ impl UProbe { fn resolve_attach_path>( target: &T, pid: Option, -) -> Result, UProbeError> { +) -> Result, UProbeError> { // Look up the path for the target. If it there is a pid, and the target is a library name // that is in the process's memory map, use the path of that library. Otherwise, use the target as-is. let target = target.as_ref(); let invalid_target = || UProbeError::InvalidTarget { path: target.to_owned(), }; - let target_str = target.to_str().ok_or_else(invalid_target)?; pid.and_then(|pid| { - find_lib_in_proc_maps(pid, target_str) + find_lib_in_proc_maps(pid, target) .map_err(|io_error| UProbeError::FileError { filename: format!("/proc/{pid}/maps"), io_error, @@ -144,14 +176,14 @@ fn resolve_attach_path>( .map(|v| v.map(Cow::Owned)) .transpose() }) - .or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target_str)))) + .or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target)))) .or_else(|| { LD_SO_CACHE .as_ref() .map_err(|error| UProbeError::InvalidLdSoCache { io_error: error.clone(), }) - .map(|cache| cache.resolve(target_str).map(Cow::Borrowed)) + .map(|cache| cache.resolve(target).map(Cow::Borrowed)) .transpose() }) .unwrap_or_else(|| Err(invalid_target())) @@ -172,6 +204,7 @@ 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", Some(pid)).unwrap(); + let libc_path = libc_path.to_str().unwrap(); // Make sure we got a path that contains libc. assert!(libc_path.contains("libc"), "libc_path: {}", libc_path); @@ -249,45 +282,55 @@ pub enum UProbeError { }, } -fn proc_maps_libs(pid: pid_t) -> Result, io::Error> { +fn proc_maps_libs(pid: pid_t) -> Result, io::Error> { + use std::os::unix::ffi::OsStrExt as _; + let maps_file = format!("/proc/{pid}/maps"); - let data = fs::read_to_string(maps_file)?; - - Ok(data - .lines() - .filter_map(|line| { - let line = line.split_whitespace().last()?; - if line.starts_with('/') { - let path = PathBuf::from(line); - let key = path.file_name().unwrap().to_string_lossy().into_owned(); - Some((key, path.to_string_lossy().to_string())) - } else { - None + let data = fs::read(maps_file)?; + + let libs = data + .split(|b| b == &b'\n') + .filter_map(|mut line| { + loop { + if let [stripped @ .., c] = line { + if c.is_ascii_whitespace() { + line = stripped; + continue; + } + } + break; } + let path = line.split(|b| b.is_ascii_whitespace()).last()?; + let path = Path::new(OsStr::from_bytes(path)); + path.is_absolute() + .then(|| { + path.file_name() + .map(|file_name| (file_name.to_owned(), path.to_owned())) + }) + .flatten() }) - .collect()) + .collect(); + Ok(libs) } -fn find_lib_in_proc_maps(pid: pid_t, lib: &str) -> Result, io::Error> { +fn find_lib_in_proc_maps(pid: pid_t, lib: &Path) -> Result, io::Error> { let libs = proc_maps_libs(pid)?; - let ret = if lib.contains(".so") { - libs.into_iter().find(|(k, _)| k.as_str().starts_with(lib)) - } else { - libs.into_iter().find(|(k, _)| { - k.strip_prefix(lib) - .map(|k| k.starts_with(".so") || k.starts_with('-')) - .unwrap_or_default() - }) - }; + let lib = lib.as_os_str(); + let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib); - Ok(ret.map(|(_, v)| v)) + Ok(libs.into_iter().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) + }) + })) } #[derive(Debug)] pub(crate) struct CacheEntry { - key: String, - value: String, + key: OsString, + value: OsString, _flags: i32, } @@ -368,11 +411,16 @@ impl LdSoCache { } let read_str = |pos| { - unsafe { - CStr::from_ptr(cursor.get_ref()[offset + pos..].as_ptr() as *const c_char) - } - .to_string_lossy() - .into_owned() + use std::os::unix::ffi::OsStrExt as _; + OsStr::from_bytes( + unsafe { + CStr::from_ptr( + cursor.get_ref()[offset + pos..].as_ptr() as *const c_char + ) + } + .to_bytes(), + ) + .to_owned() }; let key = read_str(k_pos); @@ -389,16 +437,18 @@ impl LdSoCache { Ok(LdSoCache { entries }) } - pub fn resolve(&self, lib: &str) -> Option<&str> { - let lib = if !lib.contains(".so") { - lib.to_string() + ".so" - } else { - lib.to_string() - }; + pub fn resolve(&self, lib: &Path) -> Option<&Path> { + let lib = lib.as_os_str(); + let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib); self.entries .iter() - .find(|entry| entry.key.starts_with(&lib)) - .map(|entry| entry.value.as_str()) + .find_map(|CacheEntry { key, value, _flags }| { + key.strip_prefix(lib).and_then(|suffix| { + suffix + .starts_with(OsStr::new(".so")) + .then_some(Path::new(value.as_os_str())) + }) + }) } } @@ -420,7 +470,7 @@ enum ResolveSymbolError { SectionFileRangeNone(String, Result), } -fn resolve_symbol(path: &str, symbol: &str) -> Result { +fn resolve_symbol(path: &Path, symbol: &str) -> Result { let data = fs::read(path)?; let obj = object::read::File::parse(&*data)?; diff --git a/aya/src/sys/netlink.rs b/aya/src/sys/netlink.rs index 1dfae70d6..3ae6948f0 100644 --- a/aya/src/sys/netlink.rs +++ b/aya/src/sys/netlink.rs @@ -742,6 +742,6 @@ mod tests { TCA_BPF_NAME as u16 ); let name = CStr::from_bytes_with_nul(inner.data).unwrap(); - assert_eq!(name.to_string_lossy(), "foo"); + assert_eq!(name.to_str().unwrap(), "foo"); } } diff --git a/aya/src/sys/perf_event.rs b/aya/src/sys/perf_event.rs index dad86a45d..000fecbc1 100644 --- a/aya/src/sys/perf_event.rs +++ b/aya/src/sys/perf_event.rs @@ -2,6 +2,7 @@ use std::{ ffi::{c_long, CString}, io, mem, os::fd::{BorrowedFd, FromRawFd as _, OwnedFd}, + path::Path, }; use libc::{c_int, pid_t}; @@ -62,17 +63,19 @@ pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult { pub(crate) fn perf_event_open_probe( ty: u32, ret_bit: Option, - name: &str, + name: &Path, offset: u64, pid: Option, ) -> SysResult { + use std::os::unix::ffi::OsStrExt as _; + let mut attr = unsafe { mem::zeroed::() }; if let Some(ret_bit) = ret_bit { attr.config = 1 << ret_bit; } - let c_name = CString::new(name).unwrap(); + let c_name = CString::new(name.as_os_str().as_bytes()).unwrap(); attr.size = mem::size_of::() as u32; attr.type_ = ty;