Skip to content

Commit

Permalink
Allow helper function parameters to be on the stack (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmay authored Jan 12, 2019
1 parent 275e898 commit 47357f5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 54 deletions.
3 changes: 2 additions & 1 deletion src/ebpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! <https://www.kernel.org/doc/Documentation/networking/filter.txt>, or for a shorter version of
//! the list of the operation codes: <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md>
use RegionPtrs;
use std::io::Error;
use std::collections::hash_map::DefaultHasher;
use std::fmt;
Expand Down Expand Up @@ -395,7 +396,7 @@ pub const BPF_ALU_OP_MASK : u8 = 0xf0;
pub type HelperFunction = fn (u64, u64, u64, u64, u64) -> u64;

/// Prototype of an eBPF helper verification function.
pub type HelperVerifier = fn (u64, u64, u64, u64, u64, &[&[u8]], &[&[u8]]) -> Result<(()), Error>;
pub type HelperVerifier = fn (u64, u64, u64, u64, u64, &[RegionPtrs], &[RegionPtrs]) -> Result<(()), Error>;

/// eBPF Helper pair
///
Expand Down
106 changes: 56 additions & 50 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,31 @@ pub type Verifier = fn(prog: &[u8]) -> Result<(), Error>;
/// eBPF Jit-compiled program.
pub type JitProgram = unsafe fn(*mut u8, usize, *mut u8, usize, usize, usize) -> u64;

/// memory region for bounds checking
#[derive(Clone, Debug)]
pub struct RegionPtrs {
/// lower address of the memory region
pub bot: u64,
/// upper address of the memory region
pub top: u64,
}
impl RegionPtrs {
/// Creates a new RegionPtrs structure from a slice
pub fn new_from_slice(v: &[u8]) -> Self {
RegionPtrs {
bot: v.as_ptr() as u64,
top: v.as_ptr() as u64 + v .len() as u64,
}
}
}

/// One call frame
#[derive(Clone, Debug)]
struct CallFrame {
stack: Vec<u8>,
return_ptr: usize,
}

/// Stack top and bottom addresses as integers
#[derive(Clone, Debug)]
struct StackPtrs {
top: u64,
bot: u64
}

/// When BPF calls a function other then a `helper` it expect the new
/// function to be called in its own frame. CallFrames manages
/// call frames
Expand All @@ -89,12 +100,8 @@ impl CallFrames {
}

/// Get stack pointers
fn get_stack(&self) -> StackPtrs {
StackPtrs {
top: self.frames[self.current].stack.as_ptr() as u64 +
self.frames[self.current].stack.len() as u64,
bot: self.frames[self.current].stack.as_ptr() as u64
}
fn get_stack(&self) -> RegionPtrs {
RegionPtrs::new_from_slice(&self.frames[self.current].stack)
}

/// Get current call frame index, 0 is the root frame
Expand Down Expand Up @@ -466,16 +473,19 @@ impl<'a> EbpfVmMbuff<'a> {
let mut frames = CallFrames::new(ebpf::MAX_CALL_DEPTH, ebpf::STACK_SIZE);
let mut ro_regions = Vec::new();
let mut rw_regions = Vec::new();
ro_regions.push(mbuff);
rw_regions.push(mbuff);
ro_regions.push(mem);
rw_regions.push(mem);
ro_regions.push(frames.get_stack());
rw_regions.push(frames.get_stack());
ro_regions.push(RegionPtrs::new_from_slice(&mbuff));
rw_regions.push(RegionPtrs::new_from_slice(&mbuff));
ro_regions.push(RegionPtrs::new_from_slice(&mem));
rw_regions.push(RegionPtrs::new_from_slice(&mem));

let mut entry: usize = 0;
let prog =
if let Some(ref elf) = self.elf {
if let Ok(regions) = elf.get_rodata() {
ro_regions.extend(regions);
let ptrs: Vec<_> = regions.iter().map( |r| RegionPtrs::new_from_slice(r)).collect();
ro_regions.extend(ptrs);
}
entry = elf.get_entrypoint_instruction_offset()?;
elf.get_text_bytes()?
Expand All @@ -496,11 +506,11 @@ impl<'a> EbpfVmMbuff<'a> {
reg[1] = mem.as_ptr() as u64;
}

let check_mem_load = | addr: u64, len: usize, pc: usize, stack: &StackPtrs | {
EbpfVmMbuff::check_mem(addr, len, "load", pc, stack, &ro_regions)
let check_mem_load = | addr: u64, len: usize, pc: usize | {
EbpfVmMbuff::check_mem(addr, len, "load", pc, &ro_regions)
};
let check_mem_store = | addr: u64, len: usize, pc: usize, stack: &StackPtrs | {
EbpfVmMbuff::check_mem(addr, len, "store", pc, stack, &rw_regions)
let check_mem_store = | addr: u64, len: usize, pc: usize | {
EbpfVmMbuff::check_mem(addr, len, "store", pc, &rw_regions)
};

// Loop on instructions
Expand All @@ -525,42 +535,42 @@ impl<'a> EbpfVmMbuff<'a> {
// bother re-fetching it, just use mem already.
ebpf::LD_ABS_B => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u8;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_ABS_H => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u16;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_ABS_W => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u32;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_ABS_DW => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u64;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_IND_B => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u8;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_IND_H => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u16;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_IND_W => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u32;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},
ebpf::LD_IND_DW => reg[0] = unsafe {
let x = (mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u64;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},

Expand All @@ -574,75 +584,75 @@ impl<'a> EbpfVmMbuff<'a> {
ebpf::LD_B_REG => reg[_dst] = unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u8;
check_mem_load(x as u64, 1, pc, &frames.get_stack())?;
check_mem_load(x as u64, 1, pc)?;
*x as u64
},
ebpf::LD_H_REG => reg[_dst] = unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u16;
check_mem_load(x as u64, 2, pc, &frames.get_stack())?;
check_mem_load(x as u64, 2, pc)?;
*x as u64
},
ebpf::LD_W_REG => reg[_dst] = unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u32;
check_mem_load(x as u64, 4, pc, &frames.get_stack())?;
check_mem_load(x as u64, 4, pc)?;
*x as u64
},
ebpf::LD_DW_REG => reg[_dst] = unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u64;
check_mem_load(x as u64, 8, pc, &frames.get_stack())?;
check_mem_load(x as u64, 8, pc)?;
*x as u64
},

// BPF_ST class
ebpf::ST_B_IMM => unsafe {
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
check_mem_store(x as u64, 1, pc, &frames.get_stack())?;
check_mem_store(x as u64, 1, pc)?;
*x = insn.imm as u8;
},
ebpf::ST_H_IMM => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
check_mem_store(x as u64, 2, pc, &frames.get_stack())?;
check_mem_store(x as u64, 2, pc)?;
*x = insn.imm as u16;
},
ebpf::ST_W_IMM => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
check_mem_store(x as u64, 4, pc, &frames.get_stack())?;
check_mem_store(x as u64, 4, pc)?;
*x = insn.imm as u32;
},
ebpf::ST_DW_IMM => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
check_mem_store(x as u64, 8, pc, &frames.get_stack())?;
check_mem_store(x as u64, 8, pc)?;
*x = insn.imm as u64;
},

// BPF_STX class
ebpf::ST_B_REG => unsafe {
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
check_mem_store(x as u64, 1, pc, &frames.get_stack())?;
check_mem_store(x as u64, 1, pc)?;
*x = reg[_src] as u8;
},
ebpf::ST_H_REG => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
check_mem_store(x as u64, 2, pc, &frames.get_stack())?;
check_mem_store(x as u64, 2, pc)?;
*x = reg[_src] as u16;
},
ebpf::ST_W_REG => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
check_mem_store(x as u64, 4, pc, &frames.get_stack())?;
check_mem_store(x as u64, 4, pc)?;
*x = reg[_src] as u32;
},
ebpf::ST_DW_REG => unsafe {
#[allow(cast_ptr_alignment)]
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
check_mem_store(x as u64, 8, pc, &frames.get_stack())?;
check_mem_store(x as u64, 8, pc)?;
*x = reg[_src] as u64;
},
ebpf::ST_W_XADD => unimplemented!(),
Expand Down Expand Up @@ -810,13 +820,9 @@ impl<'a> EbpfVmMbuff<'a> {
unreachable!()
}

