diff --git a/ebpf/aya-ebpf/src/helpers.rs b/ebpf/aya-ebpf/src/helpers.rs index 60fcf0c3f..7dcddc35d 100644 --- a/ebpf/aya-ebpf/src/helpers.rs +++ b/ebpf/aya-ebpf/src/helpers.rs @@ -19,10 +19,7 @@ pub use aya_ebpf_bindings::helpers as generated; #[doc(hidden)] pub use generated::*; -use crate::{ - check_bounds_signed, - cty::{c_char, c_long}, -}; +use crate::cty::{c_char, c_long}; // TODO: Add a static assertion that `MAX_ERRNO`[0] fits in `i32`, to prove the // correctness of using `i32` as an error type and catch eventual changes of @@ -221,87 +218,25 @@ pub unsafe fn bpf_probe_read_kernel_buf(src: *const u8, dst: &mut [u8]) -> Resul if ret == 0 { Ok(()) } else { Err(ret as i32) } } -/// Read a null-terminated string stored at `src` into `dest`. -/// -/// Generally speaking, the more specific [`bpf_probe_read_user_str`] and -/// [`bpf_probe_read_kernel_str`] should be preferred over this function. -/// -/// In case the length of `dest` is smaller then the length of `src`, the read bytes will -/// be truncated to the size of `dest`. -/// -/// # Examples -/// -/// ```no_run -/// # #[expect(deprecated)] -/// # use aya_ebpf::{helpers::bpf_probe_read_str}; -/// # fn try_test() -> Result<(), i32> { -/// # let kernel_ptr: *const u8 = 0 as _; -/// let mut my_str = [0u8; 16]; -/// # #[expect(deprecated)] -/// let num_read = unsafe { bpf_probe_read_str(kernel_ptr, &mut my_str)? }; -/// -/// // Do something with num_read and my_str -/// # Ok::<(), i32>(()) -/// # } -/// ``` -/// -/// # Errors -/// -/// On failure, this function returns Err(-1). -#[deprecated( - note = "Use `bpf_probe_read_user_str_bytes` or `bpf_probe_read_kernel_str_bytes` instead" -)] -#[inline] -pub unsafe fn bpf_probe_read_str(src: *const u8, dest: &mut [u8]) -> Result { - let len = unsafe { - generated::bpf_probe_read_str(dest.as_mut_ptr().cast(), dest.len() as u32, src.cast()) - }; - let len = usize::try_from(len).map_err(|core::num::TryFromIntError { .. }| -1)?; - // this can never happen, it's needed to tell the verifier that len is bounded. - Ok(len.min(dest.len())) -} - -/// Read a null-terminated string from _user space_ stored at `src` into `dest`. -/// -/// In case the length of `dest` is smaller then the length of `src`, the read bytes will -/// be truncated to the size of `dest`. -/// -/// # Examples -/// -/// ```no_run -/// # #[expect(deprecated)] -/// # use aya_ebpf::{helpers::bpf_probe_read_user_str}; -/// # fn try_test() -> Result<(), i32> { -/// # let user_ptr: *const u8 = 0 as _; -/// let mut my_str = [0u8; 16]; -/// # #[expect(deprecated)] -/// let num_read = unsafe { bpf_probe_read_user_str(user_ptr, &mut my_str)? }; -/// -/// // Do something with num_read and my_str -/// # Ok::<(), i32>(()) -/// # } -/// ``` -/// -/// # Errors -/// -/// On failure, this function returns Err(-1). -#[deprecated(note = "Use `bpf_probe_read_user_str_bytes` instead")] -#[inline] -pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result { - let len = unsafe { - generated::bpf_probe_read_user_str(dest.as_mut_ptr().cast(), dest.len() as u32, src.cast()) - }; +fn read_str(dest: &mut [u8], f: impl FnOnce(&mut [u8]) -> i64) -> Result<&CStr, i32> { + let len = f(dest); let len = usize::try_from(len).map_err(|core::num::TryFromIntError { .. }| -1)?; - // this can never happen, it's needed to tell the verifier that len is bounded. - Ok(len.min(dest.len())) + // len includes the NULL terminator but not for b"\0" for which the kernel + // returns len=0. + let len = if len == 0 { 1 } else { len }; + let bytes = dest.get(..len).ok_or(-1)?; + // Successful bpf_probe_read_{user,kernel}_str calls always write a + // NUL-terminated destination. Avoid CStr::from_bytes_with_nul here: its + // validation scans with pointer-alignment arithmetic that older verifiers + // reject for map-value pointers. + Ok(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }) } -/// Returns a byte slice read from _user space_ address `src`. +/// Returns a C string read from _user space_ address `src`. /// /// Reads at most `dest.len()` bytes from the `src` address, truncating if the /// length of the source string is larger than `dest`. On success, the -/// destination buffer is always null terminated, and the returned slice -/// includes the bytes up to and not including NULL. +/// destination buffer is always null terminated. /// /// # Examples /// @@ -309,13 +244,13 @@ pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result /// eBPF stack limit is 512 bytes): /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_user_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_user_str}; /// # fn try_test() -> Result<(), i32> { /// # let user_ptr: *const u8 = 0 as _; /// let mut buf = [0u8; 16]; -/// let my_str_bytes = unsafe { bpf_probe_read_user_str_bytes(user_ptr, &mut buf)? }; +/// let my_str = unsafe { bpf_probe_read_user_str(user_ptr, &mut buf)? }; /// -/// // Do something with my_str_bytes +/// // Do something with my_str /// # Ok::<(), i32>(()) /// # } /// ``` @@ -323,7 +258,7 @@ pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result /// With a `PerCpuArray` (with size defined by us): /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_user_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_user_str}; /// use aya_ebpf::{macros::map, maps::PerCpuArray}; /// /// #[repr(C)] @@ -340,18 +275,17 @@ pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result /// let ptr = BUF.get_ptr_mut(0).ok_or(0)?; /// &mut *ptr /// }; -/// let my_str_bytes = unsafe { bpf_probe_read_user_str_bytes(user_ptr, &mut buf.buf)? }; +/// let my_str = unsafe { bpf_probe_read_user_str(user_ptr, &mut buf.buf)? }; /// -/// // Do something with my_str_bytes +/// // Do something with my_str /// # Ok::<(), i32>(()) /// # } /// ``` /// -/// You can also convert the resulted bytes slice into `&str` using -/// [`core::str::from_utf8_unchecked`]: +/// You can also access the non-null bytes using [`CStr::to_bytes`]: /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_user_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_user_str}; /// # use aya_ebpf::{macros::map, maps::PerCpuArray}; /// # #[repr(C)] /// # pub struct Buf { @@ -365,11 +299,9 @@ pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result /// # let ptr = BUF.get_ptr_mut(0).ok_or(0)?; /// # &mut *ptr /// # }; -/// let my_str = unsafe { -/// core::str::from_utf8_unchecked(bpf_probe_read_user_str_bytes(user_ptr, &mut buf.buf)?) -/// }; +/// let my_bytes = unsafe { bpf_probe_read_user_str(user_ptr, &mut buf.buf)? }.to_bytes(); /// -/// // Do something with my_str +/// // Do something with my_bytes /// # Ok::<(), i32>(()) /// # } /// ``` @@ -378,76 +310,17 @@ pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result /// /// On failure, this function returns Err(-1). #[inline] -pub unsafe fn bpf_probe_read_user_str_bytes(src: *const u8, dest: &mut [u8]) -> Result<&[u8], i32> { - let len = unsafe { +pub unsafe fn bpf_probe_read_user_str(src: *const u8, dest: &mut [u8]) -> Result<&CStr, i32> { + read_str(dest, |dest| unsafe { generated::bpf_probe_read_user_str(dest.as_mut_ptr().cast(), dest.len() as u32, src.cast()) - }; - - read_str_bytes(len, dest) -} - -fn read_str_bytes(len: i64, dest: &[u8]) -> Result<&[u8], i32> { - // The lower bound is 0, since it's what is returned for b"\0". See the - // bpf_probe_read_user_[user|kernel]_bytes_empty integration tests. The upper bound - // check is not needed since the helper truncates, but the verifier doesn't - // know that so we show it the upper bound. - if !check_bounds_signed(len, 0, dest.len() as i64) { - return Err(-1); - } - - // len includes the NULL terminator but not for b"\0" for which the kernel - // returns len=0. So we do a saturating sub and for b"\0" we return the - // empty slice, for all other cases we omit the terminator. - let len = usize::try_from(len).map_err(|core::num::TryFromIntError { .. }| -1)?; - let len = len.saturating_sub(1); - dest.get(..len).ok_or(-1) -} - -/// Read a null-terminated string from _kernel space_ stored at `src` into `dest`. -/// -/// In case the length of `dest` is smaller then the length of `src`, the read bytes will -/// be truncated to the size of `dest`. -/// -/// # Examples -/// -/// ```no_run -/// # #[expect(deprecated)] -/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str}; -/// # fn try_test() -> Result<(), i32> { -/// # let kernel_ptr: *const u8 = 0 as _; -/// let mut my_str = [0u8; 16]; -/// # #[expect(deprecated)] -/// let num_read = unsafe { bpf_probe_read_kernel_str(kernel_ptr, &mut my_str)? }; -/// -/// // Do something with num_read and my_str -/// # Ok::<(), i32>(()) -/// # } -/// ``` -/// -/// # Errors -/// -/// On failure, this function returns Err(-1). -#[deprecated(note = "Use bpf_probe_read_kernel_str_bytes instead")] -#[inline] -pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Result { - let len = unsafe { - generated::bpf_probe_read_kernel_str( - dest.as_mut_ptr().cast(), - dest.len() as u32, - src.cast(), - ) - }; - let len = usize::try_from(len).map_err(|core::num::TryFromIntError { .. }| -1)?; - // this can never happen, it's needed to tell the verifier that len is bounded. - Ok(len.min(dest.len())) + }) } -/// Returns a byte slice read from _kernel space_ address `src`. +/// Returns a C string read from _kernel space_ address `src`. /// /// Reads at most `dest.len()` bytes from the `src` address, truncating if the /// length of the source string is larger than `dest`. On success, the -/// destination buffer is always null terminated, and the returned slice -/// includes the bytes up to and not including NULL. +/// destination buffer is always null terminated. /// /// # Examples /// @@ -455,13 +328,13 @@ pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Resu /// eBPF stack limit is 512 bytes): /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str}; /// # fn try_test() -> Result<(), i32> { /// # let kernel_ptr: *const u8 = 0 as _; /// let mut buf = [0u8; 16]; -/// let my_str_bytes = unsafe { bpf_probe_read_kernel_str_bytes(kernel_ptr, &mut buf)? }; +/// let my_str = unsafe { bpf_probe_read_kernel_str(kernel_ptr, &mut buf)? }; /// -/// // Do something with my_str_bytes +/// // Do something with my_str /// # Ok::<(), i32>(()) /// # } /// ``` @@ -469,7 +342,7 @@ pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Resu /// With a `PerCpuArray` (with size defined by us): /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str}; /// use aya_ebpf::{macros::map, maps::PerCpuArray}; /// /// #[repr(C)] @@ -486,18 +359,17 @@ pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Resu /// let ptr = BUF.get_ptr_mut(0).ok_or(0)?; /// &mut *ptr /// }; -/// let my_str_bytes = unsafe { bpf_probe_read_kernel_str_bytes(kernel_ptr, &mut buf.buf)? }; +/// let my_str = unsafe { bpf_probe_read_kernel_str(kernel_ptr, &mut buf.buf)? }; /// -/// // Do something with my_str_bytes +/// // Do something with my_str /// # Ok::<(), i32>(()) /// # } /// ``` /// -/// You can also convert the resulted bytes slice into `&str` using -/// [`core::str::from_utf8_unchecked`]: +/// You can also access the non-null bytes using [`CStr::to_bytes`]: /// /// ```no_run -/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str_bytes}; +/// # use aya_ebpf::{helpers::bpf_probe_read_kernel_str}; /// # use aya_ebpf::{macros::map, maps::PerCpuArray}; /// # #[repr(C)] /// # pub struct Buf { @@ -511,11 +383,9 @@ pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Resu /// # let ptr = BUF.get_ptr_mut(0).ok_or(0)?; /// # &mut *ptr /// # }; -/// let my_str = unsafe { -/// core::str::from_utf8_unchecked(bpf_probe_read_kernel_str_bytes(kernel_ptr, &mut buf.buf)?) -/// }; +/// let my_bytes = unsafe { bpf_probe_read_kernel_str(kernel_ptr, &mut buf.buf)? }.to_bytes(); /// -/// // Do something with my_str +/// // Do something with my_bytes /// # Ok::<(), i32>(()) /// # } /// ``` @@ -524,19 +394,14 @@ pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Resu /// /// On failure, this function returns Err(-1). #[inline] -pub unsafe fn bpf_probe_read_kernel_str_bytes( - src: *const u8, - dest: &mut [u8], -) -> Result<&[u8], i32> { - let len = unsafe { +pub unsafe fn bpf_probe_read_kernel_str(src: *const u8, dest: &mut [u8]) -> Result<&CStr, i32> { + read_str(dest, |dest| unsafe { generated::bpf_probe_read_kernel_str( dest.as_mut_ptr().cast(), dest.len() as u32, src.cast(), ) - }; - - read_str_bytes(len, dest) + }) } /// Write bytes to the _user space_ pointer `src` and store them as a `T`. diff --git a/test/integration-ebpf/src/bpf_probe_read.rs b/test/integration-ebpf/src/bpf_probe_read.rs index dff67d156..40294eda4 100644 --- a/test/integration-ebpf/src/bpf_probe_read.rs +++ b/test/integration-ebpf/src/bpf_probe_read.rs @@ -2,8 +2,10 @@ #![no_main] #![expect(unused_crate_dependencies, reason = "used in other bins")] +use core::ffi::CStr; + use aya_ebpf::{ - helpers::{bpf_probe_read_kernel_str_bytes, bpf_probe_read_user_str_bytes}, + helpers::{bpf_probe_read_kernel_str, bpf_probe_read_user_str}, macros::{map, uprobe}, maps::Array, programs::ProbeContext, @@ -12,8 +14,8 @@ use integration_common::bpf_probe_read::{RESULT_BUF_LEN, TestResult}; #[cfg(not(test))] extern crate ebpf_panic; -fn read_str_bytes( - fun: unsafe fn(*const u8, &mut [u8]) -> Result<&[u8], i32>, +fn read_str( + fun: unsafe fn(*const u8, &mut [u8]) -> Result<&CStr, i32>, iptr: Option<*const u8>, ilen: Option, ) { @@ -45,7 +47,7 @@ fn read_str_bytes( return; }; - *len = Some(unsafe { fun(iptr, buf) }.map(<[_]>::len)); + *len = Some(unsafe { fun(iptr, buf) }.map(|s| s.to_bytes().len())); } #[map] @@ -55,18 +57,18 @@ static RESULT: Array = Array::with_max_entries(1, 0); static KERNEL_BUFFER: Array<[u8; RESULT_BUF_LEN]> = Array::with_max_entries(1, 0); #[uprobe] -fn test_bpf_probe_read_user_str_bytes(ctx: ProbeContext) { - read_str_bytes( - bpf_probe_read_user_str_bytes, +fn test_bpf_probe_read_user_str(ctx: ProbeContext) { + read_str( + bpf_probe_read_user_str, ctx.arg::<*const u8>(0), ctx.arg::(1), ); } #[uprobe] -fn test_bpf_probe_read_kernel_str_bytes(ctx: ProbeContext) { - read_str_bytes( - bpf_probe_read_kernel_str_bytes, +fn test_bpf_probe_read_kernel_str(ctx: ProbeContext) { + read_str( + bpf_probe_read_kernel_str, KERNEL_BUFFER .get_ptr(0) .and_then(|ptr| unsafe { ptr.as_ref() }) diff --git a/test/integration-ebpf/src/strncmp.rs b/test/integration-ebpf/src/strncmp.rs index 4e57dd7b7..1c3f54d24 100644 --- a/test/integration-ebpf/src/strncmp.rs +++ b/test/integration-ebpf/src/strncmp.rs @@ -4,7 +4,7 @@ use aya_ebpf::{ cty::c_long, - helpers::{bpf_probe_read_user_str_bytes, bpf_strncmp}, + helpers::{bpf_probe_read_user_str, bpf_strncmp}, macros::{map, uprobe}, maps::Array, programs::ProbeContext, @@ -20,7 +20,7 @@ static RESULT: Array = Array::with_max_entries(1, 0); 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 _: &core::ffi::CStr = unsafe { bpf_probe_read_user_str(s1, &mut b1) }?; let ptr = RESULT.get_ptr_mut(0).ok_or(-1)?; let dst = unsafe { ptr.as_mut() }; diff --git a/test/integration-test/src/tests/bpf_probe_read.rs b/test/integration-test/src/tests/bpf_probe_read.rs index b2c23edba..58999841d 100644 --- a/test/integration-test/src/tests/bpf_probe_read.rs +++ b/test/integration-test/src/tests/bpf_probe_read.rs @@ -2,13 +2,13 @@ use aya::{Ebpf, maps::Array, programs::UProbe}; use integration_common::bpf_probe_read::{RESULT_BUF_LEN, TestResult}; #[test_log::test] -fn bpf_probe_read_user_str_bytes() { +fn bpf_probe_read_user_str() { let bpf = set_user_buffer(b"foo\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b"foo"); } #[test_log::test] -fn bpf_probe_read_user_str_bytes_truncate() { +fn bpf_probe_read_user_str_truncate() { let s = vec![b'a'; RESULT_BUF_LEN]; let bpf = set_user_buffer(&s, RESULT_BUF_LEN); // The kernel truncates the string and the last byte is the null terminator @@ -16,25 +16,25 @@ fn bpf_probe_read_user_str_bytes_truncate() { } #[test_log::test] -fn bpf_probe_read_user_str_bytes_empty_string() { +fn bpf_probe_read_user_str_empty_string() { let bpf = set_user_buffer(b"\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b""); } #[test_log::test] -fn bpf_probe_read_user_str_bytes_empty_dest() { +fn bpf_probe_read_user_str_empty_dest() { let bpf = set_user_buffer(b"foo\0", 0); assert_eq!(result_bytes(&bpf), b""); } #[test_log::test] -fn bpf_probe_read_kernel_str_bytes() { +fn bpf_probe_read_kernel_str() { let bpf = set_kernel_buffer(b"foo\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b"foo"); } #[test_log::test] -fn bpf_probe_read_kernel_str_bytes_truncate() { +fn bpf_probe_read_kernel_str_truncate() { let s = vec![b'a'; RESULT_BUF_LEN]; let bpf = set_kernel_buffer(&s, RESULT_BUF_LEN); // The kernel truncates the string and the last byte is the null terminator @@ -42,20 +42,20 @@ fn bpf_probe_read_kernel_str_bytes_truncate() { } #[test_log::test] -fn bpf_probe_read_kernel_str_bytes_empty_string() { +fn bpf_probe_read_kernel_str_empty_string() { let bpf = set_kernel_buffer(b"\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b""); } #[test_log::test] -fn bpf_probe_read_kernel_str_bytes_empty_dest() { +fn bpf_probe_read_kernel_str_empty_dest() { let bpf = set_kernel_buffer(b"foo\0", 0); assert_eq!(result_bytes(&bpf), b""); } fn set_user_buffer(bytes: &[u8], dest_len: usize) -> Ebpf { let bpf = load_and_attach_uprobe( - "test_bpf_probe_read_user_str_bytes", + "test_bpf_probe_read_user_str", "trigger_bpf_probe_read_user", crate::BPF_PROBE_READ, ); @@ -65,7 +65,7 @@ fn set_user_buffer(bytes: &[u8], dest_len: usize) -> Ebpf { fn set_kernel_buffer(bytes: &[u8], dest_len: usize) -> Ebpf { let mut bpf = load_and_attach_uprobe( - "test_bpf_probe_read_kernel_str_bytes", + "test_bpf_probe_read_kernel_str", "trigger_bpf_probe_read_kernel", crate::BPF_PROBE_READ, );