Skip to content

Commit

Permalink
bpf: Handle raw tracepoint arguments
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vadorovsky authored and tamird committed Nov 21, 2024
1 parent 0b58d3e commit 2ff8470
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 6 deletions.
109 changes: 108 additions & 1 deletion ebpf/aya-ebpf/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::bindings::user_pt_regs as pt_regs;
// riscv64 uses user_regs_struct instead of 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.
Expand Down Expand Up @@ -418,3 +418,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<T>::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<T: FromRawTracepointArgs>(&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<T>::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<T> 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<T>` in the Rust
// wraapper.
//
// The most convenient way of accessing such type in Rust is to use
// `__IncompleteArrayField<T>::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);
2 changes: 1 addition & 1 deletion ebpf/aya-ebpf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
pub use aya_ebpf_bindings::bindings;

mod args;
pub use args::PtRegs;
pub use args::{PtRegs, RawTracepointArgs};
pub mod helpers;
pub mod maps;
pub mod programs;
Expand Down
14 changes: 10 additions & 4 deletions ebpf/aya-ebpf/src/programs/raw_tracepoint.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use core::ffi::c_void;

use crate::EbpfContext;
use crate::{args::FromRawTracepointArgs, bindings::bpf_raw_tracepoint_args, EbpfContext};

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<T: FromRawTracepointArgs>(&self, n: usize) -> T {
T::from_argument(&*self.ctx, n)
}
}

impl EbpfContext for RawTracePointContext {
fn as_ptr(&self) -> *mut c_void {
self.ctx
self.ctx as *mut c_void
}
}
4 changes: 4 additions & 0 deletions test/integration-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ path = "src/name_test.rs"
name = "pass"
path = "src/pass.rs"

[[bin]]
name = "raw_tracepoint"
path = "src/raw_tracepoint.rs"

[[bin]]
name = "redirect"
path = "src/redirect.rs"
Expand Down
50 changes: 50 additions & 0 deletions test/integration-ebpf/src/raw_tracepoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#![no_std]
#![no_main]

use aya_ebpf::{
macros::{map, raw_tracepoint},
maps::Array,
programs::RawTracePointContext,
};

#[map]
static RESULT: Array<SysEnterEvent> = 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 {}
}
2 changes: 2 additions & 0 deletions test/integration-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ma
pub const MEMMOVE_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/memmove_test"));
pub const NAME_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/name_test"));
pub const PASS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/pass"));
pub const RAW_TRACEPOINT: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/raw_tracepoint"));
pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/redirect"));
pub const RELOCATIONS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/relocations"));
pub const RING_BUF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ring_buf"));
Expand Down
1 change: 1 addition & 0 deletions test/integration-test/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod elf;
mod info;
mod load;
mod log;
mod raw_tracepoint;
mod rbpf;
mod relocations;
mod ring_buf;
Expand Down
25 changes: 25 additions & 0 deletions test/integration-test/src/tests/raw_tracepoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use aya::{maps::Array, programs::RawTracePoint, Ebpf};

#[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 = Ebpf::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);
}
28 changes: 28 additions & 0 deletions xtask/public-api/aya-ebpf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ pub fn aya_ebpf::programs::probe::ProbeContext::from(t: T) -> T
pub mod aya_ebpf::programs::raw_tracepoint
pub struct aya_ebpf::programs::raw_tracepoint::RawTracePointContext
impl aya_ebpf::programs::raw_tracepoint::RawTracePointContext
pub unsafe fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: FromRawTracepointArgs>(&self, n: usize) -> T
pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> aya_ebpf::programs::raw_tracepoint::RawTracePointContext
impl aya_ebpf::EbpfContext for aya_ebpf::programs::raw_tracepoint::RawTracePointContext
pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
Expand Down Expand Up @@ -2242,6 +2243,7 @@ impl<T> core::convert::From<T> for aya_ebpf::programs::probe::ProbeContext
pub fn aya_ebpf::programs::probe::ProbeContext::from(t: T) -> T
pub struct aya_ebpf::programs::RawTracePointContext
impl aya_ebpf::programs::raw_tracepoint::RawTracePointContext
pub unsafe fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::arg<T: FromRawTracepointArgs>(&self, n: usize) -> T
pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::new(ctx: *mut core::ffi::c_void) -> aya_ebpf::programs::raw_tracepoint::RawTracePointContext
impl aya_ebpf::EbpfContext for aya_ebpf::programs::raw_tracepoint::RawTracePointContext
pub fn aya_ebpf::programs::raw_tracepoint::RawTracePointContext::as_ptr(&self) -> *mut core::ffi::c_void
Expand Down Expand Up @@ -2685,6 +2687,32 @@ impl<T> core::borrow::BorrowMut<T> for aya_ebpf::PtRegs where T: ?core::marker::
pub fn aya_ebpf::PtRegs::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_ebpf::PtRegs
pub fn aya_ebpf::PtRegs::from(t: T) -> T
pub struct aya_ebpf::RawTracepointArgs
impl aya_ebpf::RawTracepointArgs
pub unsafe fn aya_ebpf::RawTracepointArgs::arg<T: FromRawTracepointArgs>(&self, n: usize) -> *const T
pub fn aya_ebpf::RawTracepointArgs::new(args: *mut aya_ebpf_bindings::x86_64::bindings::bpf_raw_tracepoint_args) -> Self
impl core::marker::Freeze for aya_ebpf::RawTracepointArgs
impl !core::marker::Send for aya_ebpf::RawTracepointArgs
impl !core::marker::Sync for aya_ebpf::RawTracepointArgs
impl core::marker::Unpin for aya_ebpf::RawTracepointArgs
impl core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::RawTracepointArgs
impl core::panic::unwind_safe::UnwindSafe for aya_ebpf::RawTracepointArgs
impl<T, U> core::convert::Into<U> for aya_ebpf::RawTracepointArgs where U: core::convert::From<T>
pub fn aya_ebpf::RawTracepointArgs::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya_ebpf::RawTracepointArgs where U: core::convert::Into<T>
pub type aya_ebpf::RawTracepointArgs::Error = core::convert::Infallible
pub fn aya_ebpf::RawTracepointArgs::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya_ebpf::RawTracepointArgs where U: core::convert::TryFrom<T>
pub type aya_ebpf::RawTracepointArgs::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya_ebpf::RawTracepointArgs::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> core::any::Any for aya_ebpf::RawTracepointArgs where T: 'static + ?core::marker::Sized
pub fn aya_ebpf::RawTracepointArgs::type_id(&self) -> core::any::TypeId
impl<T> core::borrow::Borrow<T> for aya_ebpf::RawTracepointArgs where T: ?core::marker::Sized
pub fn aya_ebpf::RawTracepointArgs::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya_ebpf::RawTracepointArgs where T: ?core::marker::Sized
pub fn aya_ebpf::RawTracepointArgs::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_ebpf::RawTracepointArgs
pub fn aya_ebpf::RawTracepointArgs::from(t: T) -> T
pub const aya_ebpf::TASK_COMM_LEN: usize
pub trait aya_ebpf::EbpfContext
pub fn aya_ebpf::EbpfContext::as_ptr(&self) -> *mut core::ffi::c_void
Expand Down

0 comments on commit 2ff8470

Please sign in to comment.