Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add an option to register allowed ranges of memory #110

Merged
merged 2 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions examples/allowed-memory/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
target = "bpfel-unknown-none"
rustflags = "-C debuginfo=2 -C link-arg=--btf"

[unstable]
build-std = ["core"]
31 changes: 31 additions & 0 deletions examples/allowed-memory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "allowed-memory"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0/MIT"

[dependencies]
aya-ebpf = "0.1.0"

[[bin]]
name = "allowed-memory"
path = "src/main.rs"
test = false
bench = false

[profile.dev]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = true
panic = "abort"
incremental = false
codegen-units = 1
rpath = false

[profile.release]
lto = true
panic = "abort"
codegen-units = 1

Binary file added examples/allowed-memory/allowed-memory.o
Binary file not shown.
13 changes: 13 additions & 0 deletions examples/allowed-memory/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[toolchain]
channel = "nightly"
# The source code of rustc, provided by the rust-src component, is needed for
# building eBPF programs.
components = [
"cargo",
"clippy",
"rust-docs",
"rust-src",
"rust-std",
"rustc",
"rustfmt",
]
39 changes: 39 additions & 0 deletions examples/allowed-memory/src/main.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() }
}
50 changes: 50 additions & 0 deletions examples/allowed_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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;

const OBJ_FILE_PATH: &str = "examples/allowed-memory/allowed-memory.o";

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(OBJ_FILE_PATH).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);
}
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
Loading