maps,programs: avoid path UTF-8 assumptions

pull/742/head
Tamir Duberstein 1 year ago
parent 572d047e37
commit 0bba9b14b0
No known key found for this signature in database

@ -1,7 +1,6 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
ffi::CString,
fs, io, fs, io,
os::{ os::{
fd::{AsFd as _, OwnedFd}, fd::{AsFd as _, OwnedFd},
@ -608,10 +607,6 @@ impl<'a> BpfLoader<'a> {
ProgramSection::SchedClassifier => { ProgramSection::SchedClassifier => {
Program::SchedClassifier(SchedClassifier { Program::SchedClassifier(SchedClassifier {
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
name: unsafe {
CString::from_vec_unchecked(Vec::from(name.clone()))
.into_boxed_c_str()
},
}) })
} }
ProgramSection::CgroupSkb => Program::CgroupSkb(CgroupSkb { ProgramSection::CgroupSkb => Program::CgroupSkb(CgroupSkb {
@ -960,13 +955,6 @@ pub enum BpfError {
name: u32, name: u32,
}, },
/// Invalid path
#[error("invalid path `{error}`")]
InvalidPath {
/// The error message
error: String,
},
/// Error parsing BPF object /// Error parsing BPF object
#[error("error parsing BPF object: {0}")] #[error("error parsing BPF object: {0}")]
ParseError(#[from] ParseError), ParseError(#[from] ParseError),

@ -515,9 +515,19 @@ impl MapData {
name: &str, name: &str,
btf_fd: Option<BorrowedFd<'_>>, btf_fd: Option<BorrowedFd<'_>>,
) -> Result<Self, MapError> { ) -> Result<Self, MapError> {
use std::os::unix::ffi::OsStrExt as _;
// try to open map in case it's already pinned // try to open map in case it's already pinned
let map_path = path.as_ref().join(name); let path = path.as_ref().join(name);
let path_string = CString::new(map_path.to_str().unwrap()).unwrap(); let path_string = match CString::new(path.as_os_str().as_bytes()) {
Ok(path) => path,
Err(error) => {
return Err(MapError::PinError {
name: Some(name.into()),
error: PinError::InvalidPinPath { path, error },
});
}
};
match bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { match bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_GET", call: "BPF_OBJ_GET",
io_error, io_error,
@ -540,14 +550,16 @@ 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<Self, MapError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, MapError> {
use std::os::unix::ffi::OsStrExt as _;
let path = path.as_ref();
let path_string = let path_string =
CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| { CString::new(path.as_os_str().as_bytes()).map_err(|error| MapError::PinError {
MapError::PinError { name: None,
name: None, error: PinError::InvalidPinPath {
error: PinError::InvalidPinPath { path: path.into(),
error: e.to_string(), error,
}, },
}
})?; })?;
let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError {
@ -580,16 +592,15 @@ 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 _;
let Self { fd, pinned, obj: _ } = self; let Self { fd, pinned, obj: _ } = self;
if *pinned { if *pinned {
return Err(PinError::AlreadyPinned { name: name.into() }); return Err(PinError::AlreadyPinned { name: name.into() });
} }
let map_path = path.as_ref().join(name); let path = path.as_ref().join(name);
let path_string = CString::new(map_path.to_string_lossy().into_owned()).map_err(|e| { let path_string = CString::new(path.as_os_str().as_bytes())
PinError::InvalidPinPath { .map_err(|error| PinError::InvalidPinPath { path, error })?;
error: e.to_string(),
}
})?;
bpf_pin_object(*fd, &path_string).map_err(|(_, io_error)| SyscallError { bpf_pin_object(*fd, &path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_PIN", call: "BPF_OBJ_PIN",
io_error, io_error,

@ -19,10 +19,14 @@ pub enum PinError {
name: String, name: String,
}, },
/// The path for the BPF object is not valid. /// The path for the BPF object is not valid.
#[error("invalid pin path `{error}`")] #[error("invalid pin path `{}`", path.display())]
InvalidPinPath { InvalidPinPath {
/// The error message. /// The path.
error: String, path: std::path::PathBuf,
#[source]
/// The source error.
error: std::ffi::NulError,
}, },
/// An error ocurred making a syscall. /// An error ocurred making a syscall.
#[error(transparent)] #[error(transparent)]

@ -1,5 +1,9 @@
//! Kernel space probes. //! Kernel space probes.
use std::{io, os::fd::AsFd as _, path::Path}; use std::{
io,
os::fd::AsFd as _,
path::{Path, PathBuf},
};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
@ -68,7 +72,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.
@ -114,7 +118,7 @@ pub enum KProbeError {
#[error("`{filename}`")] #[error("`{filename}`")]
FileError { FileError {
/// The file name /// The file name
filename: String, filename: PathBuf,
/// The [`io::Error`] returned from the file operation /// The [`io::Error`] returned from the file operation
#[source] #[source]
io_error: io::Error, io_error: io::Error,

@ -146,19 +146,22 @@ 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 = path.as_ref();
error: e.to_string(), let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| {
} PinError::InvalidPinPath {
})?; path: path.into(),
error,
}
})?;
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",
io_error, io_error,
} }
})?; })?;
Ok(PinnedLink::new(PathBuf::from(path.as_ref()), self)) Ok(PinnedLink::new(path.into(), self))
} }
} }
@ -203,7 +206,10 @@ 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 _;
// TODO: avoid this unwrap by adding a new error variant.
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",

@ -487,9 +487,10 @@ impl<T: Link> ProgramData<T> {
path: P, path: P,
verifier_log_level: VerifierLogLevel, verifier_log_level: VerifierLogLevel,
) -> Result<Self, ProgramError> { ) -> Result<Self, 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();
// TODO: avoid this unwrap by adding a new error variant.
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,
@ -520,6 +521,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.as_ref().ok_or(PinError::NoFd { let fd = data.fd.as_ref().ok_or(PinError::NoFd {
name: data name: data
.name .name
@ -527,11 +530,12 @@ 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 = path.as_ref();
PinError::InvalidPinPath { let path_string =
error: e.to_string(), CString::new(path.as_os_str().as_bytes()).map_err(|error| PinError::InvalidPinPath {
} path: path.into(),
})?; error,
})?;
bpf_pin_object(fd.as_fd().as_raw_fd(), &path_string).map_err(|(_, io_error)| SyscallError { bpf_pin_object(fd.as_fd().as_raw_fd(), &path_string).map_err(|(_, io_error)| SyscallError {
call: "BPF_OBJ_PIN", call: "BPF_OBJ_PIN",
io_error, io_error,
@ -1073,7 +1077,10 @@ impl ProgramInfo {
/// Loads a program from a pinned path in bpffs. /// Loads a program from a pinned path in bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let path_string = CString::new(path.as_ref().to_str().unwrap()).unwrap(); use std::os::unix::ffi::OsStrExt as _;
// TODO: avoid this unwrap by adding a new error variant.
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,

@ -1,10 +1,12 @@
use crate::util::KernelVersion; use crate::util::KernelVersion;
use libc::pid_t; use libc::pid_t;
use std::{ use std::{
ffi::{OsStr, OsString},
fmt::Write as _,
fs::{self, OpenOptions}, fs::{self, OpenOptions},
io::{self, Write}, io::{self, Write},
os::fd::{AsFd as _, AsRawFd as _, OwnedFd}, os::fd::{AsFd as _, AsRawFd as _, OwnedFd},
path::Path, path::{Path, PathBuf},
process, process,
sync::atomic::{AtomicUsize, Ordering}, sync::atomic::{AtomicUsize, Ordering},
}; };
@ -42,16 +44,64 @@ impl ProbeKind {
} }
} }
pub(crate) fn lines(bytes: &[u8]) -> impl Iterator<Item = &OsStr> {
use std::os::unix::ffi::OsStrExt as _;
bytes.as_ref().split(|b| b == &b'\n').map(|mut line| {
while let [stripped @ .., c] = line {
if c.is_ascii_whitespace() {
line = stripped;
continue;
}
break;
}
OsStr::from_bytes(line)
})
}
pub(crate) 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(Self::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(Self::from_bytes)
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ProbeEvent { pub(crate) struct ProbeEvent {
kind: ProbeKind, kind: ProbeKind,
event_alias: String, event_alias: OsString,
} }
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, fn_name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<T::Id, ProgramError> { ) -> Result<T::Id, ProgramError> {
@ -90,7 +140,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, fn_name: &Path,
offset: u64, offset: u64,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<OwnedFd, ProgramError> { ) -> Result<OwnedFd, ProgramError> {
@ -126,10 +176,10 @@ 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, OsString), ProgramError> {
use ProbeKind::*; use ProbeKind::*;
let tracefs = find_tracefs_path()?; let tracefs = find_tracefs_path()?;
@ -142,7 +192,7 @@ fn create_as_trace_point(
}; };
let category = format!("{}s", kind.pmu()); let category = format!("{}s", kind.pmu());
let tpid = read_sys_fs_trace_point_id(tracefs, &category, &event_alias)?; let tpid = read_sys_fs_trace_point_id(tracefs, &category, event_alias.as_ref())?;
let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError { let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError {
call: "perf_event_open", call: "perf_event_open",
io_error, io_error,
@ -154,9 +204,10 @@ 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<OsString, (PathBuf, io::Error)> {
use std::os::unix::ffi::OsStrExt as _;
use ProbeKind::*; use ProbeKind::*;
let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
@ -165,93 +216,129 @@ fn create_probe_event(
KRetProbe | URetProbe => 'r', KRetProbe | URetProbe => 'r',
}; };
let fixed_fn_name = fn_name.replace(['.', '/', '-'], "_"); let fn_name = fn_name.as_os_str();
let event_alias = format!( let mut event_alias = OsString::new();
"aya_{}_{}_{}_{:#x}_{}", write!(
&mut event_alias,
"aya_{}_{}_",
process::id(), process::id(),
probe_type_prefix, probe_type_prefix,
fixed_fn_name, )
.unwrap();
for b in fn_name.as_bytes() {
let b = match *b {
b'.' | b'/' | b'-' => b'_',
b => b,
};
event_alias.push(OsStr::from_bytes(&[b]));
}
write!(
&mut event_alias,
"_{:#x}_{}",
offset, offset,
PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel) PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel)
); )
let offset_suffix = match kind { .unwrap();
KProbe => format!("+{offset}"),
UProbe | URetProbe => format!(":{offset:#x}"), let mut probe = OsString::new();
_ => String::new(), write!(&mut probe, "{}:{}s/", probe_type_prefix, kind.pmu(),).unwrap();
probe.push(&event_alias);
probe.push(" ");
probe.push(fn_name);
match kind {
KProbe => write!(&mut probe, "+{offset}").unwrap(),
UProbe | URetProbe => write!(&mut probe, ":{offset:#x}").unwrap(),
_ => {}
}; };
let probe = format!( probe.push("\n");
"{}:{}s/{} {}{}\n",
probe_type_prefix,
kind.pmu(),
event_alias,
fn_name,
offset_suffix
);
let mut events_file = OpenOptions::new() OpenOptions::new()
.append(true) .append(true)
.open(&events_file_name) .open(&events_file_name)
.map_err(|e| (events_file_name.display().to_string(), e))?; .and_then(|mut events_file| events_file.write_all(probe.as_bytes()))
.map_err(|e| (events_file_name, e))?;
events_file
.write_all(probe.as_bytes())
.map_err(|e| (events_file_name.display().to_string(), e))?;
Ok(event_alias) Ok(event_alias)
} }
fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (String, io::Error)> { fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (PathBuf, io::Error)> {
use std::os::unix::ffi::OsStrExt as _;
let ProbeEvent { kind, event_alias } = event; let ProbeEvent { kind, event_alias } = event;
let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); let events_file_name = tracefs.join(format!("{}_events", kind.pmu()));
let events = fs::read_to_string(&events_file_name) fs::read(&events_file_name)
.map_err(|e| (events_file_name.display().to_string(), e))?; .and_then(|events| {
let found = lines(&events).any(|line| {
let found = events.lines().any(|line| line.contains(&event_alias)); let mut line = line.as_bytes();
// See [`create_probe_event`] and the documentation:
if found { //
let mut events_file = OpenOptions::new() // https://docs.kernel.org/trace/kprobetrace.html
.append(true) //
.open(&events_file_name) // https://docs.kernel.org/trace/uprobetracer.html
.map_err(|e| (events_file_name.display().to_string(), e))?; loop {
match line.split_first() {
let rm = format!("-:{event_alias}\n"); None => break false,
Some((b, rest)) => {
events_file line = rest;
.write_all(rm.as_bytes()) if *b == b'/' {
.map_err(|e| (events_file_name.display().to_string(), e))?; break line.starts_with(event_alias.as_bytes());
} }
}
Ok(()) }
}
});
if found {
OpenOptions::new()
.append(true)
.open(&events_file_name)
.and_then(|mut events_file| {
let mut rm = OsString::new();
rm.push("-:");
rm.push(event_alias);
rm.push("\n");
events_file.write_all(rm.as_bytes())
})
} else {
Ok(())
}
})
.map_err(|e| (events_file_name, e))
} }
fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (String, io::Error)> { fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (PathBuf, io::Error)> {
let file = format!("/sys/bus/event_source/devices/{pmu}/type"); let file = Path::new("/sys/bus/event_source/devices")
.join(pmu)
let perf_ty = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?; .join("type");
let perf_ty = perf_ty
.trim() fs::read_to_string(&file)
.parse::<u32>() .and_then(|perf_ty| {
.map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))?; perf_ty
.trim()
Ok(perf_ty) .parse::<u32>()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
})
.map_err(|e| (file, e))
} }
fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (String, io::Error)> { fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (PathBuf, io::Error)> {
let file = format!("/sys/bus/event_source/devices/{pmu}/format/retprobe"); let file = Path::new("/sys/bus/event_source/devices")
.join(pmu)
let data = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?; .join("format/retprobe");
let mut parts = data.trim().splitn(2, ':').skip(1); fs::read_to_string(&file)
let config = parts.next().ok_or_else(|| { .and_then(|data| {
( let mut parts = data.trim().splitn(2, ':').skip(1);
file.clone(), let config = parts
io::Error::new(io::ErrorKind::Other, "invalid format"), .next()
) .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid format"))?;
})?;
config
config .parse::<u32>()
.parse::<u32>() .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e))) })
.map_err(|e| (file, e))
} }

