solana_rbpf/
program.rs

1//! Common interface for built-in and user supplied programs
2use {
3    crate::{
4        ebpf,
5        elf::ElfError,
6        vm::{Config, ContextObject, EbpfVm},
7    },
8    std::collections::{btree_map::Entry, BTreeMap},
9};
10
11/// Defines a set of sbpf_version of an executable
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub enum SBPFVersion {
14    /// The legacy format
15    V1,
16    /// The current format
17    V2,
18    /// The future format with BTF support
19    V3,
20}
21
22impl SBPFVersion {
23    /// Enable the little-endian byte swap instructions
24    pub fn enable_le(&self) -> bool {
25        self == &SBPFVersion::V1
26    }
27
28    /// Enable the negation instruction
29    pub fn enable_neg(&self) -> bool {
30        self == &SBPFVersion::V1
31    }
32
33    /// Swaps the reg and imm operands of the subtraction instruction
34    pub fn swap_sub_reg_imm_operands(&self) -> bool {
35        self != &SBPFVersion::V1
36    }
37
38    /// Enable the only two slots long instruction: LD_DW_IMM
39    pub fn enable_lddw(&self) -> bool {
40        self == &SBPFVersion::V1
41    }
42
43    /// Enable the BPF_PQR instruction class
44    pub fn enable_pqr(&self) -> bool {
45        self != &SBPFVersion::V1
46    }
47
48    /// Use src reg instead of imm in callx
49    pub fn callx_uses_src_reg(&self) -> bool {
50        self != &SBPFVersion::V1
51    }
52
53    /// Ensure that rodata sections don't exceed their maximum allowed size and
54    /// overlap with the stack
55    pub fn reject_rodata_stack_overlap(&self) -> bool {
56        self != &SBPFVersion::V1
57    }
58
59    /// Allow sh_addr != sh_offset in elf sections. Used in V2 to align
60    /// section vaddrs to MM_PROGRAM_START.
61    pub fn enable_elf_vaddr(&self) -> bool {
62        self != &SBPFVersion::V1
63    }
64
65    /// Use dynamic stack frame sizes
66    pub fn dynamic_stack_frames(&self) -> bool {
67        self != &SBPFVersion::V1
68    }
69
70    /// Support syscalls via pseudo calls (insn.src = 0)
71    pub fn static_syscalls(&self) -> bool {
72        self != &SBPFVersion::V1
73    }
74}
75
76/// Holds the function symbols of an Executable
77#[derive(Debug, PartialEq, Eq)]
78pub struct FunctionRegistry<T> {
79    pub(crate) map: BTreeMap<u32, (Vec<u8>, T)>,
80}
81
82impl<T> Default for FunctionRegistry<T> {
83    fn default() -> Self {
84        Self {
85            map: BTreeMap::new(),
86        }
87    }
88}
89
90impl<T: Copy + PartialEq> FunctionRegistry<T> {
91    /// Register a symbol with an explicit key
92    pub fn register_function(
93        &mut self,
94        key: u32,
95        name: impl Into<Vec<u8>>,
96        value: T,
97    ) -> Result<(), ElfError> {
98        match self.map.entry(key) {
99            Entry::Vacant(entry) => {
100                entry.insert((name.into(), value));
101            }
102            Entry::Occupied(entry) => {
103                if entry.get().1 != value {
104                    return Err(ElfError::SymbolHashCollision(key));
105                }
106            }
107        }
108        Ok(())
109    }
110
111    /// Register a symbol with an implicit key
112    pub fn register_function_hashed(
113        &mut self,
114        name: impl Into<Vec<u8>>,
115        value: T,
116    ) -> Result<u32, ElfError> {
117        let name = name.into();
118        let key = ebpf::hash_symbol_name(name.as_slice());
119        self.register_function(key, name, value)?;
120        Ok(key)
121    }
122
123    /// Used for transitioning from SBPFv1 to SBPFv2
124    pub(crate) fn register_function_hashed_legacy<C: ContextObject>(
125        &mut self,
126        loader: &BuiltinProgram<C>,
127        hash_symbol_name: bool,
128        name: impl Into<Vec<u8>>,
129        value: T,
130    ) -> Result<u32, ElfError>
131    where
132        usize: From<T>,
133    {
134        let name = name.into();
135        let config = loader.get_config();
136        let key = if hash_symbol_name {
137            let hash = if name == b"entrypoint" {
138                ebpf::hash_symbol_name(b"entrypoint")
139            } else {
140                ebpf::hash_symbol_name(&usize::from(value).to_le_bytes())
141            };
142            if config.external_internal_function_hash_collision
143                && loader.get_function_registry().lookup_by_key(hash).is_some()
144            {
145                return Err(ElfError::SymbolHashCollision(hash));
146            }
147            hash
148        } else {
149            usize::from(value) as u32
150        };
151        self.register_function(
152            key,
153            if config.enable_symbol_and_section_labels || name == b"entrypoint" {
154                name
155            } else {
156                Vec::default()
157            },
158            value,
159        )?;
160        Ok(key)
161    }
162
163    /// Unregister a symbol again
164    pub fn unregister_function(&mut self, key: u32) {
165        self.map.remove(&key);
166    }
167
168    /// Iterate over all keys
169    pub fn keys(&self) -> impl Iterator<Item = u32> + '_ {
170        self.map.keys().copied()
171    }
172
173    /// Iterate over all entries
174    pub fn iter(&self) -> impl Iterator<Item = (u32, (&[u8], T))> + '_ {
175        self.map
176            .iter()
177            .map(|(key, (name, value))| (*key, (name.as_slice(), *value)))
178    }
179
180    /// Get a function by its key
181    pub fn lookup_by_key(&self, key: u32) -> Option<(&[u8], T)> {
182        // String::from_utf8_lossy(function_name).as_str()
183        self.map
184            .get(&key)
185            .map(|(function_name, value)| (function_name.as_slice(), *value))
186    }
187
188    /// Get a function by its name
189    pub fn lookup_by_name(&self, name: &[u8]) -> Option<(&[u8], T)> {
190        self.map
191            .values()
192            .find(|(function_name, _value)| function_name == name)
193            .map(|(function_name, value)| (function_name.as_slice(), *value))
194    }
195
196    /// Calculate memory size
197    pub fn mem_size(&self) -> usize {
198        std::mem::size_of::<Self>().saturating_add(self.map.iter().fold(
199            0,
200            |state: usize, (_, (name, value))| {
201                state.saturating_add(
202                    std::mem::size_of_val(value).saturating_add(
203                        std::mem::size_of_val(name).saturating_add(name.capacity()),
204                    ),
205                )
206            },
207        ))
208    }
209}
210
211/// Syscall function without context
212pub type BuiltinFunction<C> = fn(*mut EbpfVm<C>, u64, u64, u64, u64, u64);
213
214/// Represents the interface to a fixed functionality program
215#[derive(Eq)]
216pub struct BuiltinProgram<C: ContextObject> {
217    /// Holds the Config if this is a loader program
218    config: Option<Box<Config>>,
219    /// Function pointers by symbol
220    functions: FunctionRegistry<BuiltinFunction<C>>,
221}
222
223impl<C: ContextObject> PartialEq for BuiltinProgram<C> {
224    fn eq(&self, other: &Self) -> bool {
225        self.config.eq(&other.config) && self.functions.eq(&other.functions)
226    }
227}
228
229impl<C: ContextObject> BuiltinProgram<C> {
230    /// Constructs a loader built-in program
231    pub fn new_loader(config: Config, functions: FunctionRegistry<BuiltinFunction<C>>) -> Self {
232        Self {
233            config: Some(Box::new(config)),
234            functions,
235        }
236    }
237
238    /// Constructs a built-in program
239    pub fn new_builtin(functions: FunctionRegistry<BuiltinFunction<C>>) -> Self {
240        Self {
241            config: None,
242            functions,
243        }
244    }
245
246    /// Constructs a mock loader built-in program
247    pub fn new_mock() -> Self {
248        Self {
249            config: Some(Box::default()),
250            functions: FunctionRegistry::default(),
251        }
252    }
253
254    /// Get the configuration settings assuming this is a loader program
255    pub fn get_config(&self) -> &Config {
256        self.config.as_ref().unwrap()
257    }
258
259    /// Get the function registry
260    pub fn get_function_registry(&self) -> &FunctionRegistry<BuiltinFunction<C>> {
261        &self.functions
262    }
263
264    /// Calculate memory size
265    pub fn mem_size(&self) -> usize {
266        std::mem::size_of::<Self>()
267            .saturating_add(if self.config.is_some() {
268                std::mem::size_of::<Config>()
269            } else {
270                0
271            })
272            .saturating_add(self.functions.mem_size())
273    }
274}
275
276impl<C: ContextObject> std::fmt::Debug for BuiltinProgram<C> {
277    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
278        writeln!(f, "{:?}", unsafe {
279            // `derive(Debug)` does not know that `C: ContextObject` does not need to implement `Debug`
280            std::mem::transmute::<&FunctionRegistry<BuiltinFunction<C>>, &FunctionRegistry<usize>>(
281                &self.functions,
282            )
283        })?;
284        Ok(())
285    }
286}
287
288/// Generates an adapter for a BuiltinFunction between the Rust and the VM interface
289#[macro_export]
290macro_rules! declare_builtin_function {
291    ($(#[$attr:meta])* $name:ident $(<$($generic_ident:tt : $generic_type:tt),+>)?, fn rust(
292        $vm:ident : &mut $ContextObject:ty,
293        $arg_a:ident : u64,
294        $arg_b:ident : u64,
295        $arg_c:ident : u64,
296        $arg_d:ident : u64,
297        $arg_e:ident : u64,
298        $memory_mapping:ident : &mut $MemoryMapping:ty,
299    ) -> $Result:ty { $($rust:tt)* }) => {
300        $(#[$attr])*
301        pub struct $name {}
302        impl $name {
303            /// Rust interface
304            pub fn rust $(<$($generic_ident : $generic_type),+>)? (
305                $vm: &mut $ContextObject,
306                $arg_a: u64,
307                $arg_b: u64,
308                $arg_c: u64,
309                $arg_d: u64,
310                $arg_e: u64,
311                $memory_mapping: &mut $MemoryMapping,
312            ) -> $Result {
313                $($rust)*
314            }
315            /// VM interface
316            #[allow(clippy::too_many_arguments)]
317            pub fn vm $(<$($generic_ident : $generic_type),+>)? (
318                $vm: *mut $crate::vm::EbpfVm<$ContextObject>,
319                $arg_a: u64,
320                $arg_b: u64,
321                $arg_c: u64,
322                $arg_d: u64,
323                $arg_e: u64,
324            ) {
325                use $crate::vm::ContextObject;
326                let vm = unsafe {
327                    &mut *($vm.cast::<u64>().offset(-($crate::vm::get_runtime_environment_key() as isize)).cast::<$crate::vm::EbpfVm<$ContextObject>>())
328                };
329                let config = vm.loader.get_config();
330                if config.enable_instruction_meter {
331                    vm.context_object_pointer.consume(vm.previous_instruction_meter - vm.due_insn_count);
332                }
333                let converted_result: $crate::error::ProgramResult = Self::rust $(::<$($generic_ident),+>)?(
334                    vm.context_object_pointer, $arg_a, $arg_b, $arg_c, $arg_d, $arg_e, &mut vm.memory_mapping,
335                ).map_err(|err| $crate::error::EbpfError::SyscallError(err)).into();
336                vm.program_result = converted_result;
337                if config.enable_instruction_meter {
338                    vm.previous_instruction_meter = vm.context_object_pointer.get_remaining();
339                }
340            }
341        }
342    };
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use crate::{syscalls, vm::TestContextObject};
349
350    #[test]
351    fn test_builtin_program_eq() {
352        let mut function_registry_a =
353            FunctionRegistry::<BuiltinFunction<TestContextObject>>::default();
354        function_registry_a
355            .register_function_hashed(*b"log", syscalls::SyscallString::vm)
356            .unwrap();
357        function_registry_a
358            .register_function_hashed(*b"log_64", syscalls::SyscallU64::vm)
359            .unwrap();
360        let mut function_registry_b =
361            FunctionRegistry::<BuiltinFunction<TestContextObject>>::default();
362        function_registry_b
363            .register_function_hashed(*b"log_64", syscalls::SyscallU64::vm)
364            .unwrap();
365        function_registry_b
366            .register_function_hashed(*b"log", syscalls::SyscallString::vm)
367            .unwrap();
368        let mut function_registry_c =
369            FunctionRegistry::<BuiltinFunction<TestContextObject>>::default();
370        function_registry_c
371            .register_function_hashed(*b"log_64", syscalls::SyscallU64::vm)
372            .unwrap();
373        let builtin_program_a = BuiltinProgram::new_loader(Config::default(), function_registry_a);
374        let builtin_program_b = BuiltinProgram::new_loader(Config::default(), function_registry_b);
375        assert_eq!(builtin_program_a, builtin_program_b);
376        let builtin_program_c = BuiltinProgram::new_loader(Config::default(), function_registry_c);
377        assert_ne!(builtin_program_a, builtin_program_c);
378    }
379}