Skip to content

Commit 384c882

Browse files
wdullaerqmonnet
authored andcommitted
feat: Add an option to register allowed ranges of memory
Certain kernel helpers return pointers to kernel managed memory, which the ebpf program is allowed to load and read. In order to implement stubs for these using rbpf, we need a way to mark these ranges of memory as safe for the check_mem function. The API specifically deals with adresses, because helpers need to be function types and not closures. This means the pointers to objects returned from them need to be static, and dealing with references to static objects gets clunky. So I have chosen to push the obtaining of the addresses into calling code. Signed-off-by: Wouter Dullaert <[email protected]>
1 parent 31e118b commit 384c882

File tree

7 files changed

+286
-12
lines changed

7 files changed

+286
-12
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ name = "to_json"
7878

7979
[[example]]
8080
name = "rbpf_plugin"
81+
82+
[[example]]
83+
name = "allowed_memory"

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ registers in a hashmap, so the key can be any `u32` value you want. It may be
209209
useful for programs that should be compatible with the Linux kernel and
210210
therefore must use specific helper numbers.
211211

212+
```rust,ignore
213+
pub fn register_allowed_memory(&mut self,, addr: &[u64]) -> ()
214+
```
215+
216+
This function adds a list of memory addresses that the eBPF program is allowed
217+
to load and store. Multiple calls to this function will append the addresses to
218+
an internal HashSet. At the moment rbpf only validates memory accesses when
219+
using the interpreter. This function is useful when using kernel helpers which
220+
return pointers to objects stored in eBPF maps.
221+
212222
```rust,ignore
213223
// for struct EbpfVmMbuff
214224
pub fn execute_program(&self,

examples/allowed-memory.o

45.3 KB
Binary file not shown.

examples/allowed_memory.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
2+
// Copyright 2024 Akenes SA <[email protected]>
3+
4+
#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal))]
5+
6+
extern crate elf;
7+
use std::{iter::FromIterator, ptr::addr_of};
8+
9+
extern crate rbpf;
10+
11+
// The following example uses an ELF file that was compiled from the ebpf-allowed-memory.rs file
12+
// It is built using the [aya framework](https://aya-rs.dev/).
13+
// Once the aya dependencies (rust-nightly, latest llvm and latest bpf-linker) are installed, it
14+
// can be compiled via
15+
//
16+
// ```bash
17+
// cargo build --target=bpfel-unknown-none -Z build-std=core
18+
// ```
19+
20+
const BPF_MAP_LOOKUP_ELEM_IDX: u32 = 1;
21+
22+
#[repr(C, packed)]
23+
#[derive(Clone, Copy)]
24+
pub struct Key {
25+
pub protocol: u8,
26+
}
27+
28+
#[repr(C, packed)]
29+
pub struct Value {
30+
pub result: i32,
31+
}
32+
33+
static MAP_VALUE: Value = Value { result: 1 };
34+
35+
fn bpf_lookup_elem(_map: u64, key_addr: u64, _flags: u64, _u4: u64, _u5: u64) -> u64 {
36+
let key: Key = unsafe { *(key_addr as *const Key) };
37+
if key.protocol == 1 {
38+
return addr_of!(MAP_VALUE) as u64;
39+
}
40+
0
41+
}
42+
43+
fn main() {
44+
let file = elf::File::open_path("examples/allowed-memory.o").unwrap();
45+
let func = file.get_section("classifier").unwrap();
46+
47+
let mut vm = rbpf::EbpfVmNoData::new(Some(&func.data)).unwrap();
48+
vm.register_helper(BPF_MAP_LOOKUP_ELEM_IDX, bpf_lookup_elem)
49+
.unwrap();
50+
51+
let start = addr_of!(MAP_VALUE) as u64;
52+
let addrs = Vec::from_iter(start..start + size_of::<Value>() as u64);
53+
vm.register_allowed_memory(&addrs);
54+
55+
let res = vm.execute_program().unwrap();
56+
assert_eq!(res, 1);
57+
}

