mirror of https://github.com/aya-rs/aya
Merge pull request #634 from alessandrod/str-helpers-asm-bounds
bpf: improve bpf_probe_read_kernel_str_bytes and bpf_probe_read_user_str_bytesreviewable/pr638/r1
commit
4b8ffa40bb
@ -0,0 +1,91 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aya_bpf::{
|
||||
helpers::{bpf_probe_read_kernel_str_bytes, bpf_probe_read_user_str_bytes},
|
||||
macros::{map, uprobe},
|
||||
maps::Array,
|
||||
programs::ProbeContext,
|
||||
};
|
||||
|
||||
const RESULT_BUF_LEN: usize = 1024;
|
||||
|
||||
macro_rules! read_str_bytes {
|
||||
($fun:ident, $ptr:expr, $len:expr $(,)?) => {
|
||||
let r = unsafe {
|
||||
let Some(ptr) = RESULT.get_ptr_mut(0) else {
|
||||
return;
|
||||
};
|
||||
&mut *ptr
|
||||
};
|
||||
|
||||
let s = unsafe {
|
||||
// $len comes from ctx.arg(1) so it's dynamic and the verifier
|
||||
// doesn't see any bounds. We do $len.min(RESULT_BUF_LEN) here to
|
||||
// ensure that the verifier can see the upper bound, or you get:
|
||||
//
|
||||
// 18: (79) r7 = *(u64 *)(r7 +8) ; R7_w=scalar()
|
||||
// [snip]
|
||||
// 27: (bf) r2 = r7 ;
|
||||
// R2_w=scalar(id=2,umax=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) [snip]
|
||||
// 28: (85) call bpf_probe_read_user_str#114
|
||||
// R2 unbounded memory access, use 'var &= const' or 'if (var < const)'
|
||||
match $fun($ptr, &mut r.buf[..$len.min(RESULT_BUF_LEN)]) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
r.did_error = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
r.len = s.len();
|
||||
};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct TestResult {
|
||||
did_error: u64,
|
||||
len: usize,
|
||||
buf: [u8; RESULT_BUF_LEN],
|
||||
}
|
||||
|
||||
#[map]
|
||||
static RESULT: Array<TestResult> = Array::with_max_entries(1, 0);
|
||||
|
||||
#[map]
|
||||
static KERNEL_BUFFER: Array<[u8; 1024]> = Array::with_max_entries(1, 0);
|
||||
|
||||
#[uprobe]
|
||||
pub fn test_bpf_probe_read_user_str_bytes(ctx: ProbeContext) {
|
||||
read_str_bytes!(
|
||||
bpf_probe_read_user_str_bytes,
|
||||
match ctx.arg::<*const u8>(0) {
|
||||
Some(p) => p,
|
||||
_ => return,
|
||||
},
|
||||
match ctx.arg::<usize>(1) {
|
||||
Some(p) => p,
|
||||
_ => return,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[uprobe]
|
||||
pub fn test_bpf_probe_read_kernel_str_bytes(ctx: ProbeContext) {
|
||||
read_str_bytes!(
|
||||
bpf_probe_read_kernel_str_bytes,
|
||||
match KERNEL_BUFFER.get_ptr(0) {
|
||||
Some(p) => p as *const u8,
|
||||
_ => return,
|
||||
},
|
||||
match ctx.arg::<usize>(0) {
|
||||
Some(p) => p,
|
||||
_ => return,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
unsafe { core::hint::unreachable_unchecked() }
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
use std::process::exit;
|
||||
|
||||
use aya::{
|
||||
include_bytes_aligned,
|
||||
maps::Array,
|
||||
programs::{ProgramError, UProbe},
|
||||
Bpf,
|
||||
};
|
||||
use integration_test_macros::integration_test;
|
||||
|
||||
const RESULT_BUF_LEN: usize = 1024;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
struct TestResult {
|
||||
did_error: u64,
|
||||
len: usize,
|
||||
buf: [u8; RESULT_BUF_LEN],
|
||||
}
|
||||
|
||||
unsafe impl aya::Pod for TestResult {}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_user_str_bytes() {
|
||||
let bpf = set_user_buffer(b"foo\0", RESULT_BUF_LEN);
|
||||
assert_eq!(result_bytes(&bpf), b"foo");
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_user_str_bytes_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
|
||||
assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]);
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_user_str_bytes_empty_string() {
|
||||
let bpf = set_user_buffer(b"\0", RESULT_BUF_LEN);
|
||||
assert_eq!(result_bytes(&bpf), b"");
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_user_str_bytes_empty_dest() {
|
||||
let bpf = set_user_buffer(b"foo\0", 0);
|
||||
assert_eq!(result_bytes(&bpf), b"");
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_kernel_str_bytes() {
|
||||
let bpf = set_kernel_buffer(b"foo\0", RESULT_BUF_LEN);
|
||||
assert_eq!(result_bytes(&bpf), b"foo");
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_kernel_str_bytes_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
|
||||
assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]);
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_kernel_str_bytes_empty_string() {
|
||||
let bpf = set_kernel_buffer(b"\0", RESULT_BUF_LEN);
|
||||
assert_eq!(result_bytes(&bpf), b"");
|
||||
}
|
||||
|
||||
#[integration_test]
|
||||
fn bpf_probe_read_kernel_str_bytes_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) -> Bpf {
|
||||
let bpf = load_and_attach_uprobe(
|
||||
"test_bpf_probe_read_user_str_bytes",
|
||||
"trigger_bpf_probe_read_user",
|
||||
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/bpf_probe_read"),
|
||||
);
|
||||
trigger_bpf_probe_read_user(bytes.as_ptr(), dest_len);
|
||||
bpf
|
||||
}
|
||||
|
||||
fn set_kernel_buffer(bytes: &[u8], dest_len: usize) -> Bpf {
|
||||
let mut bpf = load_and_attach_uprobe(
|
||||
"test_bpf_probe_read_kernel_str_bytes",
|
||||
"trigger_bpf_probe_read_kernel",
|
||||
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/bpf_probe_read"),
|
||||
);
|
||||
set_kernel_buffer_element(&mut bpf, bytes);
|
||||
trigger_bpf_probe_read_kernel(dest_len);
|
||||
bpf
|
||||
}
|
||||
|
||||
fn set_kernel_buffer_element(bpf: &mut Bpf, bytes: &[u8]) {
|
||||
let mut bytes = bytes.to_vec();
|
||||
bytes.resize(1024, 0xFF);
|
||||
let bytes: [u8; 1024] = bytes.try_into().unwrap();
|
||||
let mut m = Array::<_, [u8; 1024]>::try_from(bpf.map_mut("KERNEL_BUFFER").unwrap()).unwrap();
|
||||
m.set(0, bytes, 0).unwrap();
|
||||
}
|
||||
|
||||
fn result_bytes(bpf: &Bpf) -> Vec<u8> {
|
||||
let m = Array::<_, TestResult>::try_from(bpf.map("RESULT").unwrap()).unwrap();
|
||||
let result = m.get(&0, 0).unwrap();
|
||||
assert!(result.did_error == 0);
|
||||
// assert that the buffer is always null terminated
|
||||
assert_eq!(result.buf[result.len], 0);
|
||||
result.buf[..result.len].to_vec()
|
||||
}
|
||||
|
||||
fn load_and_attach_uprobe(prog_name: &str, func_name: &str, bytes: &[u8]) -> Bpf {
|
||||
let mut bpf = Bpf::load(bytes).unwrap();
|
||||
|
||||
let prog: &mut UProbe = bpf.program_mut(prog_name).unwrap().try_into().unwrap();
|
||||
if let Err(ProgramError::LoadError {
|
||||
io_error,
|
||||
verifier_log,
|
||||
}) = prog.load()
|
||||
{
|
||||
println!(
|
||||
"Failed to load program `{prog_name}`: {io_error}. Verifier log:\n{verifier_log:#}"
|
||||
);
|
||||
exit(1);
|
||||
};
|
||||
|
||||
prog.attach(Some(func_name), 0, "/proc/self/exe", None)
|
||||
.unwrap();
|
||||
|
||||
bpf
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn trigger_bpf_probe_read_user(_string: *const u8, _len: usize) {}
|
||||
|
||||
#[no_mangle]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn trigger_bpf_probe_read_kernel(_len: usize) {}
|
Loading…
Reference in New Issue