Skip to content
Open
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
8 changes: 4 additions & 4 deletions src/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,23 +429,23 @@ pub fn assemble<C: ContextObject>(
target_pc as usize,
)
.map_err(|_| format!("Label hash collision {name}"))?;
let instr_imm = if sbpf_version.static_syscalls() {
let instr_imm = if loader.get_config().enable_static_syscalls {
*imm
} else {
target_pc
};
insn(opc, 0, sbpf_version.static_syscalls() as i64, 0, instr_imm)
insn(opc, 0, 1, 0, instr_imm)
}
(CallImm, [Label(label)]) => {
let label: &str = label;
let mut target_pc = *labels
.get(label)
.ok_or_else(|| format!("Label not found {label}"))?
as i64;
if sbpf_version.static_syscalls() {
if loader.get_config().enable_static_syscalls {
target_pc = target_pc - insn_ptr as i64 - 1;
}
insn(opc, 0, sbpf_version.static_syscalls() as i64, 0, target_pc)
insn(opc, 0, 1, 0, target_pc)
}
(Syscall, [Label(label)]) => insn(
opc,
Expand Down
8 changes: 6 additions & 2 deletions src/disassembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,14 @@ pub fn disassemble_instruction<C: ContextObject>(
ebpf::JSLE64_IMM => { name = "jsle"; desc = jmp_imm_str(name, insn, cfg_nodes); },
ebpf::JSLE64_REG => { name = "jsle"; desc = jmp_reg_str(name, insn, cfg_nodes); },
ebpf::CALL_IMM => {
let key = sbpf_version.calculate_call_imm_target_pc(pc, insn.imm);
let key = if loader.get_config().enable_static_syscalls {
(pc as i64).saturating_add(insn.imm).saturating_add(1) as u32
} else {
insn.imm as u32
};
let mut name = "call";
let mut function_name = function_registry.lookup_by_key(key).map(|(function_name, _)| String::from_utf8_lossy(function_name).to_string());
if (function_name.is_none() && !sbpf_version.static_syscalls()) || insn.src == 0 {
if insn.src == 0 {
name = "syscall";
function_name = loader.get_function_registry().lookup_by_key(insn.imm as u32).map(|(function_name, _)| String::from_utf8_lossy(function_name).to_string());
}
Expand Down
26 changes: 17 additions & 9 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,7 @@ impl<C: ContextObject> Executable<C> {
let entry_pc = if let Some((_name, pc)) = function_registry.lookup_by_name(b"entrypoint") {
pc
} else {
function_registry.register_function_hashed_legacy(
&loader,
!sbpf_version.static_syscalls(),
*b"entrypoint",
0,
)?;
function_registry.register_function_hashed_legacy(&loader, false, *b"entrypoint", 0)?;
0
};
Ok(Self {
Expand Down Expand Up @@ -921,7 +916,9 @@ impl<C: ContextObject> Executable<C> {
let checked_slice = text_bytes
.get_mut(offset..offset.saturating_add(4))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, key);
if !loader.get_config().enable_static_syscalls {
LittleEndian::write_u32(checked_slice, key);
}
}
}

Expand Down Expand Up @@ -1097,8 +1094,19 @@ impl<C: ContextObject> Executable<C> {
as usize)
.checked_div(ebpf::INSN_SIZE)
.unwrap_or_default();
function_registry
.register_function_hashed_legacy(loader, true, name, target_pc)?
let key = function_registry
.register_function_hashed_legacy(loader, true, name, target_pc)?;
if config.enable_static_syscalls {
let insn_ptr = r_offset
.saturating_sub(text_section.file_range().unwrap_or_default().start)
.checked_div(ebpf::INSN_SIZE)
.unwrap_or_default();
(target_pc as u32)
.saturating_sub(insn_ptr as u32)
.saturating_sub(1)
} else {
key
}
} else {
// Else it's a syscall
let hash = *syscall_cache
Expand Down
31 changes: 21 additions & 10 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,16 +544,27 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
// Do not delegate the check to the verifier, since self.registered functions can be
// changed after the program has been verified.
ebpf::CALL_IMM => {
let key = self
.executable
.get_sbpf_version()
.calculate_call_imm_target_pc(self.reg[11] as usize, insn.imm);
if self.executable.get_sbpf_version().static_syscalls() && insn.src == 1 {
// make BPF to BPF call
if !self.push_frame(config) {
return false;
if config.enable_static_syscalls {
if insn.src == 1 {
// make BPF to BPF call
if !self.push_frame(config) {
return false;
}
let target_pc = (self.reg[11] as i64).saturating_add(insn.imm).saturating_add(1);
check_pc!(self, next_pc, target_pc as u64);
} else if insn.src == 0 {
// Static syscall
if let Some((_, function)) = self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
self.reg[0] = match self.dispatch_syscall(function) {
ProgramResult::Ok(value) => *value,
ProgramResult::Err(_err) => return false,
};
} else {
throw_error!(self, EbpfError::UnsupportedInstruction);
}
} else {
throw_error!(self, EbpfError::UnsupportedInstruction);
}
check_pc!(self, next_pc, key as u64);
} else if let Some((_, function)) = self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
// SBPFv0 syscall
self.reg[0] = match self.dispatch_syscall(function) {
Expand All @@ -563,7 +574,7 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
} else if let Some((_, target_pc)) =
self.executable
.get_function_registry()
.lookup_by_key(key) {
.lookup_by_key(insn.imm as u32) {
// make BPF to BPF call
if !self.push_frame(config) {
return false;
Expand Down
69 changes: 48 additions & 21 deletions src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,17 @@ const ANCHOR_THROW_EXCEPTION_UNCHECKED: usize = 3;
const ANCHOR_EXIT: usize = 4;
const ANCHOR_THROW_EXCEPTION: usize = 5;
const ANCHOR_CALL_DEPTH_EXCEEDED: usize = 6;
const ANCHOR_CALL_OUTSIDE_TEXT_SEGMENT: usize = 7;
const ANCHOR_DIV_BY_ZERO: usize = 8;
const ANCHOR_DIV_OVERFLOW: usize = 9;
const ANCHOR_CALL_REG_UNSUPPORTED_INSTRUCTION: usize = 10;
const ANCHOR_CALL_UNSUPPORTED_INSTRUCTION: usize = 11;
const ANCHOR_EXTERNAL_FUNCTION_CALL: usize = 12;
const ANCHOR_INTERNAL_FUNCTION_CALL_PROLOGUE: usize = 13;
const ANCHOR_INTERNAL_FUNCTION_CALL_REG: usize = 14;
const ANCHOR_TRANSLATE_MEMORY_ADDRESS: usize = 21;
const ANCHOR_COUNT: usize = 34; // Update me when adding or removing anchors
const ANCHOR_CALL_REG_OUTSIDE_TEXT_SEGMENT: usize = 7;
const ANCHOR_CALL_OUTSIDE_TEXT_SEGMENT: usize = 8;
const ANCHOR_DIV_BY_ZERO: usize = 9;
const ANCHOR_DIV_OVERFLOW: usize = 10;
const ANCHOR_CALL_REG_UNSUPPORTED_INSTRUCTION: usize = 11;
const ANCHOR_CALL_UNSUPPORTED_INSTRUCTION: usize = 12;
const ANCHOR_EXTERNAL_FUNCTION_CALL: usize = 13;
const ANCHOR_INTERNAL_FUNCTION_CALL_PROLOGUE: usize = 14;
const ANCHOR_INTERNAL_FUNCTION_CALL_REG: usize = 15;
const ANCHOR_TRANSLATE_MEMORY_ADDRESS: usize = 22;
const ANCHOR_COUNT: usize = 35; // Update me when adding or removing anchors

const REGISTER_MAP: [X86Register; 11] = [
CALLER_SAVED_REGISTERS[0], // RAX
Expand Down Expand Up @@ -814,21 +815,45 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> {
ebpf::JSLE64_REG => self.emit_conditional_branch_reg(OperandSize::S64, 0x8e, false, src, dst, target_pc),
ebpf::CALL_IMM => {
// For JIT, external functions MUST be registered at compile time.
let key = self
.executable
.get_sbpf_version()
.calculate_call_imm_target_pc(self.pc, insn.imm);
if self.executable.get_sbpf_version().static_syscalls() && insn.src == 1 {
// BPF to BPF call
self.emit_internal_call(Value::Constant64(key as i64, true));
if self.config.enable_static_syscalls {
if insn.src == 1 {
// BPF to BPF call
let target_pc = (self.pc as i64).saturating_add(insn.imm).saturating_add(1);
if (target_pc as usize)
.checked_mul(ebpf::INSN_SIZE)
.and_then(|offset| {
self
.program
.get(offset..offset.saturating_add(ebpf::INSN_SIZE))
})
.is_some()
{
self.emit_internal_call(Value::Constant64(target_pc as i64, true));
} else {
self.emit_ins(X86Instruction::load_immediate(REGISTER_SCRATCH, self.pc as i64));
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_CALL_OUTSIDE_TEXT_SEGMENT, 5)));
}
} else if insn.src == 0 {
if let Some((_, function)) =
self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
// Static syscall
self.emit_syscall_dispatch(function);
} else {
self.emit_ins(X86Instruction::load_immediate(REGISTER_SCRATCH, self.pc as i64));
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_CALL_UNSUPPORTED_INSTRUCTION, 5)));
}
} else {
self.emit_ins(X86Instruction::load_immediate(REGISTER_SCRATCH, self.pc as i64));
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_CALL_UNSUPPORTED_INSTRUCTION, 5)));
}
} else if let Some((_, function)) =
self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
// SBPFv0 syscall
self.emit_syscall_dispatch(function);
} else if let Some((_function_name, target_pc)) =
self.executable
.get_function_registry()
.lookup_by_key(key) {
.lookup_by_key(insn.imm as u32) {
// BPF to BPF call
self.emit_internal_call(Value::Constant64(target_pc as i64, true));
} else {
Expand Down Expand Up @@ -1491,10 +1516,12 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> {
self.emit_set_exception_kind(EbpfError::CallDepthExceeded);
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_THROW_EXCEPTION, 5)));

// Handler for EbpfError::CallOutsideTextSegment
// Handler for EbpfError::CallOutsideTextSegment from callx
self.set_anchor(ANCHOR_CALL_REG_OUTSIDE_TEXT_SEGMENT);
self.emit_ins(X86Instruction::load(OperandSize::S64, RSP, REGISTER_SCRATCH, X86IndirectAccess::OffsetIndexShift(-8, RSP, 0)));
// Handler for EbpfError::CallOutsideTextSegment from call
self.set_anchor(ANCHOR_CALL_OUTSIDE_TEXT_SEGMENT);
self.emit_set_exception_kind(EbpfError::CallOutsideTextSegment);
self.emit_ins(X86Instruction::load(OperandSize::S64, RSP, REGISTER_SCRATCH, X86IndirectAccess::OffsetIndexShift(-8, RSP, 0)));
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_THROW_EXCEPTION, 5)));

// Handler for EbpfError::DivideByZero
Expand Down Expand Up @@ -1588,7 +1615,7 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> {
// if(guest_target_pc >= number_of_instructions * INSN_SIZE) throw CALL_OUTSIDE_TEXT_SEGMENT;
let number_of_instructions = self.result.pc_section.len();
self.emit_ins(X86Instruction::cmp_immediate(OperandSize::S64, REGISTER_SCRATCH, (number_of_instructions * INSN_SIZE) as i64, None)); // guest_target_pc.cmp(number_of_instructions * INSN_SIZE)
self.emit_ins(X86Instruction::conditional_jump_immediate(0x83, self.relative_to_anchor(ANCHOR_CALL_OUTSIDE_TEXT_SEGMENT, 6)));
self.emit_ins(X86Instruction::conditional_jump_immediate(0x83, self.relative_to_anchor(ANCHOR_CALL_REG_OUTSIDE_TEXT_SEGMENT, 6)));
// Calculate the guest_target_pc (dst / INSN_SIZE) to update REGISTER_INSTRUCTION_METER
// and as target_pc for potential ANCHOR_CALL_REG_UNSUPPORTED_INSTRUCTION
let shift_amount = INSN_SIZE.trailing_zeros();
Expand Down
14 changes: 0 additions & 14 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ impl SBPFVersion {
self == SBPFVersion::V2
}

/// Enable SIMD-0178: SBPF Static Syscalls
pub fn static_syscalls(self) -> bool {
self >= SBPFVersion::V3
}
/// Enable SIMD-0179: SBPF stricter verification constraints
pub fn enable_stricter_verification(self) -> bool {
self >= SBPFVersion::V3
Expand All @@ -85,16 +81,6 @@ impl SBPFVersion {
pub fn enable_jmp32(self) -> bool {
self >= SBPFVersion::V3
}

/// Calculate the target program counter for a CALL_IMM instruction depending on
/// the SBPF version.
pub fn calculate_call_imm_target_pc(self, pc: usize, imm: i64) -> u32 {
if self.static_syscalls() {
(pc as i64).saturating_add(imm).saturating_add(1) as u32
} else {
imm as u32
}
}
}

/// Holds the function symbols of an Executable
Expand Down
29 changes: 15 additions & 14 deletions src/static_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,23 +240,24 @@ impl<'a> Analysis<'a> {
self.cfg_nodes.entry(*pc).or_default();
}
let mut cfg_edges = BTreeMap::new();
for (pc, insn) in self.instructions.iter().enumerate() {
for insn in self.instructions.iter() {
let target_pc = (insn.ptr as isize + insn.off as isize + 1) as usize;
match insn.opc {
ebpf::CALL_IMM => {
let key = sbpf_version.calculate_call_imm_target_pc(pc, insn.imm);
if let Some((_function_name, target_pc)) =
self.executable.get_function_registry().lookup_by_key(key)
{
self.cfg_nodes.entry(insn.ptr + 1).or_default();
self.cfg_nodes.entry(target_pc).or_default();
let destinations = if flatten_call_graph {
vec![insn.ptr + 1, target_pc]
} else {
vec![insn.ptr + 1]
};
cfg_edges.insert(insn.ptr, (insn.opc, destinations));
}
let target_pc = self
.executable
.get_function_registry()
.lookup_by_key(insn.imm as u32)
.map(|(_function_name, target_pc)| target_pc)
.unwrap_or(target_pc);
self.cfg_nodes.entry(insn.ptr + 1).or_default();
self.cfg_nodes.entry(target_pc).or_default();
let destinations = if flatten_call_graph {
vec![insn.ptr + 1, target_pc]
} else {
vec![insn.ptr + 1]
};
cfg_edges.insert(insn.ptr, (insn.opc, destinations));
}
ebpf::CALL_REG => {
// Abnormal CFG edge
Expand Down
17 changes: 2 additions & 15 deletions src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub struct RequisiteVerifier {}
impl Verifier for RequisiteVerifier {
/// Check the program against the verifier's rules
#[rustfmt::skip]
fn verify<C: ContextObject>(prog: &[u8], _config: &Config, sbpf_version: SBPFVersion, _function_registry: &FunctionRegistry<usize>, syscall_registry: &FunctionRegistry<BuiltinFunction<C>>) -> Result<(), VerifierError> {
fn verify<C: ContextObject>(prog: &[u8], _config: &Config, sbpf_version: SBPFVersion, _function_registry: &FunctionRegistry<usize>, _syscall_registry: &FunctionRegistry<BuiltinFunction<C>>) -> Result<(), VerifierError> {
check_prog_len(prog)?;

let program_range = 0..prog.len() / ebpf::INSN_SIZE;
Expand Down Expand Up @@ -424,20 +424,7 @@ impl Verifier for RequisiteVerifier {
| ebpf::JSLT64_REG
| ebpf::JSLE64_IMM
| ebpf::JSLE64_REG => { check_jmp_offset(prog, insn_ptr, &function_range)?; },
ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src == 1 => {
let target_pc = sbpf_version.calculate_call_imm_target_pc(insn_ptr, insn.imm);
if !program_range.contains(&(target_pc as usize)) ||
!ebpf::get_insn(prog, target_pc as usize).is_function_start_marker() {
return Err(VerifierError::InvalidFunction(target_pc as usize));
}
},
ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src == 0 => {
syscall_registry
.lookup_by_key(insn.imm as u32)
.map(|_| ())
.ok_or(VerifierError::InvalidSyscall(insn.imm as u32))?;
},
ebpf::CALL_IMM if !sbpf_version.static_syscalls() => {},
ebpf::CALL_IMM => {},
ebpf::CALL_REG => { check_callx_register(&insn, insn_ptr, sbpf_version)?; },
ebpf::EXIT => {},

Expand Down
3 changes: 3 additions & 0 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub struct Config {
pub optimize_rodata: bool,
/// Use aligned memory mapping
pub aligned_memory_mapping: bool,
/// Enable SIMD-0178 (src field distinguishes call and syscall)
pub enable_static_syscalls: bool,
/// Allowed [SBPFVersion]s
pub enabled_sbpf_versions: std::ops::RangeInclusive<SBPFVersion>,
}
Expand Down Expand Up @@ -112,6 +114,7 @@ impl Default for Config {
sanitize_user_provided_values: true,
optimize_rodata: true,
aligned_memory_mapping: true,
enable_static_syscalls: true,
enabled_sbpf_versions: SBPFVersion::V0..=SBPFVersion::V4,
}
}
Expand Down
Binary file modified tests/elfs/syscall_reloc_64_32_sbpfv0.so
Binary file not shown.
Loading