From 0b58d3eb6d399c812181d2d64de32cde1b44f6eb Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 22 Jan 2024 09:47:29 +0100 Subject: [PATCH] bpf: Add `bpf_strncmp` helper The `bpf_strncmp` helper allows for better string comparison in eBPF programs. Added in https://github.com/torvalds/linux/commit/c5fb19937455095573a19. --- ebpf/aya-ebpf/src/helpers.rs | 26 +++++++++- test/integration-ebpf/Cargo.toml | 4 ++ test/integration-ebpf/src/strncmp.rs | 38 +++++++++++++++ test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + test/integration-test/src/tests/strncmp.rs | 55 ++++++++++++++++++++++ xtask/public-api/aya-ebpf.txt | 1 + 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 test/integration-ebpf/src/strncmp.rs create mode 100644 test/integration-test/src/tests/strncmp.rs diff --git a/ebpf/aya-ebpf/src/helpers.rs b/ebpf/aya-ebpf/src/helpers.rs index 6720bc72c..f96044f32 100644 --- a/ebpf/aya-ebpf/src/helpers.rs +++ b/ebpf/aya-ebpf/src/helpers.rs @@ -9,7 +9,11 @@ //! helpers, but also expose bindings to the underlying helpers as a fall-back //! in case of a missing implementation. -use core::mem::{self, MaybeUninit}; +use core::{ + cmp::Ordering, + ffi::CStr, + mem::{self, MaybeUninit}, +}; pub use aya_ebpf_bindings::helpers as gen; #[doc(hidden)] @@ -838,3 +842,23 @@ pub unsafe fn bpf_printk_impl( _ => gen::bpf_trace_vprintk(fmt_ptr, fmt_size, args.as_ptr() as _, (NUM_ARGS * 8) as _), } } + +/// Compares the given byte `s1` with a [`&CStr`](core::ffi::CStr) `s2`. +/// +/// # Examples +/// +/// ```no_run +/// # use aya_ebpf::helpers::bpf_strncmp; +/// # let data = b"something"; +/// assert_ne!(bpf_strncmp(data, c"foo"), core::cmp::Ordering::Equal); +/// ``` +#[inline] +pub fn bpf_strncmp(s1: &[u8; N], s2: &CStr) -> Ordering { + // NB: s1 does not need to be null-terminated. + // + // See https://github.com/torvalds/linux/blob/adc218676/include/uapi/linux/bpf.h#L5391-L5393. + // + // NB: s1's size must be known at compile time to appease the verifier. This is also the typical + // usage of strncmp in C programs. + unsafe { gen::bpf_strncmp(s1.as_ptr() as *const _, N as u32, s2.as_ptr() as *const _) }.cmp(&0) +} diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 542934670..6c550dae4 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -57,6 +57,10 @@ path = "src/ring_buf.rs" name = "simple_prog" path = "src/simple_prog.rs" +[[bin]] +name = "strncmp" +path = "src/strncmp.rs" + [[bin]] name = "tcx" path = "src/tcx.rs" diff --git a/test/integration-ebpf/src/strncmp.rs b/test/integration-ebpf/src/strncmp.rs new file mode 100644 index 000000000..975d9f0c4 --- /dev/null +++ b/test/integration-ebpf/src/strncmp.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use core::cmp::Ordering; + +use aya_ebpf::{ + cty::c_long, + helpers::{bpf_probe_read_user_str_bytes, bpf_strncmp}, + macros::{map, uprobe}, + maps::Array, + programs::ProbeContext, +}; + +#[repr(C)] +struct TestResult(Ordering); + +#[map] +static RESULT: Array = Array::with_max_entries(1, 0); + +#[uprobe] +pub fn test_bpf_strncmp(ctx: ProbeContext) -> Result<(), c_long> { + let s1: *const u8 = ctx.arg(0).ok_or(-1)?; + let mut b1 = [0u8; 3]; + let _: &[u8] = unsafe { bpf_probe_read_user_str_bytes(s1, &mut b1) }?; + + let ptr = RESULT.get_ptr_mut(0).ok_or(-1)?; + let dst = unsafe { ptr.as_mut() }; + let TestResult(dst_res) = dst.ok_or(-1)?; + *dst_res = bpf_strncmp(&b1, c"ff"); + + Ok(()) +} + +#[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 c185f69df..9d72286a9 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -23,6 +23,7 @@ pub const REDIRECT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/re 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")); pub const SIMPLE_PROG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/simple_prog")); +pub const STRNCMP: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/strncmp")); pub const TCX: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/tcx")); pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test")); pub const TWO_PROGS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/two_progs")); diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index 61f490cf0..4b5c8fcbb 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -8,5 +8,6 @@ mod rbpf; mod relocations; mod ring_buf; mod smoke; +mod strncmp; mod tcx; mod xdp; diff --git a/test/integration-test/src/tests/strncmp.rs b/test/integration-test/src/tests/strncmp.rs new file mode 100644 index 000000000..55254d9c5 --- /dev/null +++ b/test/integration-test/src/tests/strncmp.rs @@ -0,0 +1,55 @@ +use std::{ + cmp::Ordering, + ffi::{c_char, CStr}, +}; + +use aya::{ + maps::{Array, MapData}, + programs::UProbe, + Ebpf, +}; + +#[derive(Copy, Clone)] +#[repr(C)] +struct TestResult(Ordering); + +unsafe impl aya::Pod for TestResult {} + +#[test] +fn bpf_strncmp() { + let mut bpf = Ebpf::load(crate::STRNCMP).unwrap(); + + { + let prog: &mut UProbe = bpf + .program_mut("test_bpf_strncmp") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + + prog.attach(Some("trigger_bpf_strncmp"), 0, "/proc/self/exe", None) + .unwrap(); + } + + let array = Array::<_, TestResult>::try_from(bpf.map("RESULT").unwrap()).unwrap(); + + assert_eq!(do_bpf_strncmp(&array, c"ff"), Ordering::Equal); + + // This is truncated in BPF; the buffer size is 3 including the null terminator. + assert_eq!(do_bpf_strncmp(&array, c"fff"), Ordering::Equal); + + assert_eq!(do_bpf_strncmp(&array, c"aa"), Ordering::Less); + assert_eq!(do_bpf_strncmp(&array, c"zz"), Ordering::Greater); +} + +fn do_bpf_strncmp(array: &Array<&MapData, TestResult>, s1: &CStr) -> Ordering { + trigger_bpf_strncmp(s1.as_ptr()); + let TestResult(ord) = array.get(&0, 0).unwrap(); + ord +} + +#[no_mangle] +#[inline(never)] +pub extern "C" fn trigger_bpf_strncmp(s1: *const c_char) { + core::hint::black_box(s1); +} diff --git a/xtask/public-api/aya-ebpf.txt b/xtask/public-api/aya-ebpf.txt index 43b93bed8..7188dcb0b 100644 --- a/xtask/public-api/aya-ebpf.txt +++ b/xtask/public-api/aya-ebpf.txt @@ -76,6 +76,7 @@ pub unsafe fn aya_ebpf::helpers::bpf_probe_read_user_buf(src: *const u8, dst: &m pub unsafe fn aya_ebpf::helpers::bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> core::result::Result pub unsafe fn aya_ebpf::helpers::bpf_probe_read_user_str_bytes(src: *const u8, dest: &mut [u8]) -> core::result::Result<&[u8], aya_ebpf_cty::od::c_long> pub unsafe fn aya_ebpf::helpers::bpf_probe_write_user(dst: *mut T, src: *const T) -> core::result::Result<(), aya_ebpf_cty::od::c_long> +pub fn aya_ebpf::helpers::bpf_strncmp(s1: &[u8; N], s2: &core::ffi::c_str::CStr) -> core::cmp::Ordering pub mod aya_ebpf::maps pub mod aya_ebpf::maps::array #[repr(transparent)] pub struct aya_ebpf::maps::array::Array