From b9ad14933ff3343ba78a189b339226eaced05b24 Mon Sep 17 00:00:00 2001 From: Mike Rostecki Date: Tue, 3 Oct 2023 17:36:18 +0100 Subject: [PATCH] bpf: Handle raw tracepoint arguments Provide an `arg()` method in `RawTracepointArgs` wrapper of `bpf_raw_tracepoint_args` and also in `RawTracepointContext`, so it's directly available in raw tracepoint programs. The methods and traits implemented here are unsafe. There is no way to reliably check the number of available arguments, so requesting a non-existing one leads to undefined behavior. --- bpf/aya-bpf/src/args.rs | 109 +++++++++++++++++- bpf/aya-bpf/src/lib.rs | 2 +- bpf/aya-bpf/src/programs/raw_tracepoint.rs | 14 ++- test/integration-ebpf/Cargo.toml | 4 + test/integration-ebpf/src/raw_tracepoint.rs | 50 ++++++++ test/integration-test/src/lib.rs | 2 + test/integration-test/src/tests.rs | 1 + .../src/tests/raw_tracepoint.rs | 25 ++++ xtask/public-api/aya-bpf.txt | 27 +++++ 9 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 test/integration-ebpf/src/raw_tracepoint.rs create mode 100644 test/integration-test/src/tests/raw_tracepoint.rs diff --git a/bpf/aya-bpf/src/args.rs b/bpf/aya-bpf/src/args.rs index 7cee97a8..ecf04cb1 100644 --- a/bpf/aya-bpf/src/args.rs +++ b/bpf/aya-bpf/src/args.rs @@ -5,7 +5,7 @@ use crate::bindings::pt_regs; use crate::bindings::user_pt_regs as pt_regs; #[cfg(bpf_target_arch = "riscv64")] use crate::bindings::user_regs_struct as pt_regs; -use crate::{cty::c_void, helpers::bpf_probe_read}; +use crate::{bindings::bpf_raw_tracepoint_args, cty::c_void, helpers::bpf_probe_read}; /// A trait that indicates a valid type for an argument which can be coerced from a BTF /// context. @@ -323,3 +323,110 @@ impl_from_pt_regs!(i32); impl_from_pt_regs!(i64); impl_from_pt_regs!(usize); impl_from_pt_regs!(isize); + +/// A Rust wrapper on `bpf_raw_tracepoint_args`. +pub struct RawTracepointArgs { + args: *mut bpf_raw_tracepoint_args, +} + +impl RawTracepointArgs { + /// Creates a new instance of `RawTracepointArgs` from the given + /// `bpf_raw_tracepoint_args` raw pointer to allow easier access + /// to raw tracepoint argumetns. + pub fn new(args: *mut bpf_raw_tracepoint_args) -> Self { + RawTracepointArgs { args } + } + + /// Returns the n-th argument of the raw tracepoint. + /// + /// ## Safety + /// + /// This method is unsafe because it performs raw pointer conversion and makes assumptions + /// about the structure of the `bpf_raw_tracepoint_args` type. The tracepoint arguments are + /// represented as an array of `__u64` values. To be precise, the wrapped + /// `bpf_raw_tracepoint_args` binding defines it as `__IncompleteArrayField<__u64>` and the + /// original C type as `__u64 args[0]`. This method provides a way to access these arguments + /// conveniently in Rust using `__IncompleteArrayField::as_slice` to represent that array + /// as a slice of length n and then retrieve the n-th element of it. + /// + /// However, the method does not check the total number of available arguments for a given + /// tracepoint and assumes that the slice has at least `n` elements, leading to undefined + /// behavior if this condition is not met. Such check is impossible to do, because the + /// tracepoint context doesn't contain any information about number of arguments. + /// + /// This method also cannot guarantee that the requested type matches the actual value type. + /// Wrong assumptions about types can lead to undefined behavior. The tracepoint context + /// doesn't provide any type information. + /// + /// The caller is responsible for ensuring they have accurate knowledge of the arguments + /// and their respective types for the accessed tracepoint context. + pub unsafe fn arg(&self, n: usize) -> *const T { + &T::from_argument(&*self.args, n) + } +} + +pub unsafe trait FromRawTracepointArgs: Sized { + /// Returns the n-th argument of the raw tracepoint. + /// + /// ## Safety + /// + /// This method is unsafe because it performs raw pointer conversion and makes assumptions + /// about the structure of the `bpf_raw_tracepoint_args` type. The tracepoint arguments are + /// represented as an array of `__u64` values. To be precise, the wrapped + /// `bpf_raw_tracepoint_args` binding defines it as `__IncompleteArrayField<__u64>` and the + /// original C type as `__u64 args[0]`. This method provides a way to access these arguments + /// conveniently in Rust using `__IncompleteArrayField::as_slice` to represent that array + /// as a slice of length n and then retrieve the n-th element of it. + /// + /// However, the method does not check the total number of available arguments for a given + /// tracepoint and assumes that the slice has at least `n` elements, leading to undefined + /// behavior if this condition is not met. Such check is impossible to do, because the + /// tracepoint context doesn't contain any information about number of arguments. + /// + /// This method also cannot guarantee that the requested type matches the actual value type. + /// Wrong assumptions about types can lead to undefined behavior. The tracepoint context + /// doesn't provide any type information. + /// + /// The caller is responsible for ensuring they have accurate knowledge of the arguments + /// and their respective types for the accessed tracepoint context. + unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> Self; +} + +unsafe impl FromRawTracepointArgs for *const T { + unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> *const T { + // Raw tracepoint arguments are exposed as `__u64 args[0]`. + // https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/bpf.h#L6829 + // They are represented as `__IncompleteArrayField` in the Rust + // wraapper. + // + // The most convenient way of accessing such type in Rust is to use + // `__IncompleteArrayField::as_slice` to represent that array as a + // slice of length n and then retrieve the n-th element of it. + // + // We don't know how many arguments are there for the given tracepoint, + // so we just assume that the slice has at least n elements. The whole + // assumntion and implementation is unsafe. + ctx.args.as_slice(n + 1)[n] as *const _ + } +} + +macro_rules! unsafe_impl_from_raw_tracepoint_args { + ($type:ident) => { + unsafe impl FromRawTracepointArgs for $type { + unsafe fn from_argument(ctx: &bpf_raw_tracepoint_args, n: usize) -> Self { + ctx.args.as_slice(n + 1)[n] as _ + } + } + }; +} + +unsafe_impl_from_raw_tracepoint_args!(u8); +unsafe_impl_from_raw_tracepoint_args!(u16); +unsafe_impl_from_raw_tracepoint_args!(u32); +unsafe_impl_from_raw_tracepoint_args!(u64); +unsafe_impl_from_raw_tracepoint_args!(i8); +unsafe_impl_from_raw_tracepoint_args!(i16); +unsafe_impl_from_raw_tracepoint_args!(i32); +unsafe_impl_from_raw_tracepoint_args!(i64); +unsafe_impl_from_raw_tracepoint_args!(usize); +unsafe_impl_from_raw_tracepoint_args!(isize); diff --git a/bpf/aya-bpf/src/lib.rs b/bpf/aya-bpf/src/lib.rs index d482de0c..eb9bbdaf 100644 --- a/bpf/aya-bpf/src/lib.rs +++ b/bpf/aya-bpf/src/lib.rs @@ -22,7 +22,7 @@ pub use aya_bpf_bindings::bindings; mod args; -pub use args::PtRegs; +pub use args::{PtRegs, RawTracepointArgs}; pub mod helpers; pub mod maps; pub mod programs; diff --git a/bpf/aya-bpf/src/programs/raw_tracepoint.rs b/bpf/aya-bpf/src/programs/raw_tracepoint.rs index cc140244..57cb643d 100644 --- a/bpf/aya-bpf/src/programs/raw_tracepoint.rs +++ b/bpf/aya-bpf/src/programs/raw_tracepoint.rs @@ -1,19 +1,25 @@ use core::ffi::c_void; -use crate::BpfContext; +use crate::{args::FromRawTracepointArgs, bindings::bpf_raw_tracepoint_args, BpfContext}; pub struct RawTracePointContext { - ctx: *mut c_void, + ctx: *mut bpf_raw_tracepoint_args, } impl RawTracePointContext { pub fn new(ctx: *mut c_void) -> RawTracePointContext { - RawTracePointContext { ctx } + RawTracePointContext { + ctx: ctx as *mut bpf_raw_tracepoint_args, + } + } + + pub unsafe fn arg(&self, n: usize) -> T { + T::from_argument(&*self.ctx, n) } } impl BpfContext for RawTracePointContext { fn as_ptr(&self) -> *mut c_void { - self.ctx + self.ctx as *mut c_void } } diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index d471acf3..7978ff2f 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -55,3 +55,7 @@ path = "src/xdp_sec.rs" [[bin]] name = "ring_buf" path = "src/ring_buf.rs" + +[[bin]] +name = "raw_tracepoint" +path = "src/raw_tracepoint.rs" diff --git a/test/integration-ebpf/src/raw_tracepoint.rs b/test/integration-ebpf/src/raw_tracepoint.rs new file mode 100644 index 00000000..c8088daf --- /dev/null +++ b/test/integration-ebpf/src/raw_tracepoint.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use aya_bpf::{ + macros::{map, raw_tracepoint}, + maps::Array, + programs::RawTracePointContext, +}; + +#[map] +static RESULT: Array = Array::with_max_entries(1, 0); + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SysEnterEvent { + pub common_type: u16, + pub common_flags: u8, + _padding: u8, +} + +impl SysEnterEvent { + pub fn new(common_type: u16, common_flags: u8) -> Self { + Self { + common_type, + common_flags, + _padding: 0, + } + } +} + +#[raw_tracepoint(tracepoint = "sys_enter")] +pub fn sys_enter(ctx: RawTracePointContext) -> i32 { + let common_type: u16 = unsafe { ctx.arg(0) }; + let common_flags: u8 = unsafe { ctx.arg(1) }; + + if let Some(ptr) = RESULT.get_ptr_mut(0) { + unsafe { + (*ptr).common_type = common_type; + (*ptr).common_flags = common_flags; + } + } + + 0 +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index d4708033..cf9a759c 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -22,6 +22,8 @@ pub const BPF_PROBE_READ: &[u8] = pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/redirect")); pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec")); pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf")); +pub const RAW_TRACEPOINT: &[u8] = + include_bytes_aligned!(concat!(env!("OUT_DIR"), "/raw_tracepoint")); #[cfg(test)] mod tests; diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index f37d54bb..e06ff510 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -3,6 +3,7 @@ mod btf_relocations; mod elf; mod load; mod log; +mod raw_tracepoint; mod rbpf; mod relocations; mod ring_buf; diff --git a/test/integration-test/src/tests/raw_tracepoint.rs b/test/integration-test/src/tests/raw_tracepoint.rs new file mode 100644 index 00000000..2d34fde7 --- /dev/null +++ b/test/integration-test/src/tests/raw_tracepoint.rs @@ -0,0 +1,25 @@ +use aya::{maps::Array, programs::RawTracePoint, Bpf}; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SysEnterEvent { + pub common_type: u16, + pub common_flags: u8, + _padding: u8, +} + +unsafe impl aya::Pod for SysEnterEvent {} + +#[test] +fn raw_tracepoint() { + let mut bpf = Bpf::load(crate::RAW_TRACEPOINT).unwrap(); + let prog: &mut RawTracePoint = bpf.program_mut("sys_enter").unwrap().try_into().unwrap(); + prog.load().unwrap(); + prog.attach("sys_enter").unwrap(); + + let map: Array<_, SysEnterEvent> = Array::try_from(bpf.map_mut("RESULT").unwrap()).unwrap(); + let result = map.get(&0, 0).unwrap(); + + assert_ne!(result.common_type, 0); + assert_ne!(result.common_flags, 0); +} diff --git a/xtask/public-api/aya-bpf.txt b/xtask/public-api/aya-bpf.txt index ba5ea3d8..d95396da 100644 --- a/xtask/public-api/aya-bpf.txt +++ b/xtask/public-api/aya-bpf.txt @@ -1489,6 +1489,7 @@ pub fn aya_bpf::programs::probe::ProbeContext::from(t: T) -> T pub mod aya_bpf::programs::raw_tracepoint pub struct aya_bpf::programs::raw_tracepoint::RawTracePointContext impl aya_bpf::programs::raw_tracepoint::RawTracePointContext +pub unsafe fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::arg(&self, n: usize) -> T pub fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> aya_bpf::programs::raw_tracepoint::RawTracePointContext impl aya_bpf::BpfContext for aya_bpf::programs::raw_tracepoint::RawTracePointContext pub fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void @@ -2140,6 +2141,7 @@ impl core::convert::From for aya_bpf::programs::probe::ProbeContext pub fn aya_bpf::programs::probe::ProbeContext::from(t: T) -> T pub struct aya_bpf::programs::RawTracePointContext impl aya_bpf::programs::raw_tracepoint::RawTracePointContext +pub unsafe fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::arg(&self, n: usize) -> T pub fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> aya_bpf::programs::raw_tracepoint::RawTracePointContext impl aya_bpf::BpfContext for aya_bpf::programs::raw_tracepoint::RawTracePointContext pub fn aya_bpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void @@ -2540,6 +2542,31 @@ impl core::borrow::BorrowMut for aya_bpf::PtRegs where T: core::marker::Si pub fn aya_bpf::PtRegs::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya_bpf::PtRegs pub fn aya_bpf::PtRegs::from(t: T) -> T +pub struct aya_bpf::RawTracepointArgs +impl aya_bpf::RawTracepointArgs +pub unsafe fn aya_bpf::RawTracepointArgs::arg(&self, n: usize) -> *const T +pub fn aya_bpf::RawTracepointArgs::new(args: *mut aya_bpf_bindings::x86_64::bindings::bpf_raw_tracepoint_args) -> Self +impl !core::marker::Send for aya_bpf::RawTracepointArgs +impl !core::marker::Sync for aya_bpf::RawTracepointArgs +impl core::marker::Unpin for aya_bpf::RawTracepointArgs +impl core::panic::unwind_safe::RefUnwindSafe for aya_bpf::RawTracepointArgs +impl core::panic::unwind_safe::UnwindSafe for aya_bpf::RawTracepointArgs +impl core::convert::Into for aya_bpf::RawTracepointArgs where U: core::convert::From +pub fn aya_bpf::RawTracepointArgs::into(self) -> U +impl core::convert::TryFrom for aya_bpf::RawTracepointArgs where U: core::convert::Into +pub type aya_bpf::RawTracepointArgs::Error = core::convert::Infallible +pub fn aya_bpf::RawTracepointArgs::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya_bpf::RawTracepointArgs where U: core::convert::TryFrom +pub type aya_bpf::RawTracepointArgs::Error = >::Error +pub fn aya_bpf::RawTracepointArgs::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya_bpf::RawTracepointArgs where T: 'static + core::marker::Sized +pub fn aya_bpf::RawTracepointArgs::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya_bpf::RawTracepointArgs where T: core::marker::Sized +pub fn aya_bpf::RawTracepointArgs::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya_bpf::RawTracepointArgs where T: core::marker::Sized +pub fn aya_bpf::RawTracepointArgs::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya_bpf::RawTracepointArgs +pub fn aya_bpf::RawTracepointArgs::from(t: T) -> T pub const aya_bpf::TASK_COMM_LEN: usize = 16usize pub trait aya_bpf::BpfContext pub fn aya_bpf::BpfContext::as_ptr(&self) -> *mut core::ffi::c_void