@ -74,7 +74,6 @@ pub enum TcAttachType {
#[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")] #[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")]
pub struct SchedClassifier { pub struct SchedClassifier {
pub(crate) data: ProgramData<SchedClassifierLink>, pub(crate) data: ProgramData<SchedClassifierLink>,
pub(crate) name: Box<CStr>,
} }
/// Errors from TC programs /// Errors from TC programs
@ -158,12 +157,15 @@ impl SchedClassifier {
let prog_fd = prog_fd.as_raw_fd(); let prog_fd = prog_fd.as_raw_fd();
let if_index = ifindex_from_ifname(interface) let if_index = ifindex_from_ifname(interface)
.map_err(|io_error| TcError::NetlinkError { io_error })?; .map_err(|io_error| TcError::NetlinkError { io_error })?;
let name = self.data.name.as_deref().unwrap_or_default();
// TODO: avoid this unwrap by adding a new error variant.
let name = CString::new(name).unwrap();
let (priority, handle) = unsafe { let (priority, handle) = unsafe {
netlink_qdisc_attach( netlink_qdisc_attach(
if_index as i32, if_index as i32,
&attach_type, &attach_type,
prog_fd, prog_fd,
&self.name, &name,
options.priority, options.priority,
options.handle, options.handle,
) )
@ -204,10 +206,7 @@ impl SchedClassifier {
/// the program being unloaded from the kernel if it is still pinned. /// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> { pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
let cname = CString::new(data.name.clone().unwrap_or_default()) Ok(Self { data })
.unwrap()
.into_boxed_c_str();
Ok(Self { data, name: cname })
} }
} }

@ -86,7 +86,7 @@ impl TracePoint {
let prog_fd = prog_fd.as_fd(); let prog_fd = prog_fd.as_fd();
let prog_fd = prog_fd.as_raw_fd(); let prog_fd = prog_fd.as_raw_fd();
let tracefs = find_tracefs_path()?; let tracefs = find_tracefs_path()?;
let id = read_sys_fs_trace_point_id(tracefs, category, name)?; let id = read_sys_fs_trace_point_id(tracefs, category, name.as_ref())?;
let fd = let fd =
perf_event_open_trace_point(id, None).map_err(|(_code, io_error)| SyscallError { perf_event_open_trace_point(id, None).map_err(|(_code, io_error)| SyscallError {
call: "perf_event_open_trace_point", call: "perf_event_open_trace_point",
@ -149,7 +149,7 @@ impl TryFrom<FdLink> for TracePointLink {
pub(crate) fn read_sys_fs_trace_point_id( pub(crate) fn read_sys_fs_trace_point_id(
tracefs: &Path, tracefs: &Path,
category: &str, category: &str,
name: &str, name: &Path,
) -> Result<u32, TracePointError> { ) -> Result<u32, TracePointError> {
let file = tracefs.join("events").join(category).join(name).join("id"); let file = tracefs.join("events").join(category).join(name).join("id");

@ -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,
@ -19,7 +19,7 @@ use crate::{
programs::{ programs::{
define_link_wrapper, load_program, define_link_wrapper, load_program,
perf_attach::{PerfLinkIdInner, PerfLinkInner}, perf_attach::{PerfLinkIdInner, PerfLinkInner},
probe::{attach, ProbeKind}, probe::{attach, OsStringExt as _, ProbeKind},
FdLink, LinkError, ProgramData, ProgramError, FdLink, LinkError, ProgramData, ProgramError,
}, },
sys::bpf_link_get_info_by_fd, sys::bpf_link_get_info_by_fd,
@ -83,7 +83,7 @@ impl UProbe {
target: T, target: T,
pid: Option<pid_t>, pid: Option<pid_t>,
) -> Result<UProbeLinkId, ProgramError> { ) -> Result<UProbeLinkId, ProgramError> {
let path = resolve_attach_path(&target, pid)?; let path = resolve_attach_path(target.as_ref(), pid)?;
let sym_offset = if let Some(fn_name) = fn_name { let sym_offset = if let Some(fn_name) = fn_name {
resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError { resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError {
@ -124,37 +124,33 @@ impl UProbe {
} }
} }
fn resolve_attach_path<T: AsRef<Path>>( fn resolve_attach_path(target: &Path, pid: Option<pid_t>) -> Result<Cow<'_, Path>, UProbeError> {
target: &T,
pid: Option<pid_t>,
) -> Result<Cow<'_, str>, 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 invalid_target = || UProbeError::InvalidTarget {
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: Path::new("/proc").join(pid.to_string()).join("maps"),
io_error, io_error,
}) })
.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(UProbeError::InvalidTarget {
path: target.to_owned(),
})
})
} }
// Only run this test on linux with glibc because only in that configuration do we know that we'll // Only run this test on linux with glibc because only in that configuration do we know that we'll
@ -171,7 +167,8 @@ 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".as_ref(), 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);
@ -242,52 +239,60 @@ pub enum UProbeError {
#[error("`{filename}`")] #[error("`{filename}`")]
FileError { FileError {
/// The file name /// The file name
filename: String, filename: PathBuf,
/// The [`io::Error`] returned from the file operation /// The [`io::Error`] returned from the file operation
#[source] #[source]
io_error: io::Error, io_error: io::Error,
}, },
} }
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()?; while let [stripped @ .., c] = line {
if line.starts_with('/') { if c.is_ascii_whitespace() {
let path = PathBuf::from(line); line = stripped;
let key = path.file_name().unwrap().to_string_lossy().into_owned(); continue;
Some((key, path.to_string_lossy().to_string())) }
} else { break;
None
} }
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 +373,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 +399,18 @@ impl LdSoCache {
Ok(Self { entries }) Ok(Self { entries })
} }
fn resolve(&self, lib: &str) -> Option<&str> { 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 +432,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)?;

@ -100,6 +100,7 @@ impl Xdp {
/// [`XdpError::NetlinkError`] is returned for older /// [`XdpError::NetlinkError`] is returned for older
/// kernels. /// kernels.
pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result<XdpLinkId, ProgramError> { pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result<XdpLinkId, ProgramError> {
// TODO: avoid this unwrap by adding a new error variant.
let c_interface = CString::new(interface).unwrap(); let c_interface = CString::new(interface).unwrap();
let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) }; let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) };
if if_index == 0 { if if_index == 0 {

@ -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;

@ -1752,7 +1752,8 @@ pub enum aya::pin::PinError
pub aya::pin::PinError::AlreadyPinned pub aya::pin::PinError::AlreadyPinned
pub aya::pin::PinError::AlreadyPinned::name: alloc::string::String pub aya::pin::PinError::AlreadyPinned::name: alloc::string::String
pub aya::pin::PinError::InvalidPinPath pub aya::pin::PinError::InvalidPinPath
pub aya::pin::PinError::InvalidPinPath::error: alloc::string::String pub aya::pin::PinError::InvalidPinPath::error: alloc::ffi::c_str::NulError
pub aya::pin::PinError::InvalidPinPath::path: std::path::PathBuf
pub aya::pin::PinError::NoFd pub aya::pin::PinError::NoFd
pub aya::pin::PinError::NoFd::name: alloc::string::String pub aya::pin::PinError::NoFd::name: alloc::string::String
pub aya::pin::PinError::SyscallError(crate::sys::SyscallError) pub aya::pin::PinError::SyscallError(crate::sys::SyscallError)
@ -2860,7 +2861,7 @@ pub fn aya::programs::fexit::FExitLinkId::from(t: T) -> T
pub mod aya::programs::kprobe pub mod aya::programs::kprobe
pub enum aya::programs::kprobe::KProbeError pub enum aya::programs::kprobe::KProbeError
pub aya::programs::kprobe::KProbeError::FileError pub aya::programs::kprobe::KProbeError::FileError
pub aya::programs::kprobe::KProbeError::FileError::filename: alloc::string::String pub aya::programs::kprobe::KProbeError::FileError::filename: std::path::PathBuf
pub aya::programs::kprobe::KProbeError::FileError::io_error: std::io::error::Error pub aya::programs::kprobe::KProbeError::FileError::io_error: std::io::error::Error
impl core::convert::From<aya::programs::kprobe::KProbeError> for aya::programs::ProgramError impl core::convert::From<aya::programs::kprobe::KProbeError> for aya::programs::ProgramError
pub fn aya::programs::ProgramError::from(source: aya::programs::kprobe::KProbeError) -> Self pub fn aya::programs::ProgramError::from(source: aya::programs::kprobe::KProbeError) -> Self
@ -4330,7 +4331,7 @@ pub fn aya::programs::trace_point::TracePointLinkId::from(t: T) -> T
pub mod aya::programs::uprobe pub mod aya::programs::uprobe
pub enum aya::programs::uprobe::UProbeError pub enum aya::programs::uprobe::UProbeError
pub aya::programs::uprobe::UProbeError::FileError pub aya::programs::uprobe::UProbeError::FileError
pub aya::programs::uprobe::UProbeError::FileError::filename: alloc::string::String pub aya::programs::uprobe::UProbeError::FileError::filename: std::path::PathBuf
pub aya::programs::uprobe::UProbeError::FileError::io_error: std::io::error::Error pub aya::programs::uprobe::UProbeError::FileError::io_error: std::io::error::Error
pub aya::programs::uprobe::UProbeError::InvalidLdSoCache pub aya::programs::uprobe::UProbeError::InvalidLdSoCache
pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc<std::io::error::Error> pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc<std::io::error::Error>
@ -4814,7 +4815,7 @@ impl<T> core::convert::From<T> for aya::programs::extension::ExtensionError
pub fn aya::programs::extension::ExtensionError::from(t: T) -> T pub fn aya::programs::extension::ExtensionError::from(t: T) -> T
pub enum aya::programs::KProbeError pub enum aya::programs::KProbeError
pub aya::programs::KProbeError::FileError pub aya::programs::KProbeError::FileError
pub aya::programs::KProbeError::FileError::filename: alloc::string::String pub aya::programs::KProbeError::FileError::filename: std::path::PathBuf
pub aya::programs::KProbeError::FileError::io_error: std::io::error::Error pub aya::programs::KProbeError::FileError::io_error: std::io::error::Error
impl core::convert::From<aya::programs::kprobe::KProbeError> for aya::programs::ProgramError impl core::convert::From<aya::programs::kprobe::KProbeError> for aya::programs::ProgramError
pub fn aya::programs::ProgramError::from(source: aya::programs::kprobe::KProbeError) -> Self pub fn aya::programs::ProgramError::from(source: aya::programs::kprobe::KProbeError) -> Self
@ -5442,7 +5443,7 @@ impl<T> core::convert::From<T> for aya::programs::trace_point::TracePointError
pub fn aya::programs::trace_point::TracePointError::from(t: T) -> T pub fn aya::programs::trace_point::TracePointError::from(t: T) -> T
pub enum aya::programs::UProbeError pub enum aya::programs::UProbeError
pub aya::programs::UProbeError::FileError pub aya::programs::UProbeError::FileError
pub aya::programs::UProbeError::FileError::filename: alloc::string::String pub aya::programs::UProbeError::FileError::filename: std::path::PathBuf
pub aya::programs::UProbeError::FileError::io_error: std::io::error::Error pub aya::programs::UProbeError::FileError::io_error: std::io::error::Error
pub aya::programs::UProbeError::InvalidLdSoCache pub aya::programs::UProbeError::InvalidLdSoCache
pub aya::programs::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc<std::io::error::Error> pub aya::programs::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc<std::io::error::Error>
@ -6988,8 +6989,6 @@ pub aya::BpfError::BtfRelocationError(aya_obj::btf::relocation::BtfRelocationErr
pub aya::BpfError::FileError pub aya::BpfError::FileError
pub aya::BpfError::FileError::error: std::io::error::Error pub aya::BpfError::FileError::error: std::io::error::Error
pub aya::BpfError::FileError::path: std::path::PathBuf pub aya::BpfError::FileError::path: std::path::PathBuf
pub aya::BpfError::InvalidPath
pub aya::BpfError::InvalidPath::error: alloc::string::String
pub aya::BpfError::MapError(aya::maps::MapError) pub aya::BpfError::MapError(aya::maps::MapError)
pub aya::BpfError::NoBTF pub aya::BpfError::NoBTF
pub aya::BpfError::NoPinPath pub aya::BpfError::NoPinPath

Loading…
Cancel
Save