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 committed Sep 6, 2024
1 parent fe7021b commit 20cdbe1
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 8 deletions.
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: &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.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: &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)
};
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)
};

// Loop on instructions
Expand Down
158 changes: 157 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ pub struct EbpfVmMbuff<'a> {
#[cfg(feature = "cranelift")]
cranelift_prog: Option<cranelift::CraneliftProgram>,
helpers: HashMap<u32, ebpf::Helper>,
allowed: HashSet<u64>,
}

impl<'a> EbpfVmMbuff<'a> {
Expand Down Expand Up @@ -213,6 +214,7 @@ impl<'a> EbpfVmMbuff<'a> {
#[cfg(feature = "cranelift")]
cranelift_prog: None,
helpers: HashMap::new(),
allowed: HashSet::new(),
})
}

Expand Down Expand Up @@ -320,6 +322,46 @@ impl<'a> EbpfVmMbuff<'a> {
Ok(())
}

/// Register a set of addresses that the ebpf program is allowed to load and store.
///
/// When using certain helpers, typically map lookups, the linux kernel will return pointers
/// to structs that the ebpf program needs to interact with. By default rbpf only allows the
/// program to interact with its stack, the memory buffer and the program itself, making it
/// impossible to supply functional implementations of these helpers.
/// This option allows you to pass in a list of addresses that rbpf will allow the program
/// to load and store to. Given rust's memory model you will always know these addresses up
/// front when implementing the helpers.
///
/// Each invocation of this method will append to the set of allowed addresses.
///
/// # Examples
///
/// ```
/// use std::iter::FromIterator;
/// use std::ptr::addr_of;
///
/// struct MapValue {
/// data: u8
/// }
/// static VALUE: MapValue = MapValue { data: 1 };
///
/// let prog = &[
/// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
/// ];
///
/// // Instantiate a VM.
/// let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
/// let start = addr_of!(VALUE) as u64;
/// let addrs = Vec::from_iter(start..start+size_of::<MapValue>() as u64);
/// vm.register_allowed_memory(&addrs);
/// ```
pub fn register_allowed_memory(&mut self, addrs: &[u64]) -> () {
for i in addrs {
self.allowed.insert(*i);
}
}

/// Execute the program loaded, with the given packet data and metadata buffer.
///
/// If the program is made to be compatible with Linux kernel, it is expected to load the
Expand Down Expand Up @@ -357,7 +399,7 @@ impl<'a> EbpfVmMbuff<'a> {
/// assert_eq!(res, 0x2211);
/// ```
pub fn execute_program(&self, mem: &[u8], mbuff: &[u8]) -> Result<u64, Error> {
interpreter::execute_program(self.prog, mem, mbuff, &self.helpers)
interpreter::execute_program(self.prog, mem, mbuff, &self.helpers, &self.allowed)
}

/// JIT-compile the loaded program. No argument required for this.
Expand Down Expand Up @@ -826,6 +868,44 @@ impl<'a> EbpfVmFixedMbuff<'a> {
self.parent.register_helper(key, function)
}

/// Register an object that the ebpf program is allowed to load and store.
///
/// When using certain helpers, typically map lookups, the linux kernel will return pointers
/// to structs that the ebpf program needs to interact with. By default rbpf only allows the
/// program to interact with its stack, the memory buffer and the program itself, making it
/// impossible to supply functional implementations of these helpers.
/// This option allows you to pass in a list of addresses that rbpf will allow the program
/// to load and store to. Given rust's memory model you will always know these addresses up
/// front when implementing the helpers.
///
/// Each invocation of this method will append to the set of allowed addresses.
///
/// # Examples
///
/// ```
/// use std::iter::FromIterator;
/// use std::ptr::addr_of;
///
/// struct MapValue {
/// data: u8
/// }
/// static VALUE: MapValue = MapValue { data: 1 };
///
/// let prog = &[
/// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
/// ];
///
/// // Instantiate a VM.
/// let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
/// let start = addr_of!(VALUE) as u64;
/// let addrs = Vec::from_iter(start..start+size_of::<MapValue>() as u64);
/// vm.register_allowed_memory(&addrs);
/// ```
pub fn register_allowed_memory(&mut self, allowed: &[u64]) -> () {
self.parent.register_allowed_memory(allowed)
}

/// Execute the program loaded, with the given packet data.
///
/// If the program is made to be compatible with Linux kernel, it is expected to load the
Expand Down Expand Up @@ -1260,6 +1340,44 @@ impl<'a> EbpfVmRaw<'a> {
self.parent.register_helper(key, function)
}

/// Register an object that the ebpf program is allowed to load and store.
///
/// When using certain helpers, typically map lookups, the linux kernel will return pointers
/// to structs that the ebpf program needs to interact with. By default rbpf only allows the
/// program to interact with its stack, the memory buffer and the program itself, making it
/// impossible to supply functional implementations of these helpers.
/// This option allows you to pass in a list of addresses that rbpf will allow the program
/// to load and store to. Given rust's memory model you will always know these addresses up
/// front when implementing the helpers.
///
/// Each invocation of this method will append to the set of allowed addresses.
///
/// # Examples
///
/// ```
/// use std::iter::FromIterator;
/// use std::ptr::addr_of;
///
/// struct MapValue {
/// data: u8
/// }
/// static VALUE: MapValue = MapValue { data: 1 };
///
/// let prog = &[
/// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
/// ];
///
/// // Instantiate a VM.
/// let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
/// let start = addr_of!(VALUE) as u64;
/// let addrs = Vec::from_iter(start..start+size_of::<MapValue>() as u64);
/// vm.register_allowed_memory(&addrs);
/// ```
pub fn register_allowed_memory(&mut self, allowed: &[u64]) -> () {
self.parent.register_allowed_memory(allowed)
}

/// Execute the program loaded, with the given packet data.
///
/// # Examples
Expand Down Expand Up @@ -1602,6 +1720,44 @@ impl<'a> EbpfVmNoData<'a> {
self.parent.register_helper(key, function)
}

/// Register an object that the ebpf program is allowed to load and store.
///
/// When using certain helpers, typically map lookups, the linux kernel will return pointers
/// to structs that the ebpf program needs to interact with. By default rbpf only allows the
/// program to interact with its stack, the memory buffer and the program itself, making it
/// impossible to supply functional implementations of these helpers.
/// This option allows you to pass in a list of addresses that rbpf will allow the program
/// to load and store to. Given rust's memory model you will always know these addresses up
/// front when implementing the helpers.
///
/// Each invocation of this method will append to the set of allowed addresses.
///
/// # Examples
///
/// ```
/// use std::iter::FromIterator;
/// use std::ptr::addr_of;
///
/// struct MapValue {
/// data: u8
/// }
/// static VALUE: MapValue = MapValue { data: 1 };
///
/// let prog = &[
/// 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
/// ];
///
/// // Instantiate a VM.
/// let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
/// let start = addr_of!(VALUE) as u64;
/// let addrs = Vec::from_iter(start..start+size_of::<MapValue>() as u64);
/// vm.register_allowed_memory(&addrs);
/// ```
pub fn register_allowed_memory(&mut self, allowed: &[u64]) -> () {
self.parent.register_allowed_memory(allowed)
}

/// JIT-compile the loaded program. No argument required for this.
///
/// If using helper functions, be sure to register them into the VM before calling this
Expand Down

0 comments on commit 20cdbe1

Please sign in to comment.