feat(aya-ebpf): introduce `array::OutOfBounds` error

Like regular arrays in Rust, arrays in eBPF are initialised upon
creation. Unlike regular arrays though, the memory is simply initialised
to all zeros without the user having to provide the array elements.

Like regular arrays, accessing the value at a specified index will yield
a reference as long as the index is within the bounds of the array.

At present, the API for `Array::get` returns an `Option` where the
`None` case signals the "out-of-bounds" case. Given that the user never
explicitly provided any values, the `None` case may be mistaked as "the
value has not been set" rather than "out of bounds".

To make this more obvious, we change the API to return a new
`OutOfBounds` error instead. This aligns the API with the user-space
equivalent of the `Array` struct.
reviewable/pr1253/r1
Thomas Eizinger 1 month ago
parent 0237e36dbe
commit 9f3a944f9b
No known key found for this signature in database
GPG Key ID: 05633CD77196CAF3

@ -1,4 +1,4 @@
use core::{cell::UnsafeCell, marker::PhantomData, mem, ptr::NonNull};
use core::{cell::UnsafeCell, fmt, marker::PhantomData, mem, ptr::NonNull};
use aya_ebpf_cty::c_long;
@ -14,6 +14,24 @@ pub struct Array<T> {
_t: PhantomData<T>,
}
#[derive(Debug)]
pub struct OutOfBounds {
length: u32,
index: u32,
}
impl core::error::Error for OutOfBounds {}
impl fmt::Display for OutOfBounds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Array index {} is out of bounds; length = {}",
self.index, self.length
)
}
}
unsafe impl<T: Sync> Sync for Array<T> {}
impl<T> Array<T> {
@ -48,24 +66,29 @@ impl<T> Array<T> {
}
#[inline(always)]
pub fn get(&self, index: u32) -> Option<&T> {
pub fn get(&self, index: u32) -> Result<&T, OutOfBounds> {
// FIXME: alignment
unsafe { self.lookup(index).map(|p| p.as_ref()) }
}
#[inline(always)]
pub fn get_ptr(&self, index: u32) -> Option<*const T> {
pub fn get_ptr(&self, index: u32) -> Result<*const T, OutOfBounds> {
unsafe { self.lookup(index).map(|p| p.as_ptr() as *const T) }
}
#[inline(always)]
pub fn get_ptr_mut(&self, index: u32) -> Option<*mut T> {
pub fn get_ptr_mut(&self, index: u32) -> Result<*mut T, OutOfBounds> {
unsafe { self.lookup(index).map(|p| p.as_ptr()) }
}
#[inline(always)]
unsafe fn lookup(&self, index: u32) -> Option<NonNull<T>> {
lookup(self.def.get(), &index)
unsafe fn lookup(&self, index: u32) -> Result<NonNull<T>, OutOfBounds> {
let map_def = self.def.get();
lookup(map_def, &index).ok_or(OutOfBounds {
length: unsafe { (*map_def).max_entries },
index,
})
}
/// Sets the value of the element at the given index.

@ -22,7 +22,7 @@ fn read_str_bytes(
let Some(ilen) = ilen else {
return;
};
let Some(ptr) = RESULT.get_ptr_mut(0) else {
let Ok(ptr) = RESULT.get_ptr_mut(0) else {
return;
};
let dst = unsafe { ptr.as_mut() };
@ -68,6 +68,7 @@ pub fn test_bpf_probe_read_kernel_str_bytes(ctx: ProbeContext) {
bpf_probe_read_kernel_str_bytes,
KERNEL_BUFFER
.get_ptr(0)
.ok()
.and_then(|ptr| unsafe { ptr.as_ref() })
.map(|buf| buf.as_ptr()),
ctx.arg::<usize>(0),

@ -29,7 +29,7 @@ static MAP_WITH_LOOOONG_NAAAAAAAAME: HashMap<u32, u8> = HashMap::<u32, u8>::with
#[socket_filter]
pub fn simple_prog(_ctx: SkBuffContext) -> i64 {
// So that these maps show up under the `map_ids` field.
FOO.get(0);
let _ = FOO.get(0);
// If we use the literal value `0` instead of the local variable `i`, then an additional
// `.rodata` map will be associated with the program.
let i = 0;

@ -18,7 +18,7 @@ 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) {
if let Ok(ptr) = RESULT.get_ptr_mut(0) {
unsafe {
(*ptr).common_type = common_type;
(*ptr).common_flags = common_flags;

@ -72,7 +72,7 @@ pub fn redirect_dev_chain(_ctx: XdpContext) -> u32 {
#[inline(always)]
fn inc_hit(index: u32) {
if let Some(hit) = HITS.get_ptr_mut(index) {
if let Ok(hit) = HITS.get_ptr_mut(index) {
unsafe { *hit += 1 };
}
}

@ -30,7 +30,7 @@ pub fn test_64_32_call_relocs(_ctx: ProbeContext) {
#[inline(never)]
fn set_result(index: u32, value: u64) {
unsafe {
if let Some(v) = RESULTS.get_ptr_mut(index) {
if let Ok(v) = RESULTS.get_ptr_mut(index) {
*v = value;
}
}

@ -21,7 +21,7 @@ pub fn test_bpf_strncmp(ctx: ProbeContext) -> Result<(), c_long> {
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 ptr = RESULT.get_ptr_mut(0).map_err(|_| -1)?;
let dst = unsafe { ptr.as_mut() };
let TestResult(dst_res) = dst.ok_or(-1)?;
*dst_res = bpf_strncmp(&b1, c"ff");

Loading…
Cancel
Save