fn check_mem(addr: u64, len: usize, access_type: &str, pc: usize, stack: &StackPtrs, regions: &'a [&[u8]]) -> Result<(), Error> {
if stack.bot as u64 <= addr && addr + len as u64 <= stack.top {
return Ok(());
}

fn check_mem(addr: u64, len: usize, access_type: &str, pc: usize, regions: &'a [RegionPtrs]) -> Result<(), Error> {
for region in regions.iter() {
if region.as_ptr() as u64 <= addr && addr + len as u64 <= region.as_ptr() as u64 + region.len() as u64 {
if region.bot <= addr && addr + len as u64 <= region.top {
return Ok(());
}
}
Expand All @@ -825,7 +831,7 @@ impl<'a> EbpfVmMbuff<'a> {
if !regions.is_empty() {
regions_string = " regions".to_string();
for region in regions.iter() {
regions_string = format!("{} {:#x}/{:#x}", regions_string, region.as_ptr() as u64, region.len());
regions_string = format!("{} {:#x}/{:#x}", regions_string, region.bot, region.top - region.bot);
}
}

Expand Down Expand Up @@ -2101,7 +2107,7 @@ mod tests {
const DEPTH: usize = 5;
const SIZE: usize = 5;
let mut frames = CallFrames::new(DEPTH, SIZE);
let mut ptrs: Vec<StackPtrs> = Vec::new();
let mut ptrs: Vec<RegionPtrs> = Vec::new();
for i in 0..DEPTH - 1 {
println!("i: {:?}", i);
assert_eq!(frames.get_current_index(), i);
Expand Down
31 changes: 28 additions & 3 deletions tests/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use libc::c_char;
use solana_rbpf::assembler::assemble;
use solana_rbpf::ebpf;
use solana_rbpf::helpers;
use solana_rbpf::RegionPtrs;
use solana_rbpf::{EbpfVmRaw, EbpfVmNoData, EbpfVmMbuff, EbpfVmFixedMbuff};

// The following two examples have been compiled from C with the following command:
Expand Down Expand Up @@ -702,11 +703,12 @@ fn test_get_last_instruction_count() {

#[allow(unused_variables)]
pub fn bpf_helper_string_verify(addr: u64, unused2: u64, unused3: u64, unused4: u64,
unused5: u64, ro_regions: &[&[u8]], unused7: &[&[u8]]) -> Result<(()), Error> {
unused5: u64, ro_regions: &[RegionPtrs], unused7: &[RegionPtrs]) -> Result<(()), Error> {
for region in ro_regions.iter() {
if region.as_ptr() as u64 <= addr && addr as u64 <= region.as_ptr() as u64 + region.len() as u64 {
if region.bot <= addr && addr as u64 <= region.top {
let c_buf: *const c_char = addr as *const c_char;
let max_size = (region.as_ptr() as u64 + region.len() as u64) - addr;
let max_size = region.top - addr;
println!("max {:?} top {:?} bot {:?} addr {:?}", max_size, region.top, region.bot, addr);
unsafe {
for i in 0..max_size {
if std::ptr::read(c_buf.offset(i as isize)) == 0 {
Expand Down Expand Up @@ -767,6 +769,29 @@ fn test_symbol_relocation() {
vm.execute_program(&mut mem).unwrap();
}

#[test]
fn test_helper_parameter_on_stack() {
// let prog = assemble(
// "mov64 r1, r10 - 10
// exit",
// ).unwrap();
// println!("prog: {:?}", prog);
let prog = &mut [
0xbf, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = r10
0x07, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // r1 += -256
0x85, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, // call -1
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
LittleEndian::write_u32(&mut prog[20..24], ebpf::hash_symbol_name(b"log"));

let mut mem = [72, 101, 108, 108, 111, 0];

let mut vm = EbpfVmRaw::new(Some(prog)).unwrap();
vm.register_helper_ex("log", Some(bpf_helper_string_verify), bpf_helper_string).unwrap();
vm.execute_program(&mut mem).unwrap();
}

#[test]
#[should_panic(expected = "Error: Load segfault, bad string pointer")]
fn test_null_string() {
Expand Down

0 comments on commit 47357f5

Please sign in to comment.