examples/ebpf-allowed-memory.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
2+
// Copyright 2024 Akenes SA <[email protected]>
3+
4+
#![no_std]
5+
#![no_main]
6+
7+
use aya_ebpf::{
8+
bindings::{BPF_F_NO_PREALLOC, TC_ACT_PIPE},
9+
macros::{classifier, map},
10+
maps::HashMap,
11+
programs::TcContext,
12+
};
13+
14+
#[map]
15+
static RULES: HashMap<Key, Value> = HashMap::<Key, Value>::with_max_entries(1, BPF_F_NO_PREALLOC);
16+
17+
#[repr(C, packed)]
18+
pub struct Key {
19+
pub protocol: u8,
20+
}
21+
22+
#[repr(C, packed)]
23+
pub struct Value {
24+
pub result: i32,
25+
}
26+
27+
#[classifier]
28+
pub fn ingress_tc(_ctx: TcContext) -> i32 {
29+
let key = Key { protocol: 1 };
30+
if let Some(action) = unsafe { RULES.get(&key) } {
31+
return action.result;
32+
}
33+
return TC_ACT_PIPE;
34+
}
35+
36+
#[panic_handler]
37+
fn panic(_info: &core::panic::PanicInfo) -> ! {
38+
unsafe { core::hint::unreachable_unchecked() }
39+
}

src/interpreter.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ use ebpf;
99
use crate::lib::*;
1010

1111
fn check_mem(addr: u64, len: usize, access_type: &str, insn_ptr: usize,
12-
mbuff: &[u8], mem: &[u8], stack: &[u8]) -> Result<(), Error> {
12+
mbuff: &[u8], mem: &[u8], stack: &[u8], allowed_memory: &HashSet<u64>) -> Result<(), Error> {
1313
if let Some(addr_end) = addr.checked_add(len as u64) {
1414
if mbuff.as_ptr() as u64 <= addr && addr_end <= mbuff.as_ptr() as u64 + mbuff.len() as u64 {
15-
return Ok(())
15+
return Ok(());
1616
}
1717
if mem.as_ptr() as u64 <= addr && addr_end <= mem.as_ptr() as u64 + mem.len() as u64 {
18-
return Ok(())
18+
return Ok(());
1919
}
2020
if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
21-
return Ok(())
21+
return Ok(());
22+
}
23+
if allowed_memory.contains(&addr) {
24+
return Ok(());
2225
}
2326
}
2427

@@ -33,7 +36,13 @@ fn check_mem(addr: u64, len: usize, access_type: &str, insn_ptr: usize,
3336

3437
#[allow(unknown_lints)]
3538
#[allow(cyclomatic_complexity)]
36-
pub fn execute_program(prog_: Option<&[u8]>, mem: &[u8], mbuff: &[u8], helpers: &HashMap<u32, ebpf::Helper>) -> Result<u64, Error> {
39+
pub fn execute_program(
40+
prog_: Option<&[u8]>,
41+
mem: &[u8],
42+
mbuff: &[u8],
43+
helpers: &HashMap<u32, ebpf::Helper>,
44+
allowed_memory: &HashSet<u64>,
45+
) -> Result<u64, Error> {
3746
const U32MAX: u64 = u32::MAX as u64;
3847
const SHIFT_MASK_64: u64 = 0x3f;
3948

@@ -56,10 +65,10 @@ pub fn execute_program(prog_: Option<&[u8]>, mem: &[u8], mbuff: &[u8], helpers:
5665
}
5766

5867
let check_mem_load = | addr: u64, len: usize, insn_ptr: usize | {
59-
check_mem(addr, len, "load", insn_ptr, mbuff, mem, &stack)
68+
check_mem(addr, len, "load", insn_ptr, mbuff, mem, &stack, allowed_memory)
6069
};
6170
let check_mem_store = | addr: u64, len: usize, insn_ptr: usize | {
62-
check_mem(addr, len, "store", insn_ptr, mbuff, mem, &stack)
71+
check_mem(addr, len, "store", insn_ptr, mbuff, mem, &stack, allowed_memory)
6372
};
6473

6574
// Loop on instructions

0 commit comments

Comments
 (0)