maps,programs: avoid path UTF-8 assumptions

reviewable/pr742/r4
Tamir Duberstein 2 years ago
parent 8055035863
commit a88bd5d238
No known key found for this signature in database

@ -541,14 +541,14 @@ impl MapData {
/// Loads a map from a pinned path in bpffs. /// Loads a map from a pinned path in bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<MapData, MapError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<MapData, MapError> {
use std::os::unix::ffi::OsStrExt as _;
let path_string = let path_string =
CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| { CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| MapError::PinError {
MapError::PinError { name: None,
name: None, error: PinError::InvalidPinPath {
error: PinError::InvalidPinPath { error: e.to_string(),
error: e.to_string(), },
},
}
})?; })?;
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
@ -587,6 +587,8 @@ impl MapData {
} }
pub(crate) fn pin<P: AsRef<Path>>(&mut self, name: &str, path: P) -> Result<(), PinError> { pub(crate) fn pin<P: AsRef<Path>>(&mut self, name: &str, path: P) -> Result<(), PinError> {
use std::os::unix::ffi::OsStrExt as _;
if self.pinned { if self.pinned {
return Err(PinError::AlreadyPinned { name: name.into() }); return Err(PinError::AlreadyPinned { name: name.into() });
} }
@ -594,7 +596,7 @@ impl MapData {
let fd = self.fd.ok_or(PinError::NoFd { let fd = self.fd.ok_or(PinError::NoFd {
name: name.to_string(), 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 { PinError::InvalidPinPath {
error: e.to_string(), error: e.to_string(),
} }

@ -68,7 +68,7 @@ impl KProbe {
/// ///
/// The returned value can be used to detach from the given function, see [KProbe::detach]. /// 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<KProbeLinkId, ProgramError> { pub fn attach(&mut self, fn_name: &str, offset: u64) -> Result<KProbeLinkId, ProgramError> {
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. /// Detaches the program.

@ -146,12 +146,13 @@ impl FdLink {
/// # Ok::<(), Error>(()) /// # Ok::<(), Error>(())
/// ``` /// ```
pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<PinnedLink, PinError> { pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<PinnedLink, PinError> {
let path_string = use std::os::unix::ffi::OsStrExt as _;
CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| {
PinError::InvalidPinPath { let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|e| {
error: e.to_string(), PinError::InvalidPinPath {
} error: e.to_string(),
})?; }
})?;
bpf_pin_object(self.fd.as_raw_fd(), &path_string).map_err(|(_, io_error)| { bpf_pin_object(self.fd.as_raw_fd(), &path_string).map_err(|(_, io_error)| {
SyscallError { SyscallError {
call: "BPF_OBJ_PIN", call: "BPF_OBJ_PIN",
@ -203,7 +204,9 @@ impl PinnedLink {
/// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs. /// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
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)| { let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| {
LinkError::SyscallError(SyscallError { LinkError::SyscallError(SyscallError {
call: "BPF_OBJ_GET", call: "BPF_OBJ_GET",

@ -481,8 +481,9 @@ impl<T: Link> ProgramData<T> {
path: P, path: P,
verifier_log_level: VerifierLogLevel, verifier_log_level: VerifierLogLevel,
) -> Result<ProgramData<T>, ProgramError> { ) -> Result<ProgramData<T>, ProgramError> {
let path_string = use std::os::unix::ffi::OsStrExt as _;
CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
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 { let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "bpf_obj_get", call: "bpf_obj_get",
io_error, io_error,
@ -514,6 +515,8 @@ fn unload_program<T: Link>(data: &mut ProgramData<T>) -> Result<(), ProgramError
} }
fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Result<(), PinError> { fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Result<(), PinError> {
use std::os::unix::ffi::OsStrExt as _;
let fd = data.fd.ok_or(PinError::NoFd { let fd = data.fd.ok_or(PinError::NoFd {
name: data name: data
.name .name
@ -521,7 +524,7 @@ fn pin_program<T: Link, P: AsRef<Path>>(data: &ProgramData<T>, path: P) -> Resul
.unwrap_or("<unknown program>") .unwrap_or("<unknown program>")
.to_string(), .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 { PinError::InvalidPinPath {
error: e.to_string(), error: e.to_string(),
} }

@ -51,14 +51,14 @@ pub(crate) struct ProbeEvent {
pub(crate) fn attach<T: Link + From<PerfLinkInner>>( pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
program_data: &mut ProgramData<T>, program_data: &mut ProgramData<T>,
kind: ProbeKind, kind: ProbeKind,
fn_name: &str, name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<T::Id, ProgramError> { ) -> Result<T::Id, ProgramError> {
// https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155
// Use debugfs to create probe // Use debugfs to create probe
if KernelVersion::current().unwrap() < KernelVersion::new(4, 17, 0) { 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( let link = T::from(perf_attach_debugfs(
program_data.fd_or_err()?, program_data.fd_or_err()?,
fd, fd,
@ -67,7 +67,7 @@ pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
return program_data.links.insert(link); 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)?); let link = T::from(perf_attach(program_data.fd_or_err()?, fd)?);
program_data.links.insert(link) program_data.links.insert(link)
} }
@ -92,7 +92,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> {
fn create_as_probe( fn create_as_probe(
kind: ProbeKind, kind: ProbeKind,
fn_name: &str, name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<OwnedFd, ProgramError> { ) -> Result<OwnedFd, ProgramError> {
@ -117,7 +117,7 @@ fn create_as_probe(
_ => None, _ => 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 { SyscallError {
call: "perf_event_open", call: "perf_event_open",
io_error, io_error,
@ -128,7 +128,7 @@ fn create_as_probe(
fn create_as_trace_point( fn create_as_trace_point(
kind: ProbeKind, kind: ProbeKind,
name: &str, name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<(OwnedFd, String), ProgramError> { ) -> Result<(OwnedFd, String), ProgramError> {
@ -156,7 +156,7 @@ fn create_as_trace_point(
fn create_probe_event( fn create_probe_event(
tracefs: &Path, tracefs: &Path,
kind: ProbeKind, kind: ProbeKind,
fn_name: &str, fn_name: &Path,
offset: u64, offset: u64,
) -> Result<String, (String, io::Error)> { ) -> Result<String, (String, io::Error)> {
use ProbeKind::*; use ProbeKind::*;
@ -167,6 +167,7 @@ fn create_probe_event(
KRetProbe | URetProbe => 'r', KRetProbe | URetProbe => 'r',
}; };
let fn_name = fn_name.to_string_lossy();
let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_"); let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_");
let event_alias = format!( let event_alias = format!(

@ -4,7 +4,7 @@ use object::{Object, ObjectSection, ObjectSymbol};
use std::{ use std::{
borrow::Cow, borrow::Cow,
error::Error, error::Error,
ffi::CStr, ffi::{CStr, OsStr, OsString},
fs, fs,
io::{self, BufRead, Cursor, Read}, io::{self, BufRead, Cursor, Read},
mem, mem,
@ -49,6 +49,39 @@ pub struct UProbe {
pub(crate) kind: ProbeKind, 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 { impl UProbe {
/// Loads the program inside the kernel. /// Loads the program inside the kernel.
pub fn load(&mut self) -> Result<(), ProgramError> { pub fn load(&mut self) -> Result<(), ProgramError> {
@ -127,16 +160,15 @@ impl UProbe {
fn resolve_attach_path<T: AsRef<Path>>( fn resolve_attach_path<T: AsRef<Path>>(
target: &T, target: &T,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<Cow<'_, str>, UProbeError> { ) -> Result<Cow<'_, Path>, UProbeError> {
// Look up the path for the target. If it there is a pid, and the target is a library name // 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. // 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 target = target.as_ref();
let invalid_target = || UProbeError::InvalidTarget { let invalid_target = || UProbeError::InvalidTarget {
path: target.to_owned(), path: target.to_owned(),
}; };
let target_str = target.to_str().ok_or_else(invalid_target)?;
pid.and_then(|pid| { 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 { .map_err(|io_error| UProbeError::FileError {
filename: format!("/proc/{pid}/maps"), filename: format!("/proc/{pid}/maps"),
io_error, io_error,
@ -144,14 +176,14 @@ fn resolve_attach_path<T: AsRef<Path>>(
.map(|v| v.map(Cow::Owned)) .map(|v| v.map(Cow::Owned))
.transpose() .transpose()
}) })
.or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target_str)))) .or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target))))
.or_else(|| { .or_else(|| {
LD_SO_CACHE LD_SO_CACHE
.as_ref() .as_ref()
.map_err(|error| UProbeError::InvalidLdSoCache { .map_err(|error| UProbeError::InvalidLdSoCache {
io_error: error.clone(), io_error: error.clone(),
}) })
.map(|cache| cache.resolve(target_str).map(Cow::Borrowed)) .map(|cache| cache.resolve(target).map(Cow::Borrowed))
.transpose() .transpose()
}) })
.unwrap_or_else(|| Err(invalid_target())) .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 // 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. // then in the ld.so.cache.
let libc_path = resolve_attach_path(&"libc", Some(pid)).unwrap(); 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. // Make sure we got a path that contains libc.
assert!(libc_path.contains("libc"), "libc_path: {}", libc_path); assert!(libc_path.contains("libc"), "libc_path: {}", libc_path);
@ -249,45 +282,55 @@ pub enum UProbeError {
}, },
} }
fn proc_maps_libs(pid: pid_t) -> Result<Vec<(String, String)>, io::Error> { fn proc_maps_libs(pid: pid_t) -> Result<Vec<(OsString, PathBuf)>, io::Error> {
use std::os::unix::ffi::OsStrExt as _;
let maps_file = format!("/proc/{pid}/maps"); let maps_file = format!("/proc/{pid}/maps");
let data = fs::read_to_string(maps_file)?; let data = fs::read(maps_file)?;
Ok(data let libs = data
.lines() .split(|b| b == &b'\n')
.filter_map(|line| { .filter_map(|mut line| {
let line = line.split_whitespace().last()?; loop {
if line.starts_with('/') { if let [stripped @ .., c] = line {
let path = PathBuf::from(line); if c.is_ascii_whitespace() {
let key = path.file_name().unwrap().to_string_lossy().into_owned(); line = stripped;
Some((key, path.to_string_lossy().to_string())) continue;
} else { }
None }
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<Option<String>, io::Error> { fn find_lib_in_proc_maps(pid: pid_t, lib: &Path) -> Result<Option<PathBuf>, io::Error> {
let libs = proc_maps_libs(pid)?; let libs = proc_maps_libs(pid)?;
let ret = if lib.contains(".so") { let lib = lib.as_os_str();
libs.into_iter().find(|(k, _)| k.as_str().starts_with(lib)) let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib);
} else {
libs.into_iter().find(|(k, _)| {
k.strip_prefix(lib)
.map(|k| k.starts_with(".so") || k.starts_with('-'))
.unwrap_or_default()
})
};
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)] #[derive(Debug)]
pub(crate) struct CacheEntry { pub(crate) struct CacheEntry {
key: String, key: OsString,
value: String, value: OsString,
_flags: i32, _flags: i32,
} }
@ -368,11 +411,16 @@ impl LdSoCache {
} }
let read_str = |pos| { let read_str = |pos| {
unsafe { use std::os::unix::ffi::OsStrExt as _;
CStr::from_ptr(cursor.get_ref()[offset + pos..].as_ptr() as *const c_char) OsStr::from_bytes(
} unsafe {
.to_string_lossy() CStr::from_ptr(
.into_owned() cursor.get_ref()[offset + pos..].as_ptr() as *const c_char
)
}
.to_bytes(),
)
.to_owned()
}; };
let key = read_str(k_pos); let key = read_str(k_pos);
@ -389,16 +437,18 @@ impl LdSoCache {
Ok(LdSoCache { entries }) Ok(LdSoCache { entries })
} }
pub fn resolve(&self, lib: &str) -> Option<&str> { pub fn resolve(&self, lib: &Path) -> Option<&Path> {
let lib = if !lib.contains(".so") { let lib = lib.as_os_str();
lib.to_string() + ".so" let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib);
} else {
lib.to_string()
};
self.entries self.entries
.iter() .iter()
.find(|entry| entry.key.starts_with(&lib)) .find_map(|CacheEntry { key, value, _flags }| {
.map(|entry| entry.value.as_str()) 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<String, object::Error>), SectionFileRangeNone(String, Result<String, object::Error>),
} }
fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> { fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError> {
let data = fs::read(path)?; let data = fs::read(path)?;
let obj = object::read::File::parse(&*data)?; let obj = object::read::File::parse(&*data)?;

@ -2,6 +2,7 @@ use std::{
ffi::{c_long, CString}, ffi::{c_long, CString},
io, mem, io, mem,
os::fd::{BorrowedFd, FromRawFd as _, OwnedFd}, os::fd::{BorrowedFd, FromRawFd as _, OwnedFd},
path::Path,
}; };
use libc::{c_int, pid_t}; use libc::{c_int, pid_t};
@ -62,17 +63,19 @@ pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult<OwnedFd> {
pub(crate) fn perf_event_open_probe( pub(crate) fn perf_event_open_probe(
ty: u32, ty: u32,
ret_bit: Option<u32>, ret_bit: Option<u32>,
name: &str, name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> SysResult<OwnedFd> { ) -> SysResult<OwnedFd> {
use std::os::unix::ffi::OsStrExt as _;
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() }; let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
if let Some(ret_bit) = ret_bit { if let Some(ret_bit) = ret_bit {
attr.config = 1 << 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::<perf_event_attr>() as u32; attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = ty; attr.type_ = ty;

Loading…
Cancel
Save