Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Commit

Permalink
Add support for PC relative calls (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmay authored Jan 12, 2019
1 parent 96b97ef commit 154bd24
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 6 deletions.
121 changes: 119 additions & 2 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl EBpfElf {
if offset % ebpf::INSN_SIZE != 0 {
Err(Error::new(
ErrorKind::Other,
"Error: Entrypoint not multple of instruction size",
"Error: Entrypoint not multiple of instruction size",
))?
}
Ok(offset / ebpf::INSN_SIZE)
Expand Down Expand Up @@ -219,6 +219,26 @@ impl EBpfElf {
}
}

fn relocate_relative_calls(calls: &mut HashMap<u32, usize>, prog: &mut Vec<u8>) -> Result<(), Error> {
for i in 0..prog.len() / ebpf::INSN_SIZE {
let mut insn = ebpf::get_insn(prog, i);
if insn.opc == 0x85 && insn.imm != -1 {
let insn_idx = (i as i32 + 1 + insn.imm) as isize;
if insn_idx < 0 || insn_idx as usize >= prog.len() / ebpf::INSN_SIZE {
return Err(Error::new(
ErrorKind::Other,
format!("Error: Relative jump at instruction {} is out of bounds", insn_idx)
));
}
let hash = ebpf::hash_symbol_name(&[i as u8]); // use the instruction index as the key
insn.imm = hash as i32;
prog.splice(i * ebpf::INSN_SIZE..(i * ebpf::INSN_SIZE) + ebpf::INSN_SIZE, insn.to_vec());
calls.insert(hash, insn_idx as usize);
}
}
Ok(())
}

/// Validates the ELF
fn validate(&self) -> Result<(), Error> {
// Validate header
Expand Down Expand Up @@ -309,6 +329,8 @@ impl EBpfElf {
))?,
};

EBpfElf::relocate_relative_calls(&mut calls, &mut text_bytes)?;

for relocation in relocations.iter() {
match BPFRelocationType::from_x86_relocation_type(&relocation.rtype) {
Some(BPFRelocationType::R_BPF_64_RELATIVE) => {
Expand Down Expand Up @@ -547,7 +569,7 @@ mod test {
}

#[test]
#[should_panic(expected = "Error: Entrypoint not multple of instruction size")]
#[should_panic(expected = "Error: Entrypoint not multiple of instruction size")]
fn test_entrypoint_not_multiple_of_instruction_size() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
Expand All @@ -558,6 +580,101 @@ mod test {
elf.elf.header.entry = elf.elf.header.entry + ebpf::INSN_SIZE as u64 + 1 ;
elf.get_entrypoint_instruction_offset().unwrap();
}

#[test]
fn test_relocate_relative_calls_back() {
let mut calls: HashMap<u32, usize> = HashMap::new();
// call -2
let mut prog = vec![
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x85, 0x10, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff];

EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 4);

// // call +6
prog.splice(44.., vec![0xfa, 0xff, 0xff, 0xff]);
EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 0);
}

#[test]
fn test_relocate_relative_calls_forward() {
let mut calls: HashMap<u32, usize> = HashMap::new();
// call +0
let mut prog = vec![
0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 1);

// call +4
prog.splice(4..8, vec![0x04, 0x00, 0x00, 0x00]);
EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 5);
}

#[test]
#[should_panic(expected = "Error: Relative jump at instruction 6 is out of bounds")]
fn test_relocate_relative_calls_out_of_bounds_forward() {
let mut calls: HashMap<u32, usize> = HashMap::new();
// call +5
let mut prog = vec![
0x85, 0x10, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 1);
}

#[test]
#[should_panic(expected = "Error: Relative jump at instruction -1 is out of bounds")]
fn test_relocate_relative_calls_out_of_bounds_back() {
let mut calls: HashMap<u32, usize> = HashMap::new();
// call -7
let mut prog = vec![
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x85, 0x10, 0x00, 0x00, 0xf9, 0xff, 0xff, 0xff];

EBpfElf::relocate_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5]);
let insn = ebpf::Insn { opc: 0x85, dst: 0, src: 1, off: 0, imm: key as i32};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 4);
}
}



2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,8 @@ impl<'a> EbpfVmMbuff<'a> {
elf.report_unresolved_symbol(pc - 1)?;
}
} else {
// Note: Raw BPF programs (without ELF relocations) cannot support relative calls
// because there is no way to determine if the imm refers to a helper or an offset
Err(Error::new(ErrorKind::Other,
format!("Error: Unresolved symbol at instruction #{:?}", pc - 1)))?;
}
Expand Down
4 changes: 4 additions & 0 deletions tests/elfs/elfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ rm unresolved_helper.o
"$LLVM_DIR"ld.lld -z notext -shared --Bdynamic -entry entrypoint -o multiple_file.so entrypoint.o helper.o
rm entrypoint.o
rm helper.o

"$LLVM_DIR"clang -Werror -target bpf -O2 -fno-builtin -fPIC -o relative_call.o -c relative_call.c
"$LLVM_DIR"ld.lld -z notext -shared --Bdynamic -entry entrypoint -o relative_call.so relative_call.o
rm relative_call.o
4 changes: 4 additions & 0 deletions tests/elfs/entrypoint.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @brief test program that creates BPF to BPF calls
*/

typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;

Expand Down
5 changes: 4 additions & 1 deletion tests/elfs/helper.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

/**
* @brief Helper function used in the BPF to BPF call test
*/

typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;

Expand Down
3 changes: 1 addition & 2 deletions tests/elfs/helper.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/**
* @brief Example C-based BPF program that prints out the parameters
* passed to it
* @brief Helper function used in the BPF to BPF call test
*/

#pragma once
Expand Down
22 changes: 22 additions & 0 deletions tests/elfs/relative_call.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @brief test program that generates BPF PC relative call instructions
*/

typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;

extern void log(const char*);
extern void log_64(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);

uint64_t __attribute__ ((noinline)) helper(uint64_t x) {
log(__func__);
return x + 1;
}

extern uint64_t entrypoint(const uint8_t *input) {
uint64_t x = (uint64_t)*input;
log(__func__);
x = helper(x);
return x;
}

Binary file added tests/elfs/relative_call.so
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/elfs/unresolved_helper.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @brief test program
* @brief test program used to test unresolved helper handling
*/

typedef unsigned char uint8_t;
Expand Down
14 changes: 14 additions & 0 deletions tests/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,20 @@ fn test_bpf_to_bpf_too_deep() {
vm.execute_program(&mut mem).unwrap();
}

#[test]
fn test_relative_call() {
let mut file = File::open("tests/elfs/relative_call.so").expect("file open failed");
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();

let mut vm = EbpfVmRaw::new(None).unwrap();
vm.register_helper_ex("log", Some(bpf_helper_string_verify), bpf_helper_string).unwrap();
vm.set_elf(&elf).unwrap();

let mut mem = [1 as u8];
vm.execute_program(&mut mem).unwrap();
}




Expand Down

0 comments on commit 154bd24

Please sign in to comment.