diff --git a/src/elf.rs b/src/elf.rs index f907705c..072c8d5c 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -107,6 +107,9 @@ pub enum ElfError { /// Invalid program header #[error("Invalid ELF program header")] InvalidProgramHeader, + /// Invalid syscall code + #[error("Invalid function index")] + InvalidDenseFunctionIndex, } impl From for ElfError { diff --git a/src/program.rs b/src/program.rs index 1f817c02..8fe58d1c 100644 --- a/src/program.rs +++ b/src/program.rs @@ -3,7 +3,8 @@ use { crate::{ ebpf, elf::ElfError, - vm::{Config, ContextObject, EbpfVm}, + error::{EbpfError, ProgramResult}, + vm::{get_runtime_environment_key, Config, ContextObject, EbpfVm}, }, std::collections::{btree_map::Entry, BTreeMap}, }; @@ -85,14 +86,40 @@ impl SBPFVersion { /// Holds the function symbols of an Executable #[derive(Debug, PartialEq, Eq)] -pub struct FunctionRegistry { - pub(crate) map: BTreeMap, T)>, +pub enum FunctionRegistry { + /// Sparse allows the registration of hashed symbols + Sparse(BTreeMap, T)>), + /// Dense allows the registration of symbols indexed by integers only + Dense(Vec<(Vec, T)>), } impl Default for FunctionRegistry { + /// Default for FunctionRegistry returns a sparse registry fn default() -> Self { - Self { - map: BTreeMap::new(), + FunctionRegistry::Sparse(BTreeMap::new()) + } +} + +impl FunctionRegistry> { + /// Create a dense function registry with pre-allocated spaces. + pub fn new_dense(size: usize) -> Self { + FunctionRegistry::Dense(vec![(b"tombstone".to_vec(), SyscallTombstone::vm); size]) + } +} + +impl FunctionRegistry { + /// Unregister a symbol again + pub fn unregister_function(&mut self, key: u32) { + match self { + FunctionRegistry::Sparse(map) => { + map.remove(&key); + } + FunctionRegistry::Dense(vec) => { + if key == 0 || key > vec.len() as u32 { + return; + } + vec[key.saturating_sub(1) as usize] = (b"tombstone".to_vec(), T::default()); + } } } } @@ -105,16 +132,25 @@ impl FunctionRegistry { name: impl Into>, value: T, ) -> Result<(), ElfError> { - match self.map.entry(key) { - Entry::Vacant(entry) => { - entry.insert((name.into(), value)); - } - Entry::Occupied(entry) => { - if entry.get().1 != value { - return Err(ElfError::SymbolHashCollision(key)); + match self { + FunctionRegistry::Sparse(map) => match map.entry(key) { + Entry::Vacant(entry) => { + entry.insert((name.into(), value)); } + Entry::Occupied(entry) => { + if entry.get().1 != value { + return Err(ElfError::SymbolHashCollision(key)); + } + } + }, + FunctionRegistry::Dense(vec) => { + if key == 0 || key > vec.len() as u32 { + return Err(ElfError::InvalidDenseFunctionIndex); + } + vec[key.saturating_sub(1) as usize] = (name.into(), value); } } + Ok(()) } @@ -168,51 +204,80 @@ impl FunctionRegistry { Ok(key) } - /// Unregister a symbol again - pub fn unregister_function(&mut self, key: u32) { - self.map.remove(&key); - } - /// Iterate over all keys - pub fn keys(&self) -> impl Iterator + '_ { - self.map.keys().copied() + pub fn keys(&self) -> Box + '_> { + match self { + FunctionRegistry::Sparse(map) => Box::new(map.keys().copied()), + FunctionRegistry::Dense(vec) => Box::new(1..=(vec.len() as u32)), + } } /// Iterate over all entries - pub fn iter(&self) -> impl Iterator + '_ { - self.map - .iter() - .map(|(key, (name, value))| (*key, (name.as_slice(), *value))) + #[allow(clippy::type_complexity)] + pub fn iter(&self) -> Box + '_> { + match self { + FunctionRegistry::Sparse(map) => Box::new( + map.iter() + .map(|(key, (name, value))| (*key, (name.as_slice(), *value))), + ), + FunctionRegistry::Dense(vec) => { + Box::new(vec.iter().enumerate().map(|(idx, value)| { + (idx.saturating_add(1) as u32, (value.0.as_slice(), value.1)) + })) + } + } } /// Get a function by its key pub fn lookup_by_key(&self, key: u32) -> Option<(&[u8], T)> { - // String::from_utf8_lossy(function_name).as_str() - self.map - .get(&key) - .map(|(function_name, value)| (function_name.as_slice(), *value)) + match self { + FunctionRegistry::Sparse(map) => map + .get(&key) + .map(|(function_name, value)| (function_name.as_slice(), *value)), + FunctionRegistry::Dense(vec) => { + if key == 0 { + return None; + } + vec.get(key.saturating_sub(1) as usize) + .map(|(function_name, value)| (function_name.as_slice(), *value)) + } + } } /// Get a function by its name pub fn lookup_by_name(&self, name: &[u8]) -> Option<(&[u8], T)> { - self.map - .values() - .find(|(function_name, _value)| function_name == name) - .map(|(function_name, value)| (function_name.as_slice(), *value)) + match self { + FunctionRegistry::Sparse(map) => map + .values() + .find(|(function_name, _value)| function_name == name) + .map(|(function_name, value)| (function_name.as_slice(), *value)), + FunctionRegistry::Dense(vec) => vec + .iter() + .find(|(funtion_name, _)| funtion_name == name) + .map(|(function_name, value)| (function_name.as_slice(), *value)), + } } /// Calculate memory size pub fn mem_size(&self) -> usize { - std::mem::size_of::().saturating_add(self.map.iter().fold( - 0, - |state: usize, (_, (name, value))| { - state.saturating_add( - std::mem::size_of_val(value).saturating_add( + let self_size = std::mem::size_of::(); + let content_size = + match &self { + FunctionRegistry::Sparse(map) => { + map.iter().fold(0, |state: usize, (_, (name, value))| { + state.saturating_add(std::mem::size_of_val(value).saturating_add( + std::mem::size_of_val(name).saturating_add(name.capacity()), + )) + }) + } + FunctionRegistry::Dense(vec) => vec.iter().fold(0, |acc: usize, (name, value)| { + acc.saturating_add(std::mem::size_of_val(value).saturating_add( std::mem::size_of_val(name).saturating_add(name.capacity()), - ), - ) - }, - )) + )) + }), + }; + + self_size.saturating_add(content_size) } } @@ -350,6 +415,41 @@ macro_rules! declare_builtin_function { }; } +/// Tombstone function for when we call an invalid syscall (e.g. a syscall deactivated through a +/// feature gate +#[derive(Clone, PartialEq)] +pub struct SyscallTombstone {} +impl SyscallTombstone { + /// Tombstone function entrypoint + #[allow(clippy::arithmetic_side_effects)] + pub fn vm( + ctx: *mut EbpfVm, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + ) { + let vm = unsafe { + &mut *(ctx + .cast::() + .offset(-(get_runtime_environment_key() as isize)) + .cast::>()) + }; + let config = vm.loader.get_config(); + if config.enable_instruction_meter { + vm.context_object_pointer + .consume(vm.previous_instruction_meter - vm.due_insn_count); + } + vm.program_result = ProgramResult::Err(EbpfError::SyscallError(Box::new( + ElfError::InvalidDenseFunctionIndex, + ))); + if config.enable_instruction_meter { + vm.previous_instruction_meter = vm.context_object_pointer.get_remaining(); + } + } +} + #[cfg(test)] mod tests { use super::*;