Skip to content

Commit

Permalink
feat: Add an option to register allowed ranges of memory
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
wdullaer authored and qmonnet committed Sep 13, 2024
1 parent 31e118b commit 384c882
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 12 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ name = "to_json"

[[example]]
name = "rbpf_plugin"

[[example]]
name = "allowed_memory"
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ registers in a hashmap, so the key can be any `u32` value you want. It may be
useful for programs that should be compatible with the Linux kernel and
therefore must use specific helper numbers.

```rust,ignore
pub fn register_allowed_memory(&mut self,, addr: &[u64]) -> ()
```

This function adds a list of memory addresses that the eBPF program is allowed
to load and store. Multiple calls to this function will append the addresses to
an internal HashSet. At the moment rbpf only validates memory accesses when
using the interpreter. This function is useful when using kernel helpers which
return pointers to objects stored in eBPF maps.

```rust,ignore
// for struct EbpfVmMbuff
pub fn execute_program(&self,
Expand Down
Binary file added examples/allowed-memory.o
Binary file not shown.
57 changes: 57 additions & 0 deletions examples/allowed_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2024 Akenes SA <[email protected]>

#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal))]

extern crate elf;
use std::{iter::FromIterator, ptr::addr_of};

extern crate rbpf;

// The following example uses an ELF file that was compiled from the ebpf-allowed-memory.rs file
// It is built using the [aya framework](https://aya-rs.dev/).
// Once the aya dependencies (rust-nightly, latest llvm and latest bpf-linker) are installed, it
// can be compiled via
//
// ```bash
// cargo build --target=bpfel-unknown-none -Z build-std=core
// ```

const BPF_MAP_LOOKUP_ELEM_IDX: u32 = 1;

#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct Key {
pub protocol: u8,
}

#[repr(C, packed)]
pub struct Value {
pub result: i32,
}

static MAP_VALUE: Value = Value { result: 1 };

fn bpf_lookup_elem(_map: u64, key_addr: u64, _flags: u64, _u4: u64, _u5: u64) -> u64 {
let key: Key = unsafe { *(key_addr as *const Key) };
if key.protocol == 1 {
return addr_of!(MAP_VALUE) as u64;
}
0
}

fn main() {
let file = elf::File::open_path("examples/allowed-memory.o").unwrap();
let func = file.get_section("classifier").unwrap();

let mut vm = rbpf::EbpfVmNoData::new(Some(&func.data)).unwrap();
vm.register_helper(BPF_MAP_LOOKUP_ELEM_IDX, bpf_lookup_elem)
.unwrap();

let start = addr_of!(MAP_VALUE) as u64;
let addrs = Vec::from_iter(start..start + size_of::<Value>() as u64);
vm.register_allowed_memory(&addrs);

let res = vm.execute_program().unwrap();
assert_eq!(res, 1);
}
39 changes: 39 additions & 0 deletions examples/ebpf-allowed-memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
// Copyright 2024 Akenes SA <[email protected]>

#![no_std]
#![no_main]

use aya_ebpf::{
bindings::{BPF_F_NO_PREALLOC, TC_ACT_PIPE},
macros::{classifier, map},
maps::HashMap,
programs::TcContext,
};

#[map]
static RULES: HashMap<Key, Value> = HashMap::<Key, Value>::with_max_entries(1, BPF_F_NO_PREALLOC);

#[repr(C, packed)]
pub struct Key {
pub protocol: u8,
}

#[repr(C, packed)]
pub struct Value {
pub result: i32,
}

#[classifier]
pub fn ingress_tc(_ctx: TcContext) -> i32 {
let key = Key { protocol: 1 };
if let Some(action) = unsafe { RULES.get(&key) } {
return action.result;
}
return TC_ACT_PIPE;
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}
23 changes: 16 additions & 7 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ use ebpf;
use crate::lib::*;

fn check_mem(addr: u64, len: usize, access_type: &str, insn_ptr: usize,
mbuff: &[u8], mem: &[u8], stack: &[u8]) -> Result<(), Error> {
mbuff: &[u8], mem: &[u8], stack: &[u8], allowed_memory: &HashSet<u64>) -> Result<(), Error> {
if let Some(addr_end) = addr.checked_add(len as u64) {
if mbuff.as_ptr() as u64 <= addr && addr_end <= mbuff.as_ptr() as u64 + mbuff.len() as u64 {
return Ok(())
return Ok(());
}
if mem.as_ptr() as u64 <= addr && addr_end <= mem.as_ptr() as u64 + mem.len() as u64 {
return Ok(())
return Ok(());
}
if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
return Ok(())
return Ok(());
}
if allowed_memory.contains(&addr) {
return Ok(());
}
}

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

#[allow(unknown_lints)]
#[allow(cyclomatic_complexity)]
pub fn execute_program(prog_: Option<&[u8]>, mem: &[u8], mbuff: &[u8], helpers: &HashMap<u32, ebpf::Helper>) -> Result<u64, Error> {
pub fn execute_program(
prog_: Option<&[u8]>,
mem: &[u8],
mbuff: &[u8],
helpers: &HashMap<u32, ebpf::Helper>,
allowed_memory: &HashSet<u64>,
) -> Result<u64, Error> {
const U32MAX: u64 = u32::MAX as u64;
const SHIFT_MASK_64: u64 = 0x3f;

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

let check_mem_load = | addr: u64, len: usize, insn_ptr: usize | {
check_mem(addr, len, "load", insn_ptr, mbuff, mem, &stack)
check_mem(addr, len, "load", insn_ptr, mbuff, mem, &stack, allowed_memory)
};
let check_mem_store = | addr: u64, len: usize, insn_ptr: usize | {
check_mem(addr, len, "store", insn_ptr, mbuff, mem, &stack)
check_mem(addr, len, "store", insn_ptr, mbuff, mem, &stack, allowed_memory)
};

// Loop on instructions
Expand Down
Loading

0 comments on commit 384c882

Please sign in to comment.