Skip to main content

solana_program_runtime/
vm.rs

1//! SBF virtual machine provisioning and execution.
2
3#[cfg(feature = "svm-internal")]
4use qualifier_attr::qualifiers;
5use {
6    crate::{
7        execution_budget::MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268,
8        invoke_context::{BpfAllocator, InvokeContext},
9        mem_pool::VmMemoryPool,
10        memory_context::{MemoryContext, SerializedAccountMetadata},
11        program_cache_entry::ProgramCacheEntry,
12        serialization::{self, SerializationResult},
13        stable_log,
14    },
15    solana_instruction_error::InstructionError,
16    solana_program_entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
17    solana_sbpf::{
18        ebpf::{self, MM_HEAP_START, MM_RODATA_START, MM_STACK_START},
19        elf::Executable,
20        error::{EbpfError, ProgramResult},
21        memory_region::{AccessType, MemoryMapping, MemoryRegion},
22        vm::{ContextObject, EbpfVm, ExecutionMode},
23    },
24    solana_sdk_ids::bpf_loader_deprecated,
25    solana_svm_log_collector::ic_logger_msg,
26    solana_svm_measure::measure::Measure,
27    solana_transaction_context::IndexOfAccount,
28    std::{cell::RefCell, mem, time::Duration},
29};
30
31thread_local! {
32    pub static MEMORY_POOL: RefCell<VmMemoryPool> = RefCell::new(VmMemoryPool::new());
33}
34
35/// Only used in macro, do not use directly!
36pub fn calculate_heap_cost(heap_size: u32, heap_cost: u64) -> u64 {
37    const KIBIBYTE: u64 = 1024;
38    const PAGE_SIZE_KB: u64 = 32;
39    let mut rounded_heap_size = u64::from(heap_size);
40    rounded_heap_size =
41        rounded_heap_size.saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1));
42    rounded_heap_size
43        .checked_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE))
44        .expect("PAGE_SIZE_KB * KIBIBYTE > 0")
45        .saturating_sub(1)
46        .saturating_mul(heap_cost)
47}
48
49/// Only used in macro, do not use directly!
50///
51/// # Safety
52///
53/// Refer to [`configure_program_regions`].
54#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
55pub unsafe fn create_vm<'a, 'b>(
56    program: &'a Executable<InvokeContext<'b, 'b>>,
57    invoke_context: &'a mut InvokeContext<'b, 'b>,
58    stack: *mut [u8],
59    heap: *mut [u8],
60) -> Result<EbpfVm<'a, InvokeContext<'b, 'b>>, Box<dyn std::error::Error>> {
61    let stack_size = stack.len();
62    unsafe {
63        // SAFETY: invariants delegated to the caller.
64        configure_program_regions(invoke_context, program, stack, heap)?;
65    }
66    Ok(EbpfVm::new(
67        program.get_loader().clone(),
68        program.get_sbpf_version(),
69        invoke_context,
70        stack_size,
71    ))
72}
73
74/// # Safety
75///
76/// The `executable`, `stack` and `heap` arguments must remain allocated for at least the lifetime
77/// of [`MemoryMapping`] (or until after the `MemoryMapping` is reconfigured with different
78/// `executable`, `stack` and `heap`).
79unsafe fn configure_program_regions<C: ContextObject>(
80    invoke_context: &mut InvokeContext,
81    executable: &Executable<C>,
82    stack: *mut [u8],
83    heap: *mut [u8],
84) -> Result<(), Box<dyn std::error::Error>> {
85    let mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
86    let regions = mapping.get_regions_mut();
87    let [ro_area, stack_area, heap_area, ..] = regions else {
88        panic!("the regions vector must have at least three entries")
89    };
90    *ro_area = executable.get_ro_region();
91    let sbpf_version = executable.get_sbpf_version();
92    let config = executable.get_config();
93    *stack_area = MemoryRegion::new_gapped(
94        stack,
95        MM_STACK_START,
96        if sbpf_version.stack_frame_gaps() && config.enable_stack_frame_gaps {
97            config.stack_frame_size as u64
98        } else {
99            0
100        },
101    );
102    *heap_area = MemoryRegion::new(heap, MM_HEAP_START);
103    mapping
104        .initialize()
105        .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
106}
107
108/// Create the SBF virtual machine
109#[macro_export]
110macro_rules! create_vm {
111    ($vm:ident, $program:expr, $invoke_context:expr $(,)?) => {
112        let invoke_context = &*$invoke_context;
113        let stack_size = $program.get_config().stack_size();
114        let heap_size = invoke_context.get_compute_budget().heap_size;
115        let heap_cost_result =
116            invoke_context.consume_checked($crate::__private::calculate_heap_cost(
117                heap_size,
118                invoke_context.get_execution_cost().heap_cost,
119            ));
120        let $vm = heap_cost_result.and_then(|_| {
121            let (mut stack, mut heap) = $crate::__private::MEMORY_POOL
122                .with_borrow_mut(|pool| (pool.get_stack(stack_size), pool.get_heap(heap_size)));
123            let vm = $crate::__private::create_vm(
124                $program,
125                $invoke_context,
126                stack
127                    .as_slice_mut()
128                    .get_mut(..stack_size)
129                    .expect("invalid stack size"),
130                heap.as_slice_mut()
131                    .get_mut(..heap_size as usize)
132                    .expect("invalid heap size"),
133            );
134            vm.map(|vm| (vm, stack, heap))
135        });
136    };
137}
138
139/// # Safety
140///
141/// The [`MemoryRegion`]s must satisfy the safety preconditions for
142/// [`MemoryMapping::new_uninitialized`].
143unsafe fn set_memory_context<'b>(
144    additional_initialized_regions: Vec<MemoryRegion>,
145    accounts_metadata: Vec<SerializedAccountMetadata>,
146    invoke_context: &mut InvokeContext<'b, 'b>,
147    executable: &Executable<InvokeContext<'b, 'b>>,
148    virtual_address_space_adjustments: bool,
149    account_data_direct_mapping: bool,
150) -> Result<(), Box<dyn std::error::Error>> {
151    let heap_size = invoke_context.get_compute_budget().heap_size;
152    let regions = [
153        MemoryRegion::new_empty(MM_RODATA_START),
154        MemoryRegion::new_empty(MM_STACK_START),
155        MemoryRegion::new_empty(MM_HEAP_START),
156    ]
157    .into_iter()
158    .chain(additional_initialized_regions)
159    .collect();
160    let memory_mapping = unsafe {
161        // SAFETY: all memory regions are `default` (and thus implicitly valid) or valid by
162        // delegating the safety invariant upon the caller.
163        MemoryMapping::new_uninitialized(
164            regions,
165            executable.get_config(),
166            executable.get_sbpf_version(),
167            invoke_context.transaction_context.access_violation_handler(
168                virtual_address_space_adjustments,
169                account_data_direct_mapping,
170            ),
171        )
172    };
173
174    invoke_context
175        .memory_contexts
176        .set_memory_context_abi_v1(MemoryContext::new(
177            BpfAllocator::new(heap_size as u64),
178            accounts_metadata,
179            memory_mapping,
180        ))
181        .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
182}
183
184#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
185pub fn execute<'a, 'b: 'a>(
186    executable: &'a Executable<InvokeContext<'static, 'static>>,
187    invoke_context: &'a mut InvokeContext<'b, 'b>,
188    cache_entry: &ProgramCacheEntry,
189) -> Result<(), Box<dyn std::error::Error>> {
190    // We dropped the lifetime tracking in the Executor by setting it to 'static,
191    // thus we need to reintroduce the correct lifetime of InvokeContext here again.
192    let executable = unsafe {
193        mem::transmute::<
194            &'a Executable<InvokeContext<'static, 'static>>,
195            &'a Executable<InvokeContext<'b, 'b>>,
196        >(executable)
197    };
198    let log_collector = invoke_context.get_log_collector();
199    let transaction_context = &invoke_context.transaction_context;
200    let instruction_context = transaction_context.get_current_instruction_context()?;
201    let program_id = *instruction_context.get_program_key()?;
202    let is_loader_deprecated =
203        instruction_context.get_program_owner()? == bpf_loader_deprecated::id();
204    let virtual_address_space_adjustments = invoke_context
205        .get_feature_set()
206        .virtual_address_space_adjustments;
207    let account_data_direct_mapping = invoke_context.get_feature_set().account_data_direct_mapping;
208    let direct_account_pointers_in_program_input = invoke_context
209        .get_feature_set()
210        .direct_account_pointers_in_program_input;
211    let pass_input_metadata_in_vm_r3_r5 = invoke_context
212        .get_feature_set()
213        .pass_input_metadata_in_vm_r3_r5;
214
215    let mut serialize_time = Measure::start("serialize");
216    let SerializationResult {
217        parameter_bytes,
218        regions,
219        accounts_metadata,
220        instruction_data_offset,
221        account_pointers_offset,
222    } = serialization::serialize_parameters(
223        &instruction_context,
224        virtual_address_space_adjustments,
225        account_data_direct_mapping,
226        direct_account_pointers_in_program_input,
227    )?;
228    serialize_time.stop();
229
230    let instruction_data_len = instruction_context.get_instruction_data().len();
231    let number_of_instruction_accounts = instruction_context.get_number_of_instruction_accounts();
232
233    // save the account addresses so in case we hit an AccessViolation error we
234    // can map to a more specific error
235    let account_region_addrs = accounts_metadata
236        .iter()
237        .map(|m| {
238            let vm_end = m
239                .vm_data_addr
240                .saturating_add(m.original_data_len as u64)
241                .saturating_add(if !is_loader_deprecated {
242                    MAX_PERMITTED_DATA_INCREASE as u64
243                } else {
244                    0
245                });
246            m.vm_data_addr..vm_end
247        })
248        .collect::<Vec<_>>();
249
250    #[cfg(feature = "sbpf-debugger")]
251    let (debug_port, debug_metadata) = if invoke_context.debug_port.is_some() {
252        (
253            invoke_context.debug_port,
254            Some(format!(
255                "program_id={};cpi_level={};caller={}",
256                program_id,
257                instruction_context.get_stack_height().saturating_sub(1),
258                invoke_context
259                    .get_stack_height()
260                    .checked_sub(2)
261                    .and_then(|nesting_level| {
262                        transaction_context
263                            .get_instruction_context_at_nesting_level(nesting_level)
264                            .ok()
265                    })
266                    .and_then(|ctx| ctx.get_program_key().ok())
267                    .map(|key| key.to_string())
268                    .unwrap_or_else(|| "none".into())
269            )),
270        )
271    } else {
272        (None, None)
273    };
274
275    let mut create_vm_time = Measure::start("create_vm");
276    unsafe {
277        // SAFETY: The memory pointed to by regions is valid for the useful lifetime of
278        // `invoke_context`, which in turn contains the `MemoryMapping` that allows access to this
279        // memory.
280        set_memory_context(
281            regions,
282            accounts_metadata,
283            invoke_context,
284            executable,
285            virtual_address_space_adjustments,
286            account_data_direct_mapping,
287        )?
288    };
289
290    let execution_result = {
291        let mut execution_mode = ExecutionMode::PreferJit;
292
293        #[cfg(feature = "sbpf-debugger")]
294        if invoke_context.debug_port.is_some() {
295            execution_mode = ExecutionMode::Interpreted;
296        }
297
298        let compute_meter_prev = invoke_context.get_remaining();
299        let mapped_heap_len = invoke_context.get_compute_budget().heap_size as usize;
300        let (mut vm, stack, heap) = unsafe {
301            // SAFETY: The `stack`, `heap` and `executable` live past the lifetime of
302            // `invoke_context`.
303            create_vm!(vm, executable, invoke_context);
304            match vm {
305                Ok(info) => info,
306                Err(e) => {
307                    ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", e);
308                    return Err(Box::new(InstructionError::ProgramEnvironmentSetupFailure));
309                }
310            }
311        };
312
313        create_vm_time.stop();
314        #[cfg(feature = "sbpf-debugger")]
315        {
316            vm.debug_port = debug_port;
317            vm.debug_metadata = debug_metadata;
318        }
319
320        let execute_time = Measure::start("execute");
321        let prev_nested_exec_time = vm.context().total_nested_exec_time;
322
323        vm.registers[1] = ebpf::MM_INPUT_START;
324        vm.registers[2] = instruction_data_offset as u64;
325
326        // SIMD-0683: Pass ABIv1 input metadata via registers r3–r5.
327        if !is_loader_deprecated && pass_input_metadata_in_vm_r3_r5 {
328            vm.registers[3] = instruction_data_len as u64;
329            vm.registers[4] = if let Some(account_pointers_offset) = account_pointers_offset {
330                account_pointers_offset as u64
331            } else {
332                // SIMD-0683 depends on SIMD-0449, which provides the account
333                // pointers array. Since this path only handles ABIv1, the offset
334                // should always be present when SIMD-0683 is enabled so this error
335                // should never happen.
336                ic_logger_msg!(
337                    log_collector,
338                    "Failed to create SBF VM: cannot determine account pointers offset"
339                );
340                return Err(Box::new(InstructionError::ProgramEnvironmentSetupFailure));
341            };
342            vm.registers[5] = number_of_instruction_accounts as u64;
343        }
344
345        let mut call_frames =
346            MEMORY_POOL.with_borrow_mut(|memory_pool| memory_pool.get_call_frames());
347        let (compute_units_consumed, result) =
348            vm.execute_program(executable, &mut execution_mode, &mut call_frames);
349        let register_trace = std::mem::take(&mut vm.register_trace);
350        MEMORY_POOL.with_borrow_mut(|memory_pool| {
351            memory_pool.put_stack(stack);
352            memory_pool.put_heap(heap, mapped_heap_len);
353            memory_pool.put_call_frames(call_frames);
354            debug_assert!(memory_pool.stack_len() <= MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268);
355            debug_assert!(memory_pool.heap_len() <= MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268);
356        });
357        drop(vm);
358        invoke_context.insert_register_trace(register_trace);
359
360        // This section is a little convoluted due to the nested and sibling (CPI) invocations.
361        let total_execute_ns = execute_time.end_as_ns();
362        let nested_execution_time_delta = invoke_context
363            .total_nested_exec_time
364            .saturating_sub(prev_nested_exec_time);
365        let this_call_ns =
366            total_execute_ns.saturating_sub(nested_execution_time_delta.as_nanos() as u64);
367        invoke_context.total_nested_exec_time = invoke_context
368            .total_nested_exec_time
369            .saturating_add(Duration::from_nanos(this_call_ns));
370        let this_call_us = this_call_ns / 1000;
371        invoke_context.timings.execute_us += this_call_us;
372        match execution_mode {
373            ExecutionMode::Interpreted => cache_entry.stats.interpreter_executed(this_call_us),
374            ExecutionMode::Jit => cache_entry.stats.jit_executed(this_call_us),
375            ExecutionMode::PreferJit => { /* not actually executed? */ }
376        }
377
378        ic_logger_msg!(
379            log_collector,
380            "Program {} consumed {} of {} compute units",
381            &program_id,
382            compute_units_consumed,
383            compute_meter_prev
384        );
385        let (_returned_from_program_id, return_data) =
386            invoke_context.transaction_context.get_return_data();
387        if !return_data.is_empty() {
388            stable_log::program_return(&log_collector, &program_id, return_data);
389        }
390        match result {
391            ProgramResult::Ok(status) if status != SUCCESS => {
392                let error: InstructionError = status.into();
393                Err(Box::new(error) as Box<dyn std::error::Error>)
394            }
395            ProgramResult::Err(mut error) => {
396                // Don't clean me up!!
397                // This feature is active on all networks, but we still toggle
398                // it off during fuzzing.
399                if invoke_context
400                    .get_feature_set()
401                    .deplete_cu_meter_on_vm_failure
402                    && !matches!(error, EbpfError::SyscallError(_))
403                {
404                    // when an exception is thrown during the execution of a
405                    // Basic Block (e.g., a null memory dereference or other
406                    // faults), determining the exact number of CUs consumed
407                    // up to the point of failure requires additional effort
408                    // and is unnecessary since these cases are rare.
409                    //
410                    // In order to simplify CU tracking, simply consume all
411                    // remaining compute units so that the block cost
412                    // tracker uses the full requested compute unit cost for
413                    // this failed transaction.
414                    invoke_context.consume(invoke_context.get_remaining());
415                }
416
417                if virtual_address_space_adjustments {
418                    if let EbpfError::SyscallError(err) = error {
419                        error = err
420                            .downcast::<EbpfError>()
421                            .map(|err| *err)
422                            .unwrap_or_else(EbpfError::SyscallError);
423                    }
424                    if let EbpfError::AccessViolation(access_type, vm_addr, len, _section_name) =
425                        error
426                    {
427                        // If virtual_address_space_adjustments is enabled and a program tries to write to a readonly
428                        // region we'll get a memory access violation. Map it to a more specific
429                        // error so it's easier for developers to see what happened.
430                        if let Some((instruction_account_index, vm_addr_range)) =
431                            account_region_addrs
432                                .iter()
433                                .enumerate()
434                                .find(|(_, vm_addr_range)| vm_addr_range.contains(&vm_addr))
435                        {
436                            let transaction_context = &invoke_context.transaction_context;
437                            let instruction_context =
438                                transaction_context.get_current_instruction_context()?;
439                            let account = instruction_context.try_borrow_instruction_account(
440                                instruction_account_index as IndexOfAccount,
441                            )?;
442                            if vm_addr.saturating_add(len) <= vm_addr_range.end {
443                                // The access was within the range of the accounts address space,
444                                // but it might not be within the range of the actual data.
445                                let is_access_outside_of_data = vm_addr
446                                    .saturating_add(len)
447                                    .saturating_sub(vm_addr_range.start)
448                                    as usize
449                                    > account.get_data().len();
450                                error = EbpfError::SyscallError(Box::new(match access_type {
451                                    AccessType::Store => {
452                                        if let Err(err) = account.can_data_be_changed() {
453                                            err
454                                        } else {
455                                            // The store was allowed but failed,
456                                            // thus it must have been an attempt to grow the account.
457                                            debug_assert!(is_access_outside_of_data);
458                                            InstructionError::InvalidRealloc
459                                        }
460                                    }
461                                    AccessType::Load => {
462                                        // Loads should only fail when they are outside of the account data.
463                                        debug_assert!(is_access_outside_of_data);
464                                        if account.can_data_be_changed().is_err() {
465                                            // Load beyond readonly account data happened because the program
466                                            // expected more data than there actually is.
467                                            InstructionError::AccountDataTooSmall
468                                        } else {
469                                            // Load beyond writable account data also attempted to grow.
470                                            InstructionError::InvalidRealloc
471                                        }
472                                    }
473                                }));
474                            }
475                        }
476                    }
477                }
478                Err(if let EbpfError::SyscallError(err) = error {
479                    err
480                } else {
481                    error.into()
482                })
483            }
484            _ => Ok(()),
485        }
486    };
487
488    fn deserialize_parameters(
489        invoke_context: &mut InvokeContext,
490        parameter_bytes: &[u8],
491        virtual_address_space_adjustments: bool,
492        account_data_direct_mapping: bool,
493    ) -> Result<(), InstructionError> {
494        serialization::deserialize_parameters(
495            &invoke_context
496                .transaction_context
497                .get_current_instruction_context()?,
498            virtual_address_space_adjustments,
499            account_data_direct_mapping,
500            parameter_bytes,
501            &invoke_context
502                .memory_contexts
503                .memory_context_abi_v1()?
504                .accounts_metadata,
505        )
506    }
507
508    let mut deserialize_time = Measure::start("deserialize");
509    let execute_or_deserialize_result = execution_result.and_then(|_| {
510        deserialize_parameters(
511            invoke_context,
512            parameter_bytes.as_slice(),
513            virtual_address_space_adjustments,
514            account_data_direct_mapping,
515        )
516        .map_err(|error| Box::new(error) as Box<dyn std::error::Error>)
517    });
518    deserialize_time.stop();
519
520    // Update the timings
521    invoke_context.timings.serialize_us += serialize_time.as_us();
522    invoke_context.timings.create_vm_us += create_vm_time.as_us();
523    invoke_context.timings.deserialize_us += deserialize_time.as_us();
524
525    execute_or_deserialize_result
526}