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

Add new syscall instruction to the verifier #620

Merged
merged 8 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion src/disassembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ pub fn disassemble_instruction<C: ContextObject>(
function_name
} else {
name = "syscall";
loader.get_function_registry().lookup_by_key(insn.imm as u32).map(|(function_name, _)| String::from_utf8_lossy(function_name).to_string()).unwrap_or_else(|| "[invalid]".to_string())
loader.get_function_registry(sbpf_version).lookup_by_key(insn.imm as u32).map(|(function_name, _)| String::from_utf8_lossy(function_name).to_string()).unwrap_or_else(|| "[invalid]".to_string())
};
desc = format!("{name} {function_name}");
},
Expand Down
7 changes: 5 additions & 2 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ impl<C: ContextObject> Executable<C> {
self.get_config(),
self.get_sbpf_version(),
self.get_function_registry(),
self.loader.get_function_registry(),
self.loader.get_function_registry(self.get_sbpf_version()),
)?;
Ok(())
}
Expand Down Expand Up @@ -1071,7 +1071,10 @@ impl<C: ContextObject> Executable<C> {
.entry(symbol.st_name)
.or_insert_with(|| ebpf::hash_symbol_name(name));
if config.reject_broken_elfs
&& loader.get_function_registry().lookup_by_key(hash).is_none()
&& loader
.get_function_registry(SBPFVersion::V1)
.lookup_by_key(hash)
.is_none()
{
return Err(ElfError::UnresolvedSymbol(
String::from_utf8_lossy(name).to_string(),
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
};

if external {
if let Some((_function_name, function)) = self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
if let Some((_function_name, function)) = self.executable.get_loader().get_function_registry(self.executable.get_sbpf_version()).lookup_by_key(insn.imm as u32) {
resolved = true;

self.vm.due_insn_count = self.vm.previous_instruction_meter - self.vm.due_insn_count;
Expand Down
2 changes: 1 addition & 1 deletion src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> {
};

if external {
if let Some((_function_name, function)) = self.executable.get_loader().get_function_registry().lookup_by_key(insn.imm as u32) {
if let Some((_function_name, function)) = self.executable.get_loader().get_function_registry(self.executable.get_sbpf_version()).lookup_by_key(insn.imm as u32) {
self.emit_validate_and_profile_instruction_count(false, Some(0));
self.emit_ins(X86Instruction::load_immediate(OperandSize::S64, REGISTER_SCRATCH, function as usize as i64));
self.emit_ins(X86Instruction::call_immediate(self.relative_to_anchor(ANCHOR_EXTERNAL_FUNCTION_CALL, 5)));
Expand Down
84 changes: 67 additions & 17 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ impl<T: Copy + PartialEq> FunctionRegistry<T> {
} else {
ebpf::hash_symbol_name(&usize::from(value).to_le_bytes())
};
if loader.get_function_registry().lookup_by_key(hash).is_some() {
if loader
.get_function_registry(SBPFVersion::V1)
.lookup_by_key(hash)
.is_some()
{
return Err(ElfError::SymbolHashCollision(hash));
}
hash
Expand Down Expand Up @@ -228,13 +232,17 @@ pub type BuiltinFunction<C> = fn(*mut EbpfVm<C>, u64, u64, u64, u64, u64);
pub struct BuiltinProgram<C: ContextObject> {
/// Holds the Config if this is a loader program
config: Option<Box<Config>>,
/// Function pointers by symbol
functions: FunctionRegistry<BuiltinFunction<C>>,
/// Function pointers by symbol with sparse indexing
sparse_registry: FunctionRegistry<BuiltinFunction<C>>,
/// Function pointers by symbol with dense indexing
dense_registry: FunctionRegistry<BuiltinFunction<C>>,
}

impl<C: ContextObject> PartialEq for BuiltinProgram<C> {
fn eq(&self, other: &Self) -> bool {
self.config.eq(&other.config) && self.functions.eq(&other.functions)
self.config.eq(&other.config)
&& self.sparse_registry.eq(&other.sparse_registry)
&& self.dense_registry.eq(&other.dense_registry)
}
}

Expand All @@ -243,23 +251,36 @@ impl<C: ContextObject> BuiltinProgram<C> {
pub fn new_loader(config: Config, functions: FunctionRegistry<BuiltinFunction<C>>) -> Self {
Self {
config: Some(Box::new(config)),
functions,
sparse_registry: functions,
dense_registry: FunctionRegistry::default(),
}
}

/// Constructs a built-in program
pub fn new_builtin(functions: FunctionRegistry<BuiltinFunction<C>>) -> Self {
Self {
config: None,
functions,
sparse_registry: functions,
dense_registry: FunctionRegistry::default(),
}
}

/// Constructs a mock loader built-in program
pub fn new_mock() -> Self {
Self {
config: Some(Box::default()),
functions: FunctionRegistry::default(),
sparse_registry: FunctionRegistry::default(),
dense_registry: FunctionRegistry::default(),
}
}

/// Create a new loader with both dense and sparse function indexation
/// Use `BuiltinProgram::register_function` for registrations.
pub fn new_loader_with_dense_registration(config: Config) -> Self {
Self {
config: Some(Box::new(config)),
sparse_registry: FunctionRegistry::default(),
dense_registry: FunctionRegistry::default(),
}
}

Expand All @@ -268,9 +289,16 @@ impl<C: ContextObject> BuiltinProgram<C> {
self.config.as_ref().unwrap()
}

/// Get the function registry
pub fn get_function_registry(&self) -> &FunctionRegistry<BuiltinFunction<C>> {
&self.functions
/// Get the function registry depending on the SBPF version
pub fn get_function_registry(
&self,
sbpf_version: SBPFVersion,
) -> &FunctionRegistry<BuiltinFunction<C>> {
if sbpf_version == SBPFVersion::V1 {
LucasSte marked this conversation as resolved.
Show resolved Hide resolved
&self.sparse_registry
} else {
&self.dense_registry
}
}

/// Calculate memory size
Expand All @@ -281,18 +309,40 @@ impl<C: ContextObject> BuiltinProgram<C> {
} else {
0
})
.saturating_add(self.functions.mem_size())
.saturating_add(self.sparse_registry.mem_size())
.saturating_add(self.dense_registry.mem_size())
}

/// Register a function both in the sparse and dense registries
pub fn register_function(
&mut self,
name: &str,
value: BuiltinFunction<C>,
LucasSte marked this conversation as resolved.
Show resolved Hide resolved
dense_key: u32,
) -> Result<(), ElfError> {
self.sparse_registry.register_function_hashed(name, value)?;
self.dense_registry
.register_function(dense_key, name, value)
}
}

impl<C: ContextObject> std::fmt::Debug for BuiltinProgram<C> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
writeln!(f, "{:?}", unsafe {
// `derive(Debug)` does not know that `C: ContextObject` does not need to implement `Debug`
std::mem::transmute::<&FunctionRegistry<BuiltinFunction<C>>, &FunctionRegistry<usize>>(
&self.functions,
)
})?;
unsafe {
writeln!(
f,
"sparse: {:?}\n dense: {:?}",
// `derive(Debug)` does not know that `C: ContextObject` does not need to implement `Debug`
std::mem::transmute::<
&FunctionRegistry<BuiltinFunction<C>>,
&FunctionRegistry<usize>,
>(&self.sparse_registry,),
std::mem::transmute::<
&FunctionRegistry<BuiltinFunction<C>>,
&FunctionRegistry<usize>,
>(&self.dense_registry,)
)?;
}
Ok(())
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/static_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
ebpf,
elf::Executable,
error::EbpfError,
program::SBPFVersion,
vm::{ContextObject, DynamicAnalysis, TestContextObject},
};
use rustc_demangle::demangle;
Expand Down Expand Up @@ -192,7 +193,7 @@ impl<'a> Analysis<'a> {
dfg_forward_edges: BTreeMap::new(),
dfg_reverse_edges: BTreeMap::new(),
};
result.split_into_basic_blocks(false);
result.split_into_basic_blocks(false, executable.get_sbpf_version());
result.control_flow_graph_tarjan();
result.control_flow_graph_dominance_hierarchy();
result.label_basic_blocks();
Expand Down Expand Up @@ -223,7 +224,7 @@ impl<'a> Analysis<'a> {
/// Splits the sequence of instructions into basic blocks
///
/// Also links the control-flow graph edges between the basic blocks.
pub fn split_into_basic_blocks(&mut self, flatten_call_graph: bool) {
pub fn split_into_basic_blocks(&mut self, flatten_call_graph: bool, sbpf_version: SBPFVersion) {
self.cfg_nodes.insert(0, CfgNode::default());
for pc in self.functions.keys() {
self.cfg_nodes.entry(*pc).or_default();
Expand All @@ -236,7 +237,7 @@ impl<'a> Analysis<'a> {
if let Some((function_name, _function)) = self
.executable
.get_loader()
.get_function_registry()
.get_function_registry(sbpf_version)
.lookup_by_key(insn.imm as u32)
{
if function_name == b"abort" {
Expand Down
22 changes: 18 additions & 4 deletions src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub enum VerifierError {
/// Invalid function
#[error("Invalid function at instruction {0}")]
InvalidFunction(usize),
/// Invalid syscall
#[error("Invalid syscall code {0}")]
InvalidSyscall(u32),
}

/// eBPF Verifier
Expand Down Expand Up @@ -157,6 +160,7 @@ fn check_jmp_offset(
fn check_call_target<T>(
key: u32,
function_registry: &FunctionRegistry<T>,
error: VerifierError,
) -> Result<(), VerifierError>
where
T: Copy,
Expand All @@ -165,7 +169,7 @@ where
function_registry
.lookup_by_key(key)
.map(|_| ())
.ok_or(VerifierError::InvalidFunction(key as usize))
.ok_or(error)
}

fn check_registers(
Expand Down Expand Up @@ -386,13 +390,23 @@ impl Verifier for RequisiteVerifier {
ebpf::JSLT_REG => { check_jmp_offset(prog, insn_ptr, &function_range)?; },
ebpf::JSLE_IMM => { check_jmp_offset(prog, insn_ptr, &function_range)?; },
ebpf::JSLE_REG => { check_jmp_offset(prog, insn_ptr, &function_range)?; },
ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src != 0 => { check_call_target(insn.imm as u32, function_registry)?; },
ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src == 0 => { check_call_target(insn.imm as u32, syscall_registry)?; },
ebpf::CALL_IMM if sbpf_version.static_syscalls() => {
check_call_target(
insn.imm as u32,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I noticed that this uses absolute addressing while the SIMD says: "immediate field must only be interpreted as a relative address to jump from the program counter". So, either one must be changed to match the other.

function_registry,
VerifierError::InvalidFunction(insn.imm as usize)
)?;
},
ebpf::CALL_IMM => {},
ebpf::CALL_REG => { check_callx_register(&insn, insn_ptr, sbpf_version)?; },
ebpf::EXIT if !sbpf_version.static_syscalls() => {},
ebpf::RETURN if sbpf_version.static_syscalls() => {},
ebpf::SYSCALL if sbpf_version.static_syscalls() => {},
ebpf::SYSCALL if sbpf_version.static_syscalls() => {
check_call_target(
insn.imm as u32,
syscall_registry,
VerifierError::InvalidSyscall(insn.imm as u32))?;
},

_ => {
return Err(VerifierError::UnknownOpCode(insn.opc, insn_ptr));
Expand Down
Loading