solana/programs/bpf_loader/src/lib.rs

4177 lines
157 KiB
Rust
Raw Normal View History

2022-03-10 11:48:33 -08:00
#![deny(clippy::integer_arithmetic)]
#![deny(clippy::indexing_slicing)]
pub mod serialization;
pub mod syscalls;
2018-10-16 16:33:31 -07:00
use {
solana_measure::measure::Measure,
solana_program_runtime::{
ic_logger_msg, ic_msg,
invoke_context::{BpfAllocator, InvokeContext, SerializedAccountMetadata, SyscallContext},
loaded_programs::{
LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET,
},
log_collector::LogCollector,
stable_log,
sysvar_cache::get_sysvar_with_account_check,
},
solana_rbpf::{
aligned_memory::AlignedMemory,
ebpf::{self, HOST_ALIGN, MM_HEAP_START},
elf::Executable,
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
error::EbpfError,
memory_region::{AccessType, MemoryCowCallback, MemoryMapping, MemoryRegion},
verifier::RequisiteVerifier,
vm::{BuiltinProgram, ContextObject, EbpfVm, ProgramResult},
},
solana_sdk::{
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
account::WritableAccount,
bpf_loader, bpf_loader_deprecated,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::Slot,
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
feature_set::{
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
bpf_account_data_direct_mapping, cap_accounts_data_allocations_per_transaction,
cap_bpf_program_instruction_accounts, delay_visibility_of_program_deployment,
enable_bpf_loader_extend_program_ix, enable_bpf_loader_set_authority_checked_ix,
enable_program_redeployment_cooldown, limit_max_instruction_trace_length,
native_programs_consume_cu, remove_bpf_loader_incorrect_program_id, FeatureSet,
},
instruction::{AccountMeta, InstructionError},
loader_instruction::LoaderInstruction,
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
native_loader,
program_error::{
MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED, MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED,
},
program_utils::limited_deserialize,
pubkey::Pubkey,
2022-01-05 16:08:35 -08:00
saturating_add_assign,
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
transaction_context::{
BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
},
},
std::{
cell::RefCell,
mem,
rc::Rc,
sync::{atomic::Ordering, Arc},
},
syscalls::create_program_runtime_environment_v1,
2020-01-02 18:18:56 -08:00
};
2019-05-24 16:21:42 -07:00
pub const DEFAULT_LOADER_COMPUTE_UNITS: u64 = 570;
pub const DEPRECATED_LOADER_COMPUTE_UNITS: u64 = 1_140;
pub const UPGRADEABLE_LOADER_COMPUTE_UNITS: u64 = 2_370;
#[allow(clippy::too_many_arguments)]
pub fn load_program_from_bytes(
feature_set: &FeatureSet,
log_collector: Option<Rc<RefCell<LogCollector>>>,
load_program_metrics: &mut LoadProgramMetrics,
programdata: &[u8],
loader_key: &Pubkey,
account_size: usize,
deployment_slot: Slot,
program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
) -> Result<LoadedProgram, InstructionError> {
let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) {
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET)
} else {
deployment_slot
};
let loaded_program = LoadedProgram::new(
loader_key,
program_runtime_environment,
deployment_slot,
effective_slot,
None,
programdata,
account_size,
load_program_metrics,
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
Ok(loaded_program)
}
macro_rules! deploy_program {
($invoke_context:expr, $program_id:expr, $loader_key:expr,
$account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{
let mut load_program_metrics = LoadProgramMetrics::default();
let mut register_syscalls_time = Measure::start("register_syscalls_time");
let program_runtime_environment = create_program_runtime_environment_v1(
&$invoke_context.feature_set,
$invoke_context.get_compute_budget(),
true, /* deployment */
false, /* debugging_features */
).map_err(|e| {
ic_msg!($invoke_context, "Failed to register syscalls: {}", e);
InstructionError::ProgramEnvironmentSetupFailure
})?;
register_syscalls_time.stop();
load_program_metrics.register_syscalls_us = register_syscalls_time.as_us();
let executor = load_program_from_bytes(
&$invoke_context.feature_set,
$invoke_context.get_log_collector(),
&mut load_program_metrics,
$new_programdata,
$loader_key,
$account_size,
$slot,
Arc::new(program_runtime_environment),
)?;
if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) {
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed
);
}
$drop
load_program_metrics.program_id = $program_id.to_string();
load_program_metrics.submit_datapoint(&mut $invoke_context.timings);
$invoke_context.programs_modified_by_tx.replenish($program_id, Arc::new(executor));
}};
}
fn write_program_data(
program_data_offset: usize,
2020-12-14 15:35:10 -08:00
bytes: &[u8],
invoke_context: &mut InvokeContext,
2020-12-14 15:35:10 -08:00
) -> Result<(), InstructionError> {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let data = program.get_data_mut()?;
let write_offset = program_data_offset.saturating_add(bytes.len());
if data.len() < write_offset {
ic_msg!(
invoke_context,
2021-01-21 09:57:59 -08:00
"Write overflow: {} < {}",
data.len(),
write_offset,
2021-01-21 09:57:59 -08:00
);
2020-12-14 15:35:10 -08:00
return Err(InstructionError::AccountDataTooSmall);
}
data.get_mut(program_data_offset..write_offset)
2022-03-10 11:48:33 -08:00
.ok_or(InstructionError::AccountDataTooSmall)?
.copy_from_slice(bytes);
2020-12-14 15:35:10 -08:00
Ok(())
}
pub fn check_loader_id(id: &Pubkey) -> bool {
bpf_loader::check_id(id)
|| bpf_loader_deprecated::check_id(id)
|| bpf_loader_upgradeable::check_id(id)
}
/// Only used in macro, do not use directly!
pub fn calculate_heap_cost(heap_size: u64, heap_cost: u64, enable_rounding_fix: bool) -> u64 {
const KIBIBYTE: u64 = 1024;
const PAGE_SIZE_KB: u64 = 32;
let mut rounded_heap_size = heap_size;
if enable_rounding_fix {
rounded_heap_size = rounded_heap_size
.saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1));
}
rounded_heap_size
.checked_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE))
.expect("PAGE_SIZE_KB * KIBIBYTE > 0")
.saturating_sub(1)
.saturating_mul(heap_cost)
}
/// Only used in macro, do not use directly!
pub fn create_vm<'a, 'b>(
program: &'a Executable<RequisiteVerifier, InvokeContext<'b>>,
regions: Vec<MemoryRegion>,
accounts_metadata: Vec<SerializedAccountMetadata>,
invoke_context: &'a mut InvokeContext<'b>,
stack: &mut AlignedMemory<HOST_ALIGN>,
heap: &mut AlignedMemory<HOST_ALIGN>,
2023-07-05 10:46:21 -07:00
) -> Result<EbpfVm<'a, InvokeContext<'b>>, Box<dyn std::error::Error>> {
let stack_size = stack.len();
let heap_size = heap.len();
let accounts = Rc::clone(invoke_context.transaction_context.accounts());
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
let memory_mapping = create_memory_mapping(
program,
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
stack,
heap,
regions,
Some(Box::new(move |index_in_transaction| {
// The two calls below can't really fail. If they fail because of a bug,
// whatever is writing will trigger an EbpfError::AccessViolation like
// if the region was readonly, and the transaction will fail gracefully.
let mut account = accounts
.try_borrow_mut(index_in_transaction as IndexOfAccount)
.map_err(|_| ())?;
accounts
.touch(index_in_transaction as IndexOfAccount)
.map_err(|_| ())?;
if account.is_shared() {
// See BorrowedAccount::make_data_mut() as to why we reserve extra
// MAX_PERMITTED_DATA_INCREASE bytes here.
account.reserve(MAX_PERMITTED_DATA_INCREASE);
}
Ok(account.data_as_mut_slice().as_mut_ptr() as u64)
})),
)?;
invoke_context.set_syscall_context(SyscallContext {
allocator: BpfAllocator::new(heap_size as u64),
accounts_metadata,
trace_log: Vec::new(),
})?;
Ok(EbpfVm::new(
2023-07-05 10:46:21 -07:00
program.get_config(),
program.get_sbpf_version(),
invoke_context,
memory_mapping,
stack_size,
))
}
/// Create the SBF virtual machine
#[macro_export]
macro_rules! create_vm {
($vm:ident, $program:expr, $regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => {
let invoke_context = &*$invoke_context;
let stack_size = $program.get_config().stack_size();
let heap_size = invoke_context
.get_compute_budget()
.heap_size
.unwrap_or(solana_sdk::entrypoint::HEAP_LENGTH);
let round_up_heap_size = invoke_context
.feature_set
.is_active(&solana_sdk::feature_set::round_up_heap_size::id());
let mut heap_cost_result = invoke_context.consume_checked($crate::calculate_heap_cost(
heap_size as u64,
invoke_context.get_compute_budget().heap_cost,
round_up_heap_size,
));
if !round_up_heap_size {
heap_cost_result = Ok(());
}
let mut allocations = None;
let $vm = heap_cost_result.and_then(|_| {
let mut stack = solana_rbpf::aligned_memory::AlignedMemory::<
{ solana_rbpf::ebpf::HOST_ALIGN },
>::zero_filled(stack_size);
let mut heap = solana_rbpf::aligned_memory::AlignedMemory::<
{ solana_rbpf::ebpf::HOST_ALIGN },
>::zero_filled(heap_size);
let vm = $crate::create_vm(
$program,
$regions,
$accounts_metadata,
$invoke_context,
&mut stack,
&mut heap,
);
allocations = Some((stack, heap));
vm
});
};
}
#[macro_export]
macro_rules! mock_create_vm {
($vm:ident, $additional_regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => {
let loader = std::sync::Arc::new(BuiltinProgram::new_loader(
solana_rbpf::vm::Config::default(),
));
let function_registry = solana_rbpf::vm::FunctionRegistry::default();
let executable = solana_rbpf::elf::Executable::<
solana_rbpf::verifier::TautologyVerifier,
InvokeContext,
>::from_text_bytes(
2023-07-05 10:46:21 -07:00
&[0x95, 0, 0, 0, 0, 0, 0, 0],
loader,
SBPFVersion::V2,
function_registry,
)
.unwrap();
let verified_executable = solana_rbpf::elf::Executable::verified(executable).unwrap();
$crate::create_vm!(
$vm,
&verified_executable,
$additional_regions,
$accounts_metadata,
$invoke_context,
);
};
}
fn create_memory_mapping<'a, 'b, C: ContextObject>(
executable: &'a Executable<RequisiteVerifier, C>,
stack: &'b mut AlignedMemory<{ HOST_ALIGN }>,
heap: &'b mut AlignedMemory<{ HOST_ALIGN }>,
additional_regions: Vec<MemoryRegion>,
cow_cb: Option<MemoryCowCallback>,
) -> Result<MemoryMapping<'a>, Box<dyn std::error::Error>> {
let config = executable.get_config();
2023-07-05 10:46:21 -07:00
let sbpf_version = executable.get_sbpf_version();
let regions: Vec<MemoryRegion> = vec![
executable.get_ro_region(),
MemoryRegion::new_writable_gapped(
stack.as_slice_mut(),
ebpf::MM_STACK_START,
2023-07-05 10:46:21 -07:00
if !sbpf_version.dynamic_stack_frames() && config.enable_stack_frame_gaps {
config.stack_frame_size as u64
} else {
0
},
),
MemoryRegion::new_writable(heap.as_slice_mut(), MM_HEAP_START),
]
.into_iter()
.chain(additional_regions)
.collect();
Ok(if let Some(cow_cb) = cow_cb {
2023-07-05 10:46:21 -07:00
MemoryMapping::new_with_cow(regions, cow_cb, config, sbpf_version)?
} else {
2023-07-05 10:46:21 -07:00
MemoryMapping::new(regions, config, sbpf_version)?
})
}
pub fn process_instruction(
invoke_context: &mut InvokeContext,
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut MemoryMapping,
result: &mut ProgramResult,
) {
*result = process_instruction_inner(invoke_context).into();
2020-12-14 15:35:10 -08:00
}
fn process_instruction_inner(
invoke_context: &mut InvokeContext,
) -> Result<u64, Box<dyn std::error::Error>> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
if !invoke_context
.feature_set
.is_active(&remove_bpf_loader_incorrect_program_id::id())
{
fn get_index_in_transaction(
instruction_context: &InstructionContext,
index_in_instruction: IndexOfAccount,
) -> Result<IndexOfAccount, InstructionError> {
if index_in_instruction < instruction_context.get_number_of_program_accounts() {
instruction_context
.get_index_of_program_account_in_transaction(index_in_instruction)
} else {
instruction_context.get_index_of_instruction_account_in_transaction(
index_in_instruction
.saturating_sub(instruction_context.get_number_of_program_accounts()),
)
}
}
fn try_borrow_account<'a>(
transaction_context: &'a TransactionContext,
instruction_context: &'a InstructionContext,
index_in_instruction: IndexOfAccount,
) -> Result<BorrowedAccount<'a>, InstructionError> {
if index_in_instruction < instruction_context.get_number_of_program_accounts() {
instruction_context
.try_borrow_program_account(transaction_context, index_in_instruction)
} else {
instruction_context.try_borrow_instruction_account(
transaction_context,
index_in_instruction
.saturating_sub(instruction_context.get_number_of_program_accounts()),
)
}
}
let first_instruction_account = {
let borrowed_root_account =
instruction_context.try_borrow_program_account(transaction_context, 0)?;
let owner_id = borrowed_root_account.get_owner();
if native_loader::check_id(owner_id) {
1
} else {
0
}
};
let first_account_key = transaction_context.get_key_of_account_at_index(
get_index_in_transaction(instruction_context, first_instruction_account)?,
)?;
let second_account_key = get_index_in_transaction(
instruction_context,
first_instruction_account.saturating_add(1),
)
.and_then(|index_in_transaction| {
transaction_context.get_key_of_account_at_index(index_in_transaction)
});
let program_id = instruction_context.get_last_program_key(transaction_context)?;
let program_account_index = if first_account_key == program_id {
first_instruction_account
} else if second_account_key
.map(|key| key == program_id)
.unwrap_or(false)
{
first_instruction_account.saturating_add(1)
} else {
let first_account = try_borrow_account(
transaction_context,
instruction_context,
first_instruction_account,
)?;
if first_account.is_executable() {
ic_logger_msg!(log_collector, "BPF loader is executable");
return Err(Box::new(InstructionError::IncorrectProgramId));
}
first_instruction_account
};
let program = try_borrow_account(
transaction_context,
instruction_context,
program_account_index,
)?;
if program.is_executable() && !check_loader_id(program.get_owner()) {
ic_logger_msg!(
log_collector,
"Executable account not owned by the BPF loader"
);
return Err(Box::new(InstructionError::IncorrectProgramId));
}
}
let program_account =
instruction_context.try_borrow_last_program_account(transaction_context)?;
// Consume compute units if feature `native_programs_consume_cu` is activated
let native_programs_consume_cu = invoke_context
.feature_set
.is_active(&native_programs_consume_cu::id());
// Program Management Instruction
if native_loader::check_id(program_account.get_owner()) {
drop(program_account);
let program_id = instruction_context.get_last_program_key(transaction_context)?;
return if bpf_loader_upgradeable::check_id(program_id) {
if native_programs_consume_cu {
invoke_context.consume_checked(UPGRADEABLE_LOADER_COMPUTE_UNITS)?;
}
process_loader_upgradeable_instruction(invoke_context)
} else if bpf_loader::check_id(program_id) {
if native_programs_consume_cu {
invoke_context.consume_checked(DEFAULT_LOADER_COMPUTE_UNITS)?;
}
process_loader_instruction(invoke_context)
} else if bpf_loader_deprecated::check_id(program_id) {
if native_programs_consume_cu {
invoke_context.consume_checked(DEPRECATED_LOADER_COMPUTE_UNITS)?;
}
ic_logger_msg!(log_collector, "Deprecated loader is no longer supported");
Err(InstructionError::UnsupportedProgramId)
} else {
ic_logger_msg!(log_collector, "Invalid BPF loader id");
Err(InstructionError::IncorrectProgramId)
}
.map(|_| 0)
.map_err(|error| Box::new(error) as Box<dyn std::error::Error>);
}
// Program Invocation
if !program_account.is_executable() {
ic_logger_msg!(log_collector, "Program is not executable");
return Err(Box::new(InstructionError::IncorrectProgramId));
}
let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
let executor = invoke_context
.find_program_in_cache(program_account.get_key())
.ok_or(InstructionError::InvalidAccountData)?;
if executor.is_tombstone() {
return Err(Box::new(InstructionError::InvalidAccountData));
}
drop(program_account);
get_or_create_executor_time.stop();
saturating_add_assign!(
invoke_context.timings.get_or_create_executor_us,
get_or_create_executor_time.as_us()
);
executor.ix_usage_counter.fetch_add(1, Ordering::Relaxed);
match &executor.program {
LoadedProgramType::FailedVerification(_)
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => {
Err(Box::new(InstructionError::InvalidAccountData) as Box<dyn std::error::Error>)
}
LoadedProgramType::LegacyV0(executable) => execute(executable, invoke_context),
LoadedProgramType::LegacyV1(executable) => execute(executable, invoke_context),
_ => Err(Box::new(InstructionError::IncorrectProgramId) as Box<dyn std::error::Error>),
2020-12-14 15:35:10 -08:00
}
.map(|_| 0)
2020-12-14 15:35:10 -08:00
}
fn process_loader_upgradeable_instruction(
invoke_context: &mut InvokeContext,
2020-12-14 15:35:10 -08:00
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let program_id = instruction_context.get_last_program_key(transaction_context)?;
2020-12-14 15:35:10 -08:00
match limited_deserialize(instruction_data)? {
UpgradeableLoaderInstruction::InitializeBuffer => {
instruction_context.check_number_of_instruction_accounts(2)?;
let mut buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
if UpgradeableLoaderState::Uninitialized != buffer.get_state()? {
ic_logger_msg!(log_collector, "Buffer account already initialized");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::AccountAlreadyInitialized);
}
let authority_key = Some(*transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
)?);
buffer.set_state(&UpgradeableLoaderState::Buffer {
authority_address: authority_key,
})?;
2020-12-14 15:35:10 -08:00
}
UpgradeableLoaderInstruction::Write { offset, bytes } => {
instruction_context.check_number_of_instruction_accounts(2)?;
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
if authority_address.is_none() {
ic_logger_msg!(log_collector, "Buffer is immutable");
return Err(InstructionError::Immutable); // TODO better error code
}
let authority_key = Some(*transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
)?);
if authority_address != authority_key {
ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Buffer authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
} else {
ic_logger_msg!(log_collector, "Invalid Buffer account");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InvalidAccountData);
}
drop(buffer);
2020-12-14 15:35:10 -08:00
write_program_data(
UpgradeableLoaderState::size_of_buffer_metadata().saturating_add(offset as usize),
2020-12-14 15:35:10 -08:00
&bytes,
invoke_context,
2020-12-14 15:35:10 -08:00
)?;
}
UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => {
instruction_context.check_number_of_instruction_accounts(4)?;
let payer_key = *transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(0)?,
)?;
let programdata_key = *transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
)?;
let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?;
let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?;
instruction_context.check_number_of_instruction_accounts(8)?;
let authority_key = Some(*transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(7)?,
)?);
2020-12-14 15:35:10 -08:00
// Verify Program account
let program =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
if UpgradeableLoaderState::Uninitialized != program.get_state()? {
ic_logger_msg!(log_collector, "Program account already initialized");
return Err(InstructionError::AccountAlreadyInitialized);
}
if program.get_data().len() < UpgradeableLoaderState::size_of_program() {
ic_logger_msg!(log_collector, "Program account too small");
return Err(InstructionError::AccountDataTooSmall);
}
if program.get_lamports() < rent.minimum_balance(program.get_data().len()) {
ic_logger_msg!(log_collector, "Program account not rent-exempt");
return Err(InstructionError::ExecutableAccountNotRentExempt);
}
let new_program_id = *program.get_key();
drop(program);
2020-12-14 15:35:10 -08:00
// Verify Buffer account
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
if authority_address != authority_key {
ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match");
2021-03-10 09:48:41 -08:00
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(7)? {
ic_logger_msg!(log_collector, "Upgrade authority did not sign");
2021-03-10 09:48:41 -08:00
return Err(InstructionError::MissingRequiredSignature);
}
} else {
ic_logger_msg!(log_collector, "Invalid Buffer account");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InvalidArgument);
}
let buffer_key = *buffer.get_key();
let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata();
let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset);
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
let programdata_len = UpgradeableLoaderState::size_of_programdata(max_data_len);
if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata()
2021-01-19 17:56:44 -08:00
|| buffer_data_len == 0
{
ic_logger_msg!(log_collector, "Buffer account too small");
2021-01-19 17:56:44 -08:00
return Err(InstructionError::InvalidAccountData);
}
drop(buffer);
2020-12-14 15:35:10 -08:00
if max_data_len < buffer_data_len {
ic_logger_msg!(
log_collector,
"Max data length is too small to hold Buffer data"
);
2020-12-14 15:35:10 -08:00
return Err(InstructionError::AccountDataTooSmall);
}
if programdata_len > MAX_PERMITTED_DATA_LENGTH as usize {
ic_logger_msg!(log_collector, "Max data length is too large");
return Err(InstructionError::InvalidArgument);
}
2020-12-14 15:35:10 -08:00
// Create ProgramData account
let (derived_address, bump_seed) =
Pubkey::find_program_address(&[new_program_id.as_ref()], program_id);
if derived_address != programdata_key {
ic_logger_msg!(log_collector, "ProgramData address is not derived");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InvalidArgument);
}
// Drain the Buffer account to payer before paying for programdata account
{
let mut buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
let mut payer =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
payer.checked_add_lamports(buffer.get_lamports())?;
buffer.set_lamports(0)?;
}
let owner_id = *program_id;
let mut instruction = system_instruction::create_account(
&payer_key,
&programdata_key,
1.max(rent.minimum_balance(programdata_len)),
programdata_len as u64,
program_id,
);
// pass an extra account to avoid the overly strict UnbalancedInstruction error
instruction
.accounts
.push(AccountMeta::new(buffer_key, false));
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let caller_program_id =
instruction_context.get_last_program_key(transaction_context)?;
2022-10-19 14:22:57 -07:00
let signers = [[new_program_id.as_ref(), &[bump_seed]]]
.iter()
2022-10-19 14:22:57 -07:00
.map(|seeds| Pubkey::create_program_address(seeds, caller_program_id))
.collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?;
invoke_context.native_invoke(instruction.into(), signers.as_slice())?;
2020-12-14 15:35:10 -08:00
// Load and verify the program bits
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
deploy_program!(
invoke_context,
new_program_id,
&owner_id,
UpgradeableLoaderState::size_of_program().saturating_add(programdata_len),
clock.slot,
{
drop(buffer);
},
buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
);
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
2020-12-14 15:35:10 -08:00
// Update the ProgramData account and record the program bits
{
let mut programdata =
instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
programdata.set_state(&UpgradeableLoaderState::ProgramData {
slot: clock.slot,
upgrade_authority_address: authority_key,
})?;
let dst_slice = programdata
.get_data_mut()?
.get_mut(
programdata_data_offset
..programdata_data_offset.saturating_add(buffer_data_len),
)
.ok_or(InstructionError::AccountDataTooSmall)?;
let mut buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
let src_slice = buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?;
dst_slice.copy_from_slice(src_slice);
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
{
buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?;
}
}
2020-12-14 15:35:10 -08:00
// Update the Program account
let mut program =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
program.set_state(&UpgradeableLoaderState::Program {
programdata_address: programdata_key,
})?;
program.set_executable(true)?;
drop(program);
2020-12-14 15:35:10 -08:00
ic_logger_msg!(log_collector, "Deployed program {:?}", new_program_id);
2020-12-14 15:35:10 -08:00
}
UpgradeableLoaderInstruction::Upgrade => {
instruction_context.check_number_of_instruction_accounts(3)?;
let programdata_key = *transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(0)?,
)?;
let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?;
let clock =
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?;
instruction_context.check_number_of_instruction_accounts(7)?;
let authority_key = Some(*transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(6)?,
)?);
2020-12-14 15:35:10 -08:00
// Verify Program account
let program =
instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
if !program.is_executable() {
ic_logger_msg!(log_collector, "Program account not executable");
return Err(InstructionError::AccountNotExecutable);
}
if !program.is_writable() {
ic_logger_msg!(log_collector, "Program account not writeable");
return Err(InstructionError::InvalidArgument);
}
if program.get_owner() != program_id {
ic_logger_msg!(log_collector, "Program account not owned by loader");
return Err(InstructionError::IncorrectProgramId);
}
if let UpgradeableLoaderState::Program {
programdata_address,
} = program.get_state()?
{
if programdata_address != programdata_key {
ic_logger_msg!(log_collector, "Program and ProgramData account mismatch");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InvalidArgument);
}
} else {
ic_logger_msg!(log_collector, "Invalid Program account");
return Err(InstructionError::InvalidAccountData);
}
let new_program_id = *program.get_key();
drop(program);
2020-12-14 15:35:10 -08:00
// Verify Buffer account
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
if authority_address != authority_key {
ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match");
2021-03-10 09:48:41 -08:00
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(6)? {
ic_logger_msg!(log_collector, "Upgrade authority did not sign");
2021-03-10 09:48:41 -08:00
return Err(InstructionError::MissingRequiredSignature);
}
} else {
ic_logger_msg!(log_collector, "Invalid Buffer account");
return Err(InstructionError::InvalidArgument);
2020-12-14 15:35:10 -08:00
}
let buffer_lamports = buffer.get_lamports();
let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata();
let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset);
if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata()
2021-01-19 17:56:44 -08:00
|| buffer_data_len == 0
{
ic_logger_msg!(log_collector, "Buffer account too small");
2021-01-19 17:56:44 -08:00
return Err(InstructionError::InvalidAccountData);
}
drop(buffer);
2021-01-19 17:56:44 -08:00
2020-12-14 15:35:10 -08:00
// Verify ProgramData account
let programdata =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
let programdata_balance_required =
1.max(rent.minimum_balance(programdata.get_data().len()));
if programdata.get_data().len()
< UpgradeableLoaderState::size_of_programdata(buffer_data_len)
{
ic_logger_msg!(log_collector, "ProgramData account not large enough");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::AccountDataTooSmall);
}
if programdata.get_lamports().saturating_add(buffer_lamports)
2022-03-02 14:50:16 -08:00
< programdata_balance_required
{
ic_logger_msg!(
log_collector,
"Buffer account balance too low to fund upgrade"
);
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InsufficientFunds);
}
if let UpgradeableLoaderState::ProgramData {
slot,
2020-12-14 15:35:10 -08:00
upgrade_authority_address,
} = programdata.get_state()?
2020-12-14 15:35:10 -08:00
{
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
&& clock.slot == slot
{
ic_logger_msg!(log_collector, "Program was deployed in this block already");
return Err(InstructionError::InvalidArgument);
}
2021-03-10 09:48:41 -08:00
if upgrade_authority_address.is_none() {
ic_logger_msg!(log_collector, "Program not upgradeable");
return Err(InstructionError::Immutable);
2020-12-14 15:35:10 -08:00
}
if upgrade_authority_address != authority_key {
ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(6)? {
ic_logger_msg!(log_collector, "Upgrade authority did not sign");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::MissingRequiredSignature);
}
2020-12-14 15:35:10 -08:00
} else {
ic_logger_msg!(log_collector, "Invalid ProgramData account");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::InvalidAccountData);
};
let programdata_len = programdata.get_data().len();
drop(programdata);
2020-12-14 15:35:10 -08:00
// Load and verify the program bits
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
deploy_program!(
invoke_context,
new_program_id,
program_id,
UpgradeableLoaderState::size_of_program().saturating_add(programdata_len),
clock.slot,
{
drop(buffer);
},
buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
);
2020-12-14 15:35:10 -08:00
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
2020-12-14 15:35:10 -08:00
// Update the ProgramData account, record the upgraded data, and zero
// the rest
let mut programdata =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
{
programdata.set_state(&UpgradeableLoaderState::ProgramData {
slot: clock.slot,
upgrade_authority_address: authority_key,
})?;
let dst_slice = programdata
.get_data_mut()?
.get_mut(
programdata_data_offset
..programdata_data_offset.saturating_add(buffer_data_len),
)
.ok_or(InstructionError::AccountDataTooSmall)?;
let buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
let src_slice = buffer
.get_data()
.get(buffer_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?;
dst_slice.copy_from_slice(src_slice);
}
2022-03-10 11:48:33 -08:00
programdata
.get_data_mut()?
2022-03-10 11:48:33 -08:00
.get_mut(programdata_data_offset.saturating_add(buffer_data_len)..)
.ok_or(InstructionError::AccountDataTooSmall)?
2022-03-02 14:50:16 -08:00
.fill(0);
2020-12-14 15:35:10 -08:00
// Fund ProgramData to rent-exemption, spill the rest
let mut buffer =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
let mut spill =
instruction_context.try_borrow_instruction_account(transaction_context, 3)?;
spill.checked_add_lamports(
programdata
.get_lamports()
.saturating_add(buffer_lamports)
2022-03-02 14:50:16 -08:00
.saturating_sub(programdata_balance_required),
)?;
buffer.set_lamports(0)?;
programdata.set_lamports(programdata_balance_required)?;
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
{
buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?;
}
2020-12-14 15:35:10 -08:00
ic_logger_msg!(log_collector, "Upgraded program {:?}", new_program_id);
2020-12-14 15:35:10 -08:00
}
UpgradeableLoaderInstruction::SetAuthority => {
instruction_context.check_number_of_instruction_accounts(2)?;
let mut account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let present_authority_key = transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
)?;
let new_authority = instruction_context
.get_index_of_instruction_account_in_transaction(2)
.and_then(|index_in_transaction| {
transaction_context.get_key_of_account_at_index(index_in_transaction)
})
.ok();
match account.get_state()? {
UpgradeableLoaderState::Buffer { authority_address } => {
2021-03-10 09:48:41 -08:00
if new_authority.is_none() {
ic_logger_msg!(log_collector, "Buffer authority is not optional");
return Err(InstructionError::IncorrectAuthority);
}
2021-03-10 09:48:41 -08:00
if authority_address.is_none() {
ic_logger_msg!(log_collector, "Buffer is immutable");
return Err(InstructionError::Immutable);
}
if authority_address != Some(*present_authority_key) {
ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Buffer authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
account.set_state(&UpgradeableLoaderState::Buffer {
authority_address: new_authority.cloned(),
})?;
2020-12-14 15:35:10 -08:00
}
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} => {
2021-03-10 09:48:41 -08:00
if upgrade_authority_address.is_none() {
ic_logger_msg!(log_collector, "Program not upgradeable");
return Err(InstructionError::Immutable);
}
if upgrade_authority_address != Some(*present_authority_key) {
ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Upgrade authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
account.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: new_authority.cloned(),
})?;
}
_ => {
ic_logger_msg!(log_collector, "Account does not support authorities");
return Err(InstructionError::InvalidArgument);
2020-12-14 15:35:10 -08:00
}
}
2020-12-14 15:35:10 -08:00
ic_logger_msg!(log_collector, "New authority {:?}", new_authority);
}
UpgradeableLoaderInstruction::SetAuthorityChecked => {
if !invoke_context
.feature_set
.is_active(&enable_bpf_loader_set_authority_checked_ix::id())
{
return Err(InstructionError::InvalidInstructionData);
}
instruction_context.check_number_of_instruction_accounts(3)?;
let mut account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let present_authority_key = transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
)?;
let new_authority_key = transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(2)?,
)?;
match account.get_state()? {
UpgradeableLoaderState::Buffer { authority_address } => {
if authority_address.is_none() {
ic_logger_msg!(log_collector, "Buffer is immutable");
return Err(InstructionError::Immutable);
}
if authority_address != Some(*present_authority_key) {
ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Buffer authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
if !instruction_context.is_instruction_account_signer(2)? {
ic_logger_msg!(log_collector, "New authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
account.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(*new_authority_key),
})?;
}
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} => {
if upgrade_authority_address.is_none() {
ic_logger_msg!(log_collector, "Program not upgradeable");
return Err(InstructionError::Immutable);
}
if upgrade_authority_address != Some(*present_authority_key) {
ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Upgrade authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
if !instruction_context.is_instruction_account_signer(2)? {
ic_logger_msg!(log_collector, "New authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
account.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(*new_authority_key),
})?;
}
_ => {
ic_logger_msg!(log_collector, "Account does not support authorities");
return Err(InstructionError::InvalidArgument);
}
}
ic_logger_msg!(log_collector, "New authority {:?}", new_authority_key);
}
UpgradeableLoaderInstruction::Close => {
instruction_context.check_number_of_instruction_accounts(2)?;
if instruction_context.get_index_of_instruction_account_in_transaction(0)?
== instruction_context.get_index_of_instruction_account_in_transaction(1)?
{
ic_logger_msg!(
log_collector,
"Recipient is the same as the account being closed"
);
return Err(InstructionError::InvalidArgument);
}
let mut close_account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let close_key = *close_account.get_key();
let close_account_state = close_account.get_state()?;
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
{
close_account.set_data_length(UpgradeableLoaderState::size_of_uninitialized())?;
}
match close_account_state {
UpgradeableLoaderState::Uninitialized => {
let mut recipient_account = instruction_context
.try_borrow_instruction_account(transaction_context, 1)?;
recipient_account.checked_add_lamports(close_account.get_lamports())?;
close_account.set_lamports(0)?;
ic_logger_msg!(log_collector, "Closed Uninitialized {}", close_key);
}
UpgradeableLoaderState::Buffer { authority_address } => {
instruction_context.check_number_of_instruction_accounts(3)?;
drop(close_account);
common_close_account(
&authority_address,
transaction_context,
instruction_context,
&log_collector,
)?;
ic_logger_msg!(log_collector, "Closed Buffer {}", close_key);
}
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: authority_address,
} => {
instruction_context.check_number_of_instruction_accounts(4)?;
drop(close_account);
let program_account = instruction_context
.try_borrow_instruction_account(transaction_context, 3)?;
let program_key = *program_account.get_key();
if !program_account.is_writable() {
ic_logger_msg!(log_collector, "Program account is not writable");
return Err(InstructionError::InvalidArgument);
}
if program_account.get_owner() != program_id {
ic_logger_msg!(log_collector, "Program account not owned by loader");
return Err(InstructionError::IncorrectProgramId);
}
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
{
let clock = invoke_context.get_sysvar_cache().get_clock()?;
if clock.slot == slot {
ic_logger_msg!(
log_collector,
"Program was deployed in this block already"
);
return Err(InstructionError::InvalidArgument);
}
}
match program_account.get_state()? {
UpgradeableLoaderState::Program {
programdata_address,
} => {
if programdata_address != close_key {
ic_logger_msg!(
log_collector,
"ProgramData account does not match ProgramData account"
);
return Err(InstructionError::InvalidArgument);
}
drop(program_account);
common_close_account(
&authority_address,
transaction_context,
instruction_context,
&log_collector,
)?;
let clock = invoke_context.get_sysvar_cache().get_clock()?;
if invoke_context
.feature_set
.is_active(&delay_visibility_of_program_deployment::id())
{
invoke_context.programs_modified_by_tx.replenish(
program_key,
Arc::new(LoadedProgram::new_tombstone(
clock.slot,
LoadedProgramType::Closed,
)),
);
} else {
invoke_context
.programs_updated_only_for_global_cache
.replenish(
program_key,
Arc::new(LoadedProgram::new_tombstone(
clock.slot,
LoadedProgramType::Closed,
)),
);
}
}
_ => {
ic_logger_msg!(log_collector, "Invalid Program account");
return Err(InstructionError::InvalidArgument);
}
}
ic_logger_msg!(log_collector, "Closed Program {}", program_key);
}
_ => {
ic_logger_msg!(log_collector, "Account does not support closing");
return Err(InstructionError::InvalidArgument);
2021-03-19 13:13:20 -07:00
}
}
}
UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => {
if !invoke_context
.feature_set
.is_active(&enable_bpf_loader_extend_program_ix::ID)
{
return Err(InstructionError::InvalidInstructionData);
}
if additional_bytes == 0 {
ic_logger_msg!(log_collector, "Additional bytes must be greater than 0");
return Err(InstructionError::InvalidInstructionData);
}
const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0;
const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1;
#[allow(dead_code)]
// System program is only required when a CPI is performed
const OPTIONAL_SYSTEM_PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 2;
const OPTIONAL_PAYER_ACCOUNT_INDEX: IndexOfAccount = 3;
let programdata_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
let programdata_key = *programdata_account.get_key();
if program_id != programdata_account.get_owner() {
ic_logger_msg!(log_collector, "ProgramData owner is invalid");
return Err(InstructionError::InvalidAccountOwner);
}
if !programdata_account.is_writable() {
ic_logger_msg!(log_collector, "ProgramData is not writable");
return Err(InstructionError::InvalidArgument);
}
let program_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_ACCOUNT_INDEX)?;
if !program_account.is_writable() {
ic_logger_msg!(log_collector, "Program account is not writable");
return Err(InstructionError::InvalidArgument);
}
if program_account.get_owner() != program_id {
ic_logger_msg!(log_collector, "Program account not owned by loader");
return Err(InstructionError::InvalidAccountOwner);
}
let program_key = *program_account.get_key();
match program_account.get_state()? {
UpgradeableLoaderState::Program {
programdata_address,
} => {
if programdata_address != programdata_key {
ic_logger_msg!(
log_collector,
"Program account does not match ProgramData account"
);
return Err(InstructionError::InvalidArgument);
}
}
_ => {
ic_logger_msg!(log_collector, "Invalid Program account");
return Err(InstructionError::InvalidAccountData);
}
}
drop(program_account);
let old_len = programdata_account.get_data().len();
let new_len = old_len.saturating_add(additional_bytes as usize);
if new_len > MAX_PERMITTED_DATA_LENGTH as usize {
ic_logger_msg!(
log_collector,
"Extended ProgramData length of {} bytes exceeds max account data length of {} bytes",
new_len,
MAX_PERMITTED_DATA_LENGTH
);
return Err(InstructionError::InvalidRealloc);
}
let clock_slot = invoke_context
.get_sysvar_cache()
.get_clock()
.map(|clock| clock.slot)?;
let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address,
} = programdata_account.get_state()?
{
if clock_slot == slot {
ic_logger_msg!(log_collector, "Program was extended in this block already");
return Err(InstructionError::InvalidArgument);
}
if upgrade_authority_address.is_none() {
ic_logger_msg!(
log_collector,
"Cannot extend ProgramData accounts that are not upgradeable"
);
return Err(InstructionError::Immutable);
}
upgrade_authority_address
} else {
ic_logger_msg!(log_collector, "ProgramData state is invalid");
return Err(InstructionError::InvalidAccountData);
};
let required_payment = {
let balance = programdata_account.get_lamports();
let rent = invoke_context.get_sysvar_cache().get_rent()?;
let min_balance = rent.minimum_balance(new_len).max(1);
min_balance.saturating_sub(balance)
};
// Borrowed accounts need to be dropped before native_invoke
drop(programdata_account);
// Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context
let program_id = *program_id;
if required_payment > 0 {
let payer_key = *transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(
OPTIONAL_PAYER_ACCOUNT_INDEX,
)?,
)?;
invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &programdata_key, required_payment)
.into(),
&[],
)?;
}
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut programdata_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
programdata_account.set_data_length(new_len)?;
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
deploy_program!(
invoke_context,
program_key,
&program_id,
UpgradeableLoaderState::size_of_program().saturating_add(new_len),
clock_slot,
{
drop(programdata_account);
},
programdata_account
.get_data()
.get(programdata_data_offset..)
.ok_or(InstructionError::AccountDataTooSmall)?,
);
let mut programdata_account = instruction_context
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
programdata_account.set_state(&UpgradeableLoaderState::ProgramData {
slot: clock_slot,
upgrade_authority_address,
})?;
ic_logger_msg!(
log_collector,
"Extended ProgramData account by {} bytes",
additional_bytes
);
}
}
2020-12-14 15:35:10 -08:00
Ok(())
}
fn common_close_account(
authority_address: &Option<Pubkey>,
transaction_context: &TransactionContext,
instruction_context: &InstructionContext,
log_collector: &Option<Rc<RefCell<LogCollector>>>,
) -> Result<(), InstructionError> {
if authority_address.is_none() {
ic_logger_msg!(log_collector, "Account is immutable");
return Err(InstructionError::Immutable);
}
if *authority_address
!= Some(*transaction_context.get_key_of_account_at_index(
instruction_context.get_index_of_instruction_account_in_transaction(2)?,
)?)
{
ic_logger_msg!(log_collector, "Incorrect authority provided");
return Err(InstructionError::IncorrectAuthority);
}
if !instruction_context.is_instruction_account_signer(2)? {
ic_logger_msg!(log_collector, "Authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
let mut close_account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let mut recipient_account =
instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
recipient_account.checked_add_lamports(close_account.get_lamports())?;
close_account.set_lamports(0)?;
2021-10-28 09:04:03 -07:00
close_account.set_state(&UpgradeableLoaderState::Uninitialized)?;
Ok(())
}
fn process_loader_instruction(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let program_id = instruction_context.get_last_program_key(transaction_context)?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
if program.get_owner() != program_id {
2021-01-21 09:57:59 -08:00
ic_msg!(
invoke_context,
"Executable account not owned by the BPF loader"
);
2020-12-14 15:35:10 -08:00
return Err(InstructionError::IncorrectProgramId);
}
let is_program_signer = program.is_signer();
2020-12-14 15:35:10 -08:00
match limited_deserialize(instruction_data)? {
LoaderInstruction::Write { offset, bytes } => {
if !is_program_signer {
2021-01-21 09:57:59 -08:00
ic_msg!(invoke_context, "Program account did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
drop(program);
write_program_data(offset as usize, &bytes, invoke_context)?;
2020-12-14 15:35:10 -08:00
}
LoaderInstruction::Finalize => {
if !is_program_signer {
ic_msg!(invoke_context, "key[0] did not sign the transaction");
2020-12-14 15:35:10 -08:00
return Err(InstructionError::MissingRequiredSignature);
}
deploy_program!(
invoke_context,
*program.get_key(),
program.get_owner(),
program.get_data().len(),
invoke_context.programs_loaded_for_tx_batch.slot(),
{},
program.get_data(),
);
program.set_executable(true)?;
ic_msg!(invoke_context, "Finalized account {:?}", program.get_key());
2020-12-14 15:35:10 -08:00
}
}
Ok(())
}
fn execute<'a, 'b: 'a>(
executable: &'a Executable<RequisiteVerifier, InvokeContext<'static>>,
invoke_context: &'a mut InvokeContext<'b>,
) -> Result<(), Box<dyn std::error::Error>> {
2023-07-05 10:46:21 -07:00
// We dropped the lifetime tracking in the Executor by setting it to 'static,
// thus we need to reintroduce the correct lifetime of InvokeContext here again.
let executable = unsafe {
mem::transmute::<_, &'a Executable<RequisiteVerifier, InvokeContext<'b>>>(executable)
};
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
let (program_id, is_loader_deprecated) = {
let program_account =
instruction_context.try_borrow_last_program_account(transaction_context)?;
(
*program_account.get_key(),
*program_account.get_owner() == bpf_loader_deprecated::id(),
)
};
#[cfg(any(target_os = "windows", not(target_arch = "x86_64")))]
let use_jit = false;
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
let use_jit = executable.get_compiled_program().is_some();
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
let direct_mapping = invoke_context
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
.feature_set
.is_active(&bpf_account_data_direct_mapping::id());
let mut serialize_time = Measure::start("serialize");
let (parameter_bytes, regions, accounts_metadata) = serialization::serialize_parameters(
invoke_context.transaction_context,
instruction_context,
invoke_context
.feature_set
.is_active(&cap_bpf_program_instruction_accounts::ID),
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
!direct_mapping,
)?;
serialize_time.stop();
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
// save the account addresses so in case we hit an AccessViolation error we
// can map to a more specific error
let account_region_addrs = accounts_metadata
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
.iter()
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
.map(|m| {
let vm_end = m
.vm_data_addr
.saturating_add(m.original_data_len as u64)
.saturating_add(if !is_loader_deprecated {
MAX_PERMITTED_DATA_INCREASE as u64
} else {
0
});
m.vm_data_addr..vm_end
})
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
.collect::<Vec<_>>();
let mut create_vm_time = Measure::start("create_vm");
let mut execute_time;
let execution_result = {
let compute_meter_prev = invoke_context.get_remaining();
create_vm!(vm, executable, regions, accounts_metadata, invoke_context,);
let mut vm = match vm {
Ok(info) => info,
Err(e) => {
ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", e);
return Err(Box::new(InstructionError::ProgramEnvironmentSetupFailure));
}
};
create_vm_time.stop();
execute_time = Measure::start("execute");
2023-07-05 10:46:21 -07:00
let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit);
drop(vm);
ic_logger_msg!(
log_collector,
"Program {} consumed {} of {} compute units",
&program_id,
compute_units_consumed,
compute_meter_prev
);
let (_returned_from_program_id, return_data) =
invoke_context.transaction_context.get_return_data();
if !return_data.is_empty() {
stable_log::program_return(&log_collector, &program_id, return_data);
}
match result {
ProgramResult::Ok(status) if status != SUCCESS => {
let error: InstructionError = if (status == MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED
&& !invoke_context
.feature_set
.is_active(&cap_accounts_data_allocations_per_transaction::id()))
|| (status == MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED
&& !invoke_context
.feature_set
.is_active(&limit_max_instruction_trace_length::id()))
{
// Until the cap_accounts_data_allocations_per_transaction feature is
// enabled, map the `MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED` error to `InvalidError`.
// Until the limit_max_instruction_trace_length feature is
// enabled, map the `MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED` error to `InvalidError`.
InstructionError::InvalidError
} else {
status.into()
};
Err(Box::new(error) as Box<dyn std::error::Error>)
}
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
ProgramResult::Err(mut error) => {
if direct_mapping {
if let Some(EbpfError::AccessViolation(
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
_pc,
AccessType::Store,
address,
_size,
_section_name,
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
)) = error.downcast_ref()
{
// If direct_mapping is enabled and a program tries to write to a readonly
// region we'll get a memory access violation. Map it to a more specific
// error so it's easier for developers to see what happened.
if let Some((instruction_account_index, _)) = account_region_addrs
.iter()
.enumerate()
.find(|(_, vm_region)| vm_region.contains(address))
{
let transaction_context = &invoke_context.transaction_context;
let instruction_context =
transaction_context.get_current_instruction_context()?;
let account = instruction_context.try_borrow_instruction_account(
transaction_context,
instruction_account_index as IndexOfAccount,
)?;
error = Box::new(if account.is_executable() {
InstructionError::ExecutableDataModified
} else if account.is_writable() {
InstructionError::ExternalAccountDataModified
} else {
InstructionError::ReadonlyDataModified
})
}
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
}
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
}
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
Err(error)
}
_ => Ok(()),
}
};
execute_time.stop();
fn deserialize_parameters(
invoke_context: &mut InvokeContext,
parameter_bytes: &[u8],
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
copy_account_data: bool,
) -> Result<(), InstructionError> {
serialization::deserialize_parameters(
invoke_context.transaction_context,
invoke_context
.transaction_context
.get_current_instruction_context()?,
Account data direct mapping (#28053) * AccountSharedData: make data_mut() private This ensures that the inner Vec is never handed out. This is in preparation of enforcing that the capacity of the inner vec never shrinks, which is required for direct mapping. * Adds the feature bpf_account_data_direct_mapping. * Remaps EbpfError::AccessViolation into InstructionError::ReadonlyDataModified. * WIP: Memory regions for each instruction account in create_vm(). * Fix serialization benches, run both copy and !copy variants * rbpf-cli: fix build * BorrowedAccount: ensure that account capacity is never reduced Accounts can be directly mapped in address space. Their capacity can't be reduced mid transaction as that would create holes in vm address space that point to invalid host memory. * bpf_load: run serialization tests for both copy and !copy account data * bpf_loader: add Serializer::write_account * fix lints * BorrowedAccount: make_data_mut is host only * Fix unused import warning * Fix lints * cpi: add explicit direct_mapping arg to update_(callee|caller)_account * cpi: rename account_data_or_only_realloc_padding to serialized_data * cpi: add CallerAccount::original_data_len comment * cpi: add update_callee_account direct_mapping test * cpi: add test_update_caller_account_data_direct_mapping and fix bug We used to have a bug in zeroing data when shrinking account, where we zeroed the spare account capacity but not the realloc padding. * cpi: add tests for mutated readonly accounts * cpi: update_caller_account doesn't need to change .serialized_data when direct_mapping is on * cpi: update_caller_account: ensure that account capacity is always enough Introduce a better way to ensure that account capacity never goes below what might be mapped in memory regions. * cpi: zero account capacity using the newly introduced BorrowedAccount::spare_data_capacity_mut() Before we were using BorrowedAccount::get_data_mut() to get the base pointer to the account data, then we were slicing the spare capacity from it. Calling get_data_mut() doesn't work if an account has been closed tho, since the current program doesn't own the account anymore and therefore get_data_mut() errors out. * bpf_loader: fix same lint for the umpteenth time * bpf_loader: map AccessViolation to ReadonlyDataModified only for account region violations * programs/sbf: realloc: add test for large write after realloc Add a test that after a realloc does a large write that spans the original account length and the realloc area. This ensures that memory mapping works correctly across the boundary. * programs/sbf: run test_program_sbf_realloc with both direct_mapping on and off By default test banks test with all features on. This ensures we keep testing the existing code until the new feature is enabled. * bpf_loader: tweak memcmp syscall Split the actual memcmp code in a separate function. Remove check indexing the slices since the slices are guaranteed to have the correct length by construction. * bpf_loader: tweak the memset syscall Use slice::fill, which is effectively memset. * bpf_loader: syscalls: update mem syscalls to work with non contiguous memory With direct mapping enabled, accounts can now span multiple memory regions. * fix lint, rebase mem_ops * Implement CoW for writable accounts * Fix CI * Move CoW to the MemoryMapping level * Update after rbpf API change * Fix merge screwup * Add create_vm macro. Fix benches. * cpi: simplify update_caller_account Simplify the logic to update a caller's memory region when a callee causes an account data pointer to change (eg during CoW) * benches/bpf_loader: move serialization out of create_vm bench * benches/bpf_loader: don't copy accounts when direct mapping is on * Fix review nits * bpf_loader: mem_ops: handle u64 overflow in MemoryChunkIterator::new When starting at u64::MAX, the chunk iterator would always return the empty sequence (None on the first next()) call, instead of returning a memory access violation. Use checked instead of saturating arithmetic to detect the condition and error out. This commit also adds more tests around boundary conditions. * Fix loader-v3 tests: data_mut => data_as_mut_slice * Fix CI * bpf_loader: fix tuner bench: account must be writable With direct mapping on, invalid writes are caught early meaning the tuner would fail on the first store and not consume the whole budget like the benchmark expects. --------- Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net>
2023-04-28 13:54:39 -07:00
copy_account_data,
parameter_bytes,
&invoke_context.get_syscall_context()?.accounts_metadata,
)
}
let mut deserialize_time = Measure::start("deserialize");
let execute_or_deserialize_result = execution_result.and_then(|_| {
direct mapping: misc fixes (#32649) * transaction_context: update make_data_mut comment * bpf_loader: cpi: pass SerializeAccountMetadata to CallerAccount::from* We now have a way to provide CallerAccount with trusted values coming from our internal serialization code and not from untrusted vm space * bpf_loader: direct_mapping: enforce account info pointers to be immutable When direct mapping is enabled, we might need to update account data memory regions across CPI calls. Since the only way we have to retrieve the regions is based on their vm addresses, we enforce vm addresses to be stable. Accounts can still be mutated and resized of course, but it must be done in place. This also locks all other AccountInfo pointers, since there's no legitimate reason to make them point to anything else. * bpf_loader: cpi: access ref_to_len_in_vm through VmValue Direct mapping needs to translate vm values at each access since permissions of the underlying memory might have changed. * direct mapping: improve memory permission tracking across CPI calls Ensure that the data and realloc regions of an account always track the account's permissions. In order to do this, we also need to split realloc regions in their own self contained regions, where before we had: [account fields][account data][account realloc + more account fields + next account fields][next account data][...] we now have: [account fields][account data][account realloc][more account fields + next account fields][next account data][...] Tested in TEST_[FORBID|ALLOW]_WRITE_AFTER_OWNERSHIP_CHANGE* Additionally when direct mapping is on, we must update all perms at once before doing account data updates. Otherwise, updating an account might write into another account whose perms we haven't updated yet. Tested in TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE. * bpf_loader: serialization: address review comment don't return vm_addr from push_account_region * bpf_loader: rename push_account_region to push_account_data_region * cpi: fix slow edge case zeroing extra account capacity after shrinking an account When returning from CPI we need to zero all the account memory up to the original length only if we know we're potentially dealing with uninitialized memory. When we know that the spare capacity has deterministic content, we only need to zero new_len..prev_len. This fixes a slow edge case that was triggerable by the following scenario: - load a large account (say 10MB) into the vm - shrink to 10 bytes - would memset 10..10MB - shrink to 9 bytes - would memset 9..10MB - shrink to 8 bytes - would memset 8..10MB - ... Now instead in the scenario above the following will happen: - load a large account (say 10MB) into the vm - shrink to 10 bytes - memsets 10..10MB - shrink to 9 bytes - memsets 9..10 - shrink to 8 bytes - memset 8..9 - ... * bpf_loader: add account_data_region_memory_state helper Shared between serialization and CPI to figure out the MemoryState of an account. * cpi: direct_mapping: error out if ref_to_len_in_vm points to account memory If ref_to_len_in_vm is allowed to be in account memory, calles could mutate it, essentially letting callees directly mutate callers memory. * bpf_loader: direct_mapping: map AccessViolation -> InstructionError Return the proper ReadonlyDataModified / ExecutableDataModified / ExternalAccountDataModified depending on where the violation occurs * bpf_loader: cpi: remove unnecessary infallible slice::get call
2023-08-30 02:57:24 -07:00
deserialize_parameters(invoke_context, parameter_bytes.as_slice(), !direct_mapping)
.map_err(|error| Box::new(error) as Box<dyn std::error::Error>)
});
deserialize_time.stop();
// Update the timings
let timings = &mut invoke_context.timings;
timings.serialize_us = timings.serialize_us.saturating_add(serialize_time.as_us());
timings.create_vm_us = timings.create_vm_us.saturating_add(create_vm_time.as_us());
timings.execute_us = timings.execute_us.saturating_add(execute_time.as_us());
timings.deserialize_us = timings
.deserialize_us
.saturating_add(deserialize_time.as_us());
execute_or_deserialize_result
}
pub mod test_utils {
use {
super::*, solana_program_runtime::loaded_programs::DELAY_VISIBILITY_SLOT_OFFSET,
solana_sdk::account::ReadableAccount,
};
pub fn load_all_invoked_programs(invoke_context: &mut InvokeContext) {
let mut load_program_metrics = LoadProgramMetrics::default();
let program_runtime_environment = create_program_runtime_environment_v1(
&invoke_context.feature_set,
invoke_context.get_compute_budget(),
false, /* deployment */
false, /* debugging_features */
);
let program_runtime_environment = Arc::new(program_runtime_environment.unwrap());
let num_accounts = invoke_context.transaction_context.get_number_of_accounts();
for index in 0..num_accounts {
let account = invoke_context
.transaction_context
.get_account_at_index(index)
.expect("Failed to get the account")
.borrow();
let owner = account.owner();
if check_loader_id(owner) {
let pubkey = invoke_context
.transaction_context
.get_key_of_account_at_index(index)
.expect("Failed to get account key");
if let Ok(loaded_program) = load_program_from_bytes(
&FeatureSet::all_enabled(),
None,
&mut load_program_metrics,
account.data(),
owner,
account.data().len(),
0,
program_runtime_environment.clone(),
) {
invoke_context
.programs_modified_by_tx
.set_slot_for_tests(DELAY_VISIBILITY_SLOT_OFFSET);
invoke_context
.programs_modified_by_tx
.replenish(*pubkey, Arc::new(loaded_program));
}
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
rand::Rng,
solana_program_runtime::{
invoke_context::mock_process_instruction, with_mock_invoke_context,
},
solana_rbpf::{
2023-07-05 10:46:21 -07:00
elf::SBPFVersion,
verifier::Verifier,
vm::{Config, ContextObject, FunctionRegistry},
},
solana_sdk::{
account::{
create_account_shared_data_for_test as create_account_for_test, AccountSharedData,
ReadableAccount, WritableAccount,
},
account_utils::StateMut,
clock::Clock,
instruction::{AccountMeta, InstructionError},
pubkey::Pubkey,
rent::Rent,
system_program, sysvar,
},
std::{fs::File, io::Read, ops::Range, sync::atomic::AtomicU64},
2020-04-28 14:33:56 -07:00
};
struct TestContextObject {
remaining: u64,
}
impl ContextObject for TestContextObject {
fn trace(&mut self, _state: [u64; 12]) {}
fn consume(&mut self, amount: u64) {
self.remaining = self.remaining.saturating_sub(amount);
}
fn get_remaining(&self) -> u64 {
self.remaining
}
2020-04-28 14:33:56 -07:00
}
fn process_instruction(
loader_id: &Pubkey,
program_indices: &[IndexOfAccount],
instruction_data: &[u8],
transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
instruction_accounts: Vec<AccountMeta>,
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
mock_process_instruction(
loader_id,
program_indices.to_vec(),
instruction_data,
transaction_accounts,
instruction_accounts,
expected_result,
super::process_instruction,
|invoke_context| {
test_utils::load_all_invoked_programs(invoke_context);
},
|_invoke_context| {},
)
}
fn load_program_account_from_elf(loader_id: &Pubkey, path: &str) -> AccountSharedData {
let mut file = File::open(path).expect("file open failed");
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
let rent = Rent::default();
let mut program_account =
AccountSharedData::new(rent.minimum_balance(elf.len()), 0, loader_id);
program_account.set_data(elf);
program_account.set_executable(true);
program_account
}
2020-11-12 13:13:42 -08:00
#[test]
2021-05-28 14:24:02 -07:00
#[should_panic(expected = "LDDWCannotBeLast")]
2020-11-12 13:13:42 -08:00
fn test_bpf_loader_check_load_dw() {
let prog = &[
0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, // first half of lddw
];
2023-07-05 10:46:21 -07:00
RequisiteVerifier::verify(
prog,
&Config::default(),
&SBPFVersion::V2,
&FunctionRegistry::default(),
)
.unwrap();
2020-11-12 13:13:42 -08:00
}
#[test]
fn test_bpf_loader_write() {
let loader_id = bpf_loader::id();
let program_id = Pubkey::new_unique();
let mut program_account = AccountSharedData::new(1, 0, &loader_id);
let instruction_data = bincode::serialize(&LoaderInstruction::Write {
offset: 3,
bytes: vec![1, 2, 3],
})
.unwrap();
// Case: No program account
process_instruction(
&loader_id,
&[],
&instruction_data,
Vec::new(),
Vec::new(),
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: Not signed
process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account.clone())],
vec![AccountMeta {
pubkey: program_id,
is_signer: false,
is_writable: true,
}],
Err(InstructionError::MissingRequiredSignature),
);
// Case: Write bytes to an offset
program_account.set_data(vec![0; 6]);
let accounts = process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account.clone())],
vec![AccountMeta {
pubkey: program_id,
is_signer: true,
is_writable: true,
}],
Ok(()),
);
2022-03-10 11:48:33 -08:00
assert_eq!(&vec![0, 0, 0, 1, 2, 3], accounts.first().unwrap().data());
// Case: Overflow
program_account.set_data(vec![0; 5]);
process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account)],
vec![AccountMeta {
pubkey: program_id,
is_signer: true,
is_writable: true,
}],
Err(InstructionError::AccountDataTooSmall),
);
}
#[test]
fn test_bpf_loader_finalize() {
let loader_id = bpf_loader::id();
let program_id = Pubkey::new_unique();
let mut program_account =
load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so");
program_account.set_executable(false);
let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap();
// Case: No program account
process_instruction(
&loader_id,
&[],
&instruction_data,
Vec::new(),
Vec::new(),
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: Not signed
process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account.clone())],
vec![AccountMeta {
pubkey: program_id,
is_signer: false,
is_writable: true,
}],
Err(InstructionError::MissingRequiredSignature),
);
// Case: Finalize
let accounts = process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account.clone())],
vec![AccountMeta {
pubkey: program_id,
is_signer: true,
is_writable: true,
}],
Ok(()),
);
2022-03-10 11:48:33 -08:00
assert!(accounts.first().unwrap().executable());
// Case: Finalize bad ELF
2022-03-10 11:48:33 -08:00
*program_account.data_as_mut_slice().get_mut(0).unwrap() = 0;
process_instruction(
&loader_id,
&[],
&instruction_data,
vec![(program_id, program_account)],
vec![AccountMeta {
pubkey: program_id,
is_signer: true,
is_writable: true,
}],
2019-12-04 12:03:29 -08:00
Err(InstructionError::InvalidAccountData),
);
}
#[test]
fn test_bpf_loader_invoke_main() {
let loader_id = bpf_loader::id();
let program_id = Pubkey::new_unique();
let mut program_account =
load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so");
let parameter_id = Pubkey::new_unique();
let parameter_account = AccountSharedData::new(1, 0, &loader_id);
let parameter_meta = AccountMeta {
pubkey: parameter_id,
is_signer: false,
is_writable: false,
};
// Case: No program account
process_instruction(
&loader_id,
&[],
&[],
Vec::new(),
Vec::new(),
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: Only a program account
process_instruction(
&loader_id,
&[0],
&[],
vec![(program_id, program_account.clone())],
Vec::new(),
Ok(()),
);
// Case: With program and parameter account
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
// Case: With duplicate accounts
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account),
],
vec![parameter_meta.clone(), parameter_meta],
Ok(()),
);
// Case: limited budget
mock_process_instruction(
&loader_id,
vec![0],
&[],
vec![(program_id, program_account.clone())],
Vec::new(),
Err(InstructionError::ProgramFailedToComplete),
super::process_instruction,
|invoke_context| {
invoke_context.mock_set_remaining(0);
test_utils::load_all_invoked_programs(invoke_context);
},
|_invoke_context| {},
);
// Case: Account not a program
program_account.set_executable(false);
process_instruction(
&loader_id,
&[0],
&[],
vec![(program_id, program_account)],
Vec::new(),
Err(InstructionError::IncorrectProgramId),
);
}
#[test]
fn test_bpf_loader_serialize_unaligned() {
let loader_id = bpf_loader_deprecated::id();
let program_id = Pubkey::new_unique();
let program_account =
load_program_account_from_elf(&loader_id, "test_elfs/out/noop_unaligned.so");
let parameter_id = Pubkey::new_unique();
let parameter_account = AccountSharedData::new(1, 0, &loader_id);
let parameter_meta = AccountMeta {
pubkey: parameter_id,
is_signer: false,
is_writable: false,
};
// Case: With program and parameter account
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
// Case: With duplicate accounts
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account),
(parameter_id, parameter_account),
],
vec![parameter_meta.clone(), parameter_meta],
Ok(()),
);
}
#[test]
fn test_bpf_loader_serialize_aligned() {
let loader_id = bpf_loader::id();
let program_id = Pubkey::new_unique();
let program_account =
load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so");
let parameter_id = Pubkey::new_unique();
let parameter_account = AccountSharedData::new(1, 0, &loader_id);
let parameter_meta = AccountMeta {
pubkey: parameter_id,
is_signer: false,
is_writable: false,
};
// Case: With program and parameter account
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
// Case: With duplicate accounts
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account),
(parameter_id, parameter_account),
],
vec![parameter_meta.clone(), parameter_meta],
Ok(()),
2020-12-14 15:35:10 -08:00
);
}
#[test]
fn test_bpf_loader_upgradeable_initialize_buffer() {
let loader_id = bpf_loader_upgradeable::id();
2020-12-14 15:35:10 -08:00
let buffer_address = Pubkey::new_unique();
let buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id);
let authority_address = Pubkey::new_unique();
let authority_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id);
let instruction_data =
bincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap();
let instruction_accounts = vec![
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
];
2020-12-14 15:35:10 -08:00
// Case: Success
let accounts = process_instruction(
&loader_id,
&[],
&instruction_data,
vec![
(buffer_address, buffer_account),
(authority_address, authority_account),
],
instruction_accounts.clone(),
2020-12-14 15:35:10 -08:00
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address)
}
);
2020-12-14 15:35:10 -08:00
// Case: Already initialized
let accounts = process_instruction(
&loader_id,
&[],
&instruction_data,
vec![
2022-03-10 11:48:33 -08:00
(buffer_address, accounts.first().unwrap().clone()),
(authority_address, accounts.get(1).unwrap().clone()),
],
instruction_accounts,
2020-12-14 15:35:10 -08:00
Err(InstructionError::AccountAlreadyInitialized),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address)
}
);
2020-12-14 15:35:10 -08:00
}
#[test]
fn test_bpf_loader_upgradeable_write() {
let loader_id = bpf_loader_upgradeable::id();
2020-12-14 15:35:10 -08:00
let buffer_address = Pubkey::new_unique();
let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id);
let instruction_accounts = vec![
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: buffer_address,
is_signer: true,
is_writable: false,
},
];
2020-12-14 15:35:10 -08:00
// Case: Not initialized
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 0,
bytes: vec![42; 9],
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts.clone(),
2020-12-14 15:35:10 -08:00
Err(InstructionError::InvalidAccountData),
);
// Case: Write entire buffer
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 0,
bytes: vec![42; 9],
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts.clone(),
2020-12-14 15:35:10 -08:00
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address)
}
);
2020-12-14 15:35:10 -08:00
assert_eq!(
2022-03-10 11:48:33 -08:00
&accounts
.first()
.unwrap()
.data()
.get(UpgradeableLoaderState::size_of_buffer_metadata()..)
2022-03-10 11:48:33 -08:00
.unwrap(),
2020-12-14 15:35:10 -08:00
&[42; 9]
);
// Case: Write portion of the buffer
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 3,
bytes: vec![42; 6],
})
.unwrap();
let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id);
2020-12-14 15:35:10 -08:00
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts.clone(),
2020-12-14 15:35:10 -08:00
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address)
}
);
2020-12-14 15:35:10 -08:00
assert_eq!(
2022-03-10 11:48:33 -08:00
&accounts
.first()
.unwrap()
.data()
.get(UpgradeableLoaderState::size_of_buffer_metadata()..)
2022-03-10 11:48:33 -08:00
.unwrap(),
2020-12-14 15:35:10 -08:00
&[0, 0, 0, 42, 42, 42, 42, 42, 42]
);
// Case: overflow size
2020-12-14 15:35:10 -08:00
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 0,
bytes: vec![42; 10],
2020-12-14 15:35:10 -08:00
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts.clone(),
Err(InstructionError::AccountDataTooSmall),
2020-12-14 15:35:10 -08:00
);
// Case: overflow offset
2020-12-14 15:35:10 -08:00
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 1,
bytes: vec![42; 9],
2020-12-14 15:35:10 -08:00
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts.clone(),
2020-12-14 15:35:10 -08:00
Err(InstructionError::AccountDataTooSmall),
);
// Case: Not signed
2020-12-14 15:35:10 -08:00
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 0,
2020-12-14 15:35:10 -08:00
bytes: vec![42; 9],
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
vec![
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: wrong authority
let authority_address = Pubkey::new_unique();
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 1,
bytes: vec![42; 9],
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(buffer_address),
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(buffer_address, buffer_account.clone()),
(authority_address, buffer_account.clone()),
],
vec![
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::IncorrectAuthority),
2020-12-14 15:35:10 -08:00
);
// Case: None authority
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write {
offset: 1,
bytes: vec![42; 9],
})
.unwrap();
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: None,
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![(buffer_address, buffer_account.clone())],
instruction_accounts,
Err(InstructionError::Immutable),
);
2020-12-14 15:35:10 -08:00
}
fn truncate_data(account: &mut AccountSharedData, len: usize) {
let mut data = account.data().to_vec();
data.truncate(len);
account.set_data(data);
}
2020-12-14 15:35:10 -08:00
#[test]
fn test_bpf_loader_upgradeable_upgrade() {
let mut file = File::open("test_elfs/out/noop_aligned.so").expect("file open failed");
2020-12-14 15:35:10 -08:00
let mut elf_orig = Vec::new();
file.read_to_end(&mut elf_orig).unwrap();
let mut file = File::open("test_elfs/out/noop_unaligned.so").expect("file open failed");
2020-12-14 15:35:10 -08:00
let mut elf_new = Vec::new();
file.read_to_end(&mut elf_new).unwrap();
assert_ne!(elf_orig.len(), elf_new.len());
const SLOT: u64 = 42;
2020-12-14 15:35:10 -08:00
let buffer_address = Pubkey::new_unique();
let upgrade_authority_address = Pubkey::new_unique();
2020-12-14 15:35:10 -08:00
fn get_accounts(
buffer_address: &Pubkey,
buffer_authority: &Pubkey,
2020-12-14 15:35:10 -08:00
upgrade_authority_address: &Pubkey,
elf_orig: &[u8],
elf_new: &[u8],
) -> (Vec<(Pubkey, AccountSharedData)>, Vec<AccountMeta>) {
let loader_id = bpf_loader_upgradeable::id();
let program_address = Pubkey::new_unique();
let spill_address = Pubkey::new_unique();
let rent = Rent::default();
let min_program_balance =
1.max(rent.minimum_balance(UpgradeableLoaderState::size_of_program()));
let min_programdata_balance = 1.max(rent.minimum_balance(
UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())),
));
let (programdata_address, _) =
Pubkey::find_program_address(&[program_address.as_ref()], &loader_id);
let mut buffer_account = AccountSharedData::new(
2020-12-14 15:35:10 -08:00
1,
UpgradeableLoaderState::size_of_buffer(elf_new.len()),
2020-12-14 15:35:10 -08:00
&bpf_loader_upgradeable::id(),
);
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(*buffer_authority),
})
2020-12-14 15:35:10 -08:00
.unwrap();
2022-03-10 11:48:33 -08:00
buffer_account
.data_as_mut_slice()
.get_mut(UpgradeableLoaderState::size_of_buffer_metadata()..)
2022-03-10 11:48:33 -08:00
.unwrap()
.copy_from_slice(elf_new);
let mut programdata_account = AccountSharedData::new(
2020-12-14 15:35:10 -08:00
min_programdata_balance,
UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())),
2020-12-14 15:35:10 -08:00
&bpf_loader_upgradeable::id(),
);
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot: SLOT,
2020-12-14 15:35:10 -08:00
upgrade_authority_address: Some(*upgrade_authority_address),
})
.unwrap();
let mut program_account = AccountSharedData::new(
2020-12-14 15:35:10 -08:00
min_program_balance,
UpgradeableLoaderState::size_of_program(),
2020-12-14 15:35:10 -08:00
&bpf_loader_upgradeable::id(),
);
program_account.set_executable(true);
2020-12-14 15:35:10 -08:00
program_account
.set_state(&UpgradeableLoaderState::Program {
programdata_address,
2020-12-14 15:35:10 -08:00
})
.unwrap();
let spill_account = AccountSharedData::new(0, 0, &Pubkey::new_unique());
let rent_account = create_account_for_test(&rent);
let clock_account = create_account_for_test(&Clock {
slot: SLOT.saturating_add(1),
..Clock::default()
});
let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let transaction_accounts = vec![
(programdata_address, programdata_account),
(program_address, program_account),
(*buffer_address, buffer_account),
(spill_address, spill_account),
(sysvar::rent::id(), rent_account),
(sysvar::clock::id(), clock_account),
(*upgrade_authority_address, upgrade_authority_account),
];
let instruction_accounts = vec![
AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: program_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: *buffer_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: spill_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: sysvar::rent::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: sysvar::clock::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: *upgrade_authority_address,
is_signer: true,
is_writable: false,
},
];
(transaction_accounts, instruction_accounts)
}
2020-12-14 15:35:10 -08:00
fn process_instruction(
transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
instruction_accounts: Vec<AccountMeta>,
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
let instruction_data =
bincode::serialize(&UpgradeableLoaderInstruction::Upgrade).unwrap();
mock_process_instruction(
&bpf_loader_upgradeable::id(),
Vec::new(),
&instruction_data,
transaction_accounts,
instruction_accounts,
expected_result,
super::process_instruction,
|_invoke_context| {},
|_invoke_context| {},
2020-12-14 15:35:10 -08:00
)
}
// Case: Success
let (transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
let accounts = process_instruction(transaction_accounts, instruction_accounts, Ok(()));
let min_programdata_balance = Rent::default().minimum_balance(
UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())),
2020-12-14 15:35:10 -08:00
);
2022-03-10 11:48:33 -08:00
assert_eq!(
min_programdata_balance,
accounts.first().unwrap().lamports()
);
assert_eq!(0, accounts.get(2).unwrap().lamports());
assert_eq!(1, accounts.get(3).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
2020-12-14 15:35:10 -08:00
assert_eq!(
state,
UpgradeableLoaderState::ProgramData {
slot: SLOT.saturating_add(1),
2020-12-14 15:35:10 -08:00
upgrade_authority_address: Some(upgrade_authority_address)
}
);
2022-03-10 11:48:33 -08:00
for (i, byte) in accounts
.first()
.unwrap()
.data()
.get(
UpgradeableLoaderState::size_of_programdata_metadata()
..UpgradeableLoaderState::size_of_programdata(elf_new.len()),
2022-03-10 11:48:33 -08:00
)
.unwrap()
2020-12-14 15:35:10 -08:00
.iter()
.enumerate()
{
2022-03-10 11:48:33 -08:00
assert_eq!(*elf_new.get(i).unwrap(), *byte);
2020-12-14 15:35:10 -08:00
}
// Case: not upgradable
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(0)
.unwrap()
.1
2020-12-14 15:35:10 -08:00
.set_state(&UpgradeableLoaderState::ProgramData {
slot: SLOT,
2020-12-14 15:35:10 -08:00
upgrade_authority_address: None,
})
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::Immutable),
2020-12-14 15:35:10 -08:00
);
// Case: wrong authority
let (mut transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
let invalid_upgrade_authority_address = Pubkey::new_unique();
2022-03-10 11:48:33 -08:00
transaction_accounts.get_mut(6).unwrap().0 = invalid_upgrade_authority_address;
instruction_accounts.get_mut(6).unwrap().pubkey = invalid_upgrade_authority_address;
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::IncorrectAuthority),
2020-12-14 15:35:10 -08:00
);
// Case: authority did not sign
let (transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
instruction_accounts.get_mut(6).unwrap().is_signer = false;
process_instruction(
transaction_accounts,
instruction_accounts,
2020-12-14 15:35:10 -08:00
Err(InstructionError::MissingRequiredSignature),
);
// Case: Buffer account and spill account alias
let (transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
*instruction_accounts.get_mut(3).unwrap() = instruction_accounts.get(2).unwrap().clone();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::AccountBorrowFailed),
);
// Case: Programdata account and spill account alias
let (transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
*instruction_accounts.get_mut(3).unwrap() = instruction_accounts.get(0).unwrap().clone();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::AccountBorrowFailed),
);
2020-12-14 15:35:10 -08:00
// Case: Program account not executable
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(1)
.unwrap()
.1
.set_executable(false);
process_instruction(
transaction_accounts,
instruction_accounts,
2020-12-14 15:35:10 -08:00
Err(InstructionError::AccountNotExecutable),
);
// Case: Program account now owned by loader
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(1)
.unwrap()
.1
.set_owner(Pubkey::new_unique());
process_instruction(
transaction_accounts,
instruction_accounts,
2020-12-14 15:35:10 -08:00
Err(InstructionError::IncorrectProgramId),
);
// Case: Program account not writable
let (transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
instruction_accounts.get_mut(1).unwrap().is_writable = false;
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::InvalidArgument),
2020-12-14 15:35:10 -08:00
);
// Case: Program account not initialized
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(1)
.unwrap()
.1
2020-12-14 15:35:10 -08:00
.set_state(&UpgradeableLoaderState::Uninitialized)
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
2020-12-14 15:35:10 -08:00
Err(InstructionError::InvalidAccountData),
);
// Case: Program ProgramData account mismatch
let (mut transaction_accounts, mut instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
let invalid_programdata_address = Pubkey::new_unique();
2022-03-10 11:48:33 -08:00
transaction_accounts.get_mut(0).unwrap().0 = invalid_programdata_address;
instruction_accounts.get_mut(0).unwrap().pubkey = invalid_programdata_address;
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::InvalidArgument),
);
// Case: Buffer account not initialized
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(2)
.unwrap()
.1
2020-12-14 15:35:10 -08:00
.set_state(&UpgradeableLoaderState::Uninitialized)
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::InvalidArgument),
2020-12-14 15:35:10 -08:00
);
// Case: Buffer account too big
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts.get_mut(2).unwrap().1 = AccountSharedData::new(
1,
UpgradeableLoaderState::size_of_buffer(
elf_orig.len().max(elf_new.len()).saturating_add(1),
),
&bpf_loader_upgradeable::id(),
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(2)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(upgrade_authority_address),
})
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::AccountDataTooSmall),
2020-12-14 15:35:10 -08:00
);
// Case: Buffer account too small
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&upgrade_authority_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(2)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(upgrade_authority_address),
})
2020-12-14 15:35:10 -08:00
.unwrap();
2022-03-10 11:48:33 -08:00
truncate_data(&mut transaction_accounts.get_mut(2).unwrap().1, 5);
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::InvalidAccountData),
2020-12-14 15:35:10 -08:00
);
// Case: Mismatched buffer and program authority
let (transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&buffer_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::IncorrectAuthority),
);
// Case: No buffer authority
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&buffer_address,
&upgrade_authority_address,
&elf_orig,
&elf_new,
2020-12-14 15:35:10 -08:00
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(2)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: None,
})
2020-12-14 15:35:10 -08:00
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::IncorrectAuthority),
2020-12-14 15:35:10 -08:00
);
// Case: No buffer and program authority
let (mut transaction_accounts, instruction_accounts) = get_accounts(
&buffer_address,
&buffer_address,
2020-12-14 15:35:10 -08:00
&upgrade_authority_address,
&elf_orig,
&elf_new,
);
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(0)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::ProgramData {
slot: SLOT,
upgrade_authority_address: None,
})
.unwrap();
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(2)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: None,
})
.unwrap();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::IncorrectAuthority),
2020-12-14 15:35:10 -08:00
);
}
#[test]
fn test_bpf_loader_upgradeable_set_upgrade_authority() {
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap();
let loader_id = bpf_loader_upgradeable::id();
2020-12-14 15:35:10 -08:00
let slot = 0;
let upgrade_authority_address = Pubkey::new_unique();
let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
2020-12-14 15:35:10 -08:00
let new_upgrade_authority_address = Pubkey::new_unique();
let new_upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
2020-12-14 15:35:10 -08:00
let program_address = Pubkey::new_unique();
2021-06-18 00:03:58 -07:00
let (programdata_address, _) = Pubkey::find_program_address(
&[program_address.as_ref()],
&bpf_loader_upgradeable::id(),
);
let mut programdata_account = AccountSharedData::new(
2020-12-14 15:35:10 -08:00
1,
UpgradeableLoaderState::size_of_programdata(0),
2020-12-14 15:35:10 -08:00
&bpf_loader_upgradeable::id(),
);
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(upgrade_authority_address),
})
.unwrap();
let programdata_meta = AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
};
let upgrade_authority_meta = AccountMeta {
pubkey: upgrade_authority_address,
is_signer: true,
is_writable: false,
};
let new_upgrade_authority_meta = AccountMeta {
pubkey: new_upgrade_authority_address,
is_signer: false,
is_writable: false,
};
// Case: Set to new authority
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![
programdata_meta.clone(),
upgrade_authority_meta.clone(),
new_upgrade_authority_meta.clone(),
],
2020-12-14 15:35:10 -08:00
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
2020-12-14 15:35:10 -08:00
assert_eq!(
state,
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(new_upgrade_authority_address),
}
);
// Case: Not upgradeable
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
],
vec![programdata_meta.clone(), upgrade_authority_meta.clone()],
2020-12-14 15:35:10 -08:00
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
2020-12-14 15:35:10 -08:00
assert_eq!(
state,
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: None,
}
);
// Case: Authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
],
vec![
programdata_meta.clone(),
AccountMeta {
pubkey: upgrade_authority_address,
is_signer: false,
is_writable: false,
},
],
2020-12-14 15:35:10 -08:00
Err(InstructionError::MissingRequiredSignature),
);
// Case: wrong authority
let invalid_upgrade_authority_address = Pubkey::new_unique();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(
invalid_upgrade_authority_address,
upgrade_authority_account.clone(),
),
(new_upgrade_authority_address, new_upgrade_authority_account),
],
vec![
programdata_meta.clone(),
AccountMeta {
pubkey: invalid_upgrade_authority_address,
is_signer: true,
is_writable: false,
},
new_upgrade_authority_meta,
],
Err(InstructionError::IncorrectAuthority),
2020-12-14 15:35:10 -08:00
);
// Case: No authority
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: None,
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
],
vec![programdata_meta.clone(), upgrade_authority_meta.clone()],
Err(InstructionError::Immutable),
2020-12-14 15:35:10 -08:00
);
// Case: Not a ProgramData account
programdata_account
.set_state(&UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account),
],
vec![programdata_meta, upgrade_authority_meta],
Err(InstructionError::InvalidArgument),
);
}
#[test]
fn test_bpf_loader_upgradeable_set_upgrade_authority_checked() {
let instruction =
bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap();
let loader_id = bpf_loader_upgradeable::id();
let slot = 0;
let upgrade_authority_address = Pubkey::new_unique();
let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let new_upgrade_authority_address = Pubkey::new_unique();
let new_upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let program_address = Pubkey::new_unique();
let (programdata_address, _) = Pubkey::find_program_address(
&[program_address.as_ref()],
&bpf_loader_upgradeable::id(),
);
let mut programdata_account = AccountSharedData::new(
1,
UpgradeableLoaderState::size_of_programdata(0),
&bpf_loader_upgradeable::id(),
);
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(upgrade_authority_address),
})
.unwrap();
let programdata_meta = AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
};
let upgrade_authority_meta = AccountMeta {
pubkey: upgrade_authority_address,
is_signer: true,
is_writable: false,
};
let new_upgrade_authority_meta = AccountMeta {
pubkey: new_upgrade_authority_address,
is_signer: true,
is_writable: false,
};
// Case: Set to new authority
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![
programdata_meta.clone(),
upgrade_authority_meta.clone(),
new_upgrade_authority_meta.clone(),
],
Ok(()),
);
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(new_upgrade_authority_address),
}
);
// Case: set to same authority
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
],
vec![
programdata_meta.clone(),
upgrade_authority_meta.clone(),
upgrade_authority_meta.clone(),
],
Ok(()),
);
// Case: present authority not in instruction
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![programdata_meta.clone(), new_upgrade_authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: new authority not in instruction
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![programdata_meta.clone(), upgrade_authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: present authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![
programdata_meta.clone(),
AccountMeta {
pubkey: upgrade_authority_address,
is_signer: false,
is_writable: false,
},
new_upgrade_authority_meta.clone(),
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: New authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
(
new_upgrade_authority_address,
new_upgrade_authority_account.clone(),
),
],
vec![
programdata_meta.clone(),
upgrade_authority_meta.clone(),
AccountMeta {
pubkey: new_upgrade_authority_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: wrong present authority
let invalid_upgrade_authority_address = Pubkey::new_unique();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(
invalid_upgrade_authority_address,
upgrade_authority_account.clone(),
),
(new_upgrade_authority_address, new_upgrade_authority_account),
],
vec![
programdata_meta.clone(),
AccountMeta {
pubkey: invalid_upgrade_authority_address,
is_signer: true,
is_writable: false,
},
new_upgrade_authority_meta.clone(),
],
Err(InstructionError::IncorrectAuthority),
);
// Case: programdata is immutable
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: None,
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account.clone()),
],
vec![
programdata_meta.clone(),
upgrade_authority_meta.clone(),
new_upgrade_authority_meta.clone(),
],
Err(InstructionError::Immutable),
);
// Case: Not a ProgramData account
programdata_account
.set_state(&UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(upgrade_authority_address, upgrade_authority_account),
],
vec![
programdata_meta,
upgrade_authority_meta,
new_upgrade_authority_meta,
],
Err(InstructionError::InvalidArgument),
);
}
#[test]
fn test_bpf_loader_upgradeable_set_buffer_authority() {
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap();
let loader_id = bpf_loader_upgradeable::id();
let invalid_authority_address = Pubkey::new_unique();
let authority_address = Pubkey::new_unique();
let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let new_authority_address = Pubkey::new_unique();
let new_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let buffer_address = Pubkey::new_unique();
let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id);
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
})
.unwrap();
let mut transaction_accounts = vec![
(buffer_address, buffer_account.clone()),
(authority_address, authority_account.clone()),
(new_authority_address, new_authority_account.clone()),
];
let buffer_meta = AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: true,
};
let authority_meta = AccountMeta {
pubkey: authority_address,
is_signer: true,
is_writable: false,
};
let new_authority_meta = AccountMeta {
pubkey: new_authority_address,
is_signer: false,
is_writable: false,
};
// Case: New authority required
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), authority_meta.clone()],
Err(InstructionError::IncorrectAuthority),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
}
);
// Case: Set to new authority
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
})
.unwrap();
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
new_authority_meta.clone(),
],
Ok(()),
);
2022-03-10 11:48:33 -08:00
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(new_authority_address),
}
);
// Case: Authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
new_authority_meta.clone(),
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: wrong authority
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(buffer_address, buffer_account.clone()),
(invalid_authority_address, authority_account),
(new_authority_address, new_authority_account),
],
vec![
buffer_meta.clone(),
AccountMeta {
pubkey: invalid_authority_address,
is_signer: true,
is_writable: false,
},
new_authority_meta.clone(),
],
Err(InstructionError::IncorrectAuthority),
);
// Case: No authority
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), authority_meta.clone()],
Err(InstructionError::IncorrectAuthority),
);
// Case: Set to no authority
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(0)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: None,
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
new_authority_meta.clone(),
],
Err(InstructionError::Immutable),
);
// Case: Not a Buffer account
2022-03-10 11:48:33 -08:00
transaction_accounts
.get_mut(0)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta, authority_meta, new_authority_meta],
Err(InstructionError::InvalidArgument),
);
}
#[test]
fn test_bpf_loader_upgradeable_set_buffer_authority_checked() {
let instruction =
bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap();
let loader_id = bpf_loader_upgradeable::id();
let invalid_authority_address = Pubkey::new_unique();
let authority_address = Pubkey::new_unique();
let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let new_authority_address = Pubkey::new_unique();
let new_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let buffer_address = Pubkey::new_unique();
let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id);
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
})
.unwrap();
let mut transaction_accounts = vec![
(buffer_address, buffer_account.clone()),
(authority_address, authority_account.clone()),
(new_authority_address, new_authority_account.clone()),
];
let buffer_meta = AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: true,
};
let authority_meta = AccountMeta {
pubkey: authority_address,
is_signer: true,
is_writable: false,
};
let new_authority_meta = AccountMeta {
pubkey: new_authority_address,
is_signer: true,
is_writable: false,
};
// Case: Set to new authority
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
})
.unwrap();
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
new_authority_meta.clone(),
],
Ok(()),
);
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(
state,
UpgradeableLoaderState::Buffer {
authority_address: Some(new_authority_address),
}
);
// Case: set to same authority
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
authority_meta.clone(),
],
Ok(()),
);
// Case: Missing current authority
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), new_authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: Missing new authority
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
// Case: wrong present authority
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(buffer_address, buffer_account.clone()),
(invalid_authority_address, authority_account),
(new_authority_address, new_authority_account),
],
vec![
buffer_meta.clone(),
AccountMeta {
pubkey: invalid_authority_address,
is_signer: true,
is_writable: false,
},
new_authority_meta.clone(),
],
Err(InstructionError::IncorrectAuthority),
);
// Case: present authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
new_authority_meta.clone(),
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: new authority did not sign
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
AccountMeta {
pubkey: new_authority_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::MissingRequiredSignature),
);
// Case: Not a Buffer account
transaction_accounts
.get_mut(0)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
new_authority_meta.clone(),
],
Err(InstructionError::InvalidArgument),
);
// Case: Buffer is immutable
transaction_accounts
.get_mut(0)
.unwrap()
.1
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: None,
})
.unwrap();
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta, authority_meta, new_authority_meta],
Err(InstructionError::Immutable),
);
}
#[test]
fn test_bpf_loader_upgradeable_close() {
let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Close).unwrap();
let loader_id = bpf_loader_upgradeable::id();
let invalid_authority_address = Pubkey::new_unique();
let authority_address = Pubkey::new_unique();
let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let recipient_address = Pubkey::new_unique();
let recipient_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let buffer_address = Pubkey::new_unique();
let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(128), &loader_id);
buffer_account
.set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address),
})
.unwrap();
let uninitialized_address = Pubkey::new_unique();
let mut uninitialized_account = AccountSharedData::new(
1,
UpgradeableLoaderState::size_of_programdata(0),
&loader_id,
);
uninitialized_account
.set_state(&UpgradeableLoaderState::Uninitialized)
.unwrap();
let programdata_address = Pubkey::new_unique();
let mut programdata_account = AccountSharedData::new(
1,
UpgradeableLoaderState::size_of_programdata(128),
&loader_id,
);
programdata_account
.set_state(&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(authority_address),
})
.unwrap();
let program_address = Pubkey::new_unique();
let mut program_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_program(), &loader_id);
program_account.set_executable(true);
program_account
.set_state(&UpgradeableLoaderState::Program {
programdata_address,
})
.unwrap();
let clock_account = create_account_for_test(&Clock {
slot: 1,
..Clock::default()
});
let transaction_accounts = vec![
(buffer_address, buffer_account.clone()),
(recipient_address, recipient_account.clone()),
(authority_address, authority_account.clone()),
];
let buffer_meta = AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: true,
};
let recipient_meta = AccountMeta {
pubkey: recipient_address,
is_signer: false,
is_writable: true,
};
let authority_meta = AccountMeta {
pubkey: authority_address,
is_signer: true,
is_writable: false,
};
// Case: close a buffer account
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts,
vec![
buffer_meta.clone(),
recipient_meta.clone(),
authority_meta.clone(),
],
Ok(()),
);
2022-03-10 11:48:33 -08:00
assert_eq!(0, accounts.first().unwrap().lamports());
assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Case: close with wrong authority
process_instruction(
&loader_id,
&[],
&instruction,
vec![
(buffer_address, buffer_account.clone()),
(recipient_address, recipient_account.clone()),
(invalid_authority_address, authority_account.clone()),
],
vec![
buffer_meta,
recipient_meta.clone(),
AccountMeta {
pubkey: invalid_authority_address,
is_signer: true,
is_writable: false,
},
],
Err(InstructionError::IncorrectAuthority),
);
// Case: close an uninitialized account
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![
(uninitialized_address, uninitialized_account.clone()),
(recipient_address, recipient_account.clone()),
(invalid_authority_address, authority_account.clone()),
],
vec![
AccountMeta {
pubkey: uninitialized_address,
is_signer: false,
is_writable: true,
},
recipient_meta.clone(),
authority_meta.clone(),
],
Ok(()),
);
2022-03-10 11:48:33 -08:00
assert_eq!(0, accounts.first().unwrap().lamports());
assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Case: close a program account
let accounts = process_instruction(
&loader_id,
&[],
&instruction,
vec![
(programdata_address, programdata_account.clone()),
(recipient_address, recipient_account.clone()),
(authority_address, authority_account.clone()),
(program_address, program_account.clone()),
(sysvar::clock::id(), clock_account.clone()),
],
vec![
AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
},
recipient_meta,
authority_meta,
AccountMeta {
pubkey: program_address,
is_signer: false,
is_writable: true,
},
],
Ok(()),
);
2022-03-10 11:48:33 -08:00
assert_eq!(0, accounts.first().unwrap().lamports());
assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Try to invoke closed account
programdata_account = accounts.first().unwrap().clone();
program_account = accounts.get(3).unwrap().clone();
process_instruction(
&loader_id,
&[0, 1],
&[],
vec![
(programdata_address, programdata_account.clone()),
(program_address, program_account.clone()),
],
Vec::new(),
Err(InstructionError::InvalidAccountData),
);
// Case: Reopen should fail
process_instruction(
&loader_id,
&[],
&bincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
max_data_len: 0,
})
.unwrap(),
vec![
(recipient_address, recipient_account),
(programdata_address, programdata_account),
(program_address, program_account),
(buffer_address, buffer_account),
(
sysvar::rent::id(),
create_account_for_test(&Rent::default()),
),
(sysvar::clock::id(), clock_account),
(
system_program::id(),
AccountSharedData::new(0, 0, &system_program::id()),
),
(authority_address, authority_account),
],
vec![
AccountMeta {
pubkey: recipient_address,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: program_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: sysvar::rent::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: sysvar::clock::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: system_program::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::AccountAlreadyInitialized),
);
}
/// fuzzing utility function
fn fuzz<F>(
bytes: &[u8],
outer_iters: usize,
inner_iters: usize,
offset: Range<usize>,
value: Range<u8>,
work: F,
) where
F: Fn(&mut [u8]),
{
let mut rng = rand::thread_rng();
for _ in 0..outer_iters {
let mut mangled_bytes = bytes.to_vec();
for _ in 0..inner_iters {
Bump rand to 0.8, rand_chacha to 0.3, getrandom to 0.2 (#32871) * sdk: Add concurrent support for rand 0.7 and 0.8 * Update rand, rand_chacha, and getrandom versions * Run command to replace `gen_range` Run `git grep -l gen_range | xargs sed -i'' -e 's/gen_range(\(\S*\), /gen_range(\1../' * sdk: Fix users of older `gen_range` * Replace `hash::new_rand` with `hash::new_with_thread_rng` Run: ``` git grep -l hash::new_rand | xargs sed -i'' -e 's/hash::new_rand([^)]*/hash::new_with_thread_rng(/' ``` * perf: Use `Keypair::new()` instead of `generate` * Use older rand version in zk-token-sdk * program-runtime: Inline random key generation * bloom: Fix clippy warnings in tests * streamer: Scope rng usage correctly * perf: Fix clippy warning * accounts-db: Map to char to generate a random string * Remove `from_secret_key_bytes`, it's just `keypair_from_seed` * ledger: Generate keypairs by hand * ed25519-tests: Use new rand * runtime: Use new rand in all tests * gossip: Clean up clippy and inline keypair generators * core: Inline keypair generation for tests * Push sbf lockfile change * sdk: Sort dependencies correctly * Remove `hash::new_with_thread_rng`, use `Hash::new_unique()` * Use Keypair::new where chacha isn't used * sdk: Fix build by marking rand 0.7 optional * Hardcode secret key length, add static assertion * Unify `getrandom` crate usage to fix linking errors * bloom: Fix tests that require a random hash * Remove some dependencies, try to unify others * Remove unnecessary uses of rand and rand_core * Update lockfiles * Add back some dependencies to reduce rebuilds * Increase max rebuilds from 14 to 15 * frozen-abi: Remove `getrandom` * Bump rebuilds to 17 * Remove getrandom from zk-token-proof
2023-08-21 10:11:21 -07:00
let offset = rng.gen_range(offset.start..offset.end);
let value = rng.gen_range(value.start..value.end);
2022-03-10 11:48:33 -08:00
*mangled_bytes.get_mut(offset).unwrap() = value;
work(&mut mangled_bytes);
}
}
}
#[test]
#[ignore]
fn test_fuzz() {
let loader_id = bpf_loader::id();
let program_id = Pubkey::new_unique();
// Create program account
let mut file = File::open("test_elfs/out/noop_aligned.so").expect("file open failed");
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
// Mangle the whole file
fuzz(
&elf,
1_000_000_000,
100,
0..elf.len(),
0..255,
|bytes: &mut [u8]| {
let mut program_account = AccountSharedData::new(1, 0, &loader_id);
program_account.set_data(bytes.to_vec());
program_account.set_executable(true);
process_instruction(
&loader_id,
&[],
&[],
vec![(program_id, program_account)],
Vec::new(),
Ok(()),
);
},
);
}
#[test]
fn test_calculate_heap_cost() {
let heap_cost = 8_u64;
// heap allocations are in 32K block, `heap_cost` of CU is consumed per additional 32k
// when `enable_heap_size_round_up` not enabled:
{
// assert less than 32K heap should cost zero unit
assert_eq!(0, calculate_heap_cost(31_u64 * 1024, heap_cost, false));
// assert exact 32K heap should be cost zero unit
assert_eq!(0, calculate_heap_cost(32_u64 * 1024, heap_cost, false));
// assert slightly more than 32K heap is mistakenly cost zero unit
assert_eq!(0, calculate_heap_cost(33_u64 * 1024, heap_cost, false));
// assert exact 64K heap should cost 1 * heap_cost
assert_eq!(
heap_cost,
calculate_heap_cost(64_u64 * 1024, heap_cost, false)
);
}
// when `enable_heap_size_round_up` is enabled:
{
// assert less than 32K heap should cost zero unit
assert_eq!(0, calculate_heap_cost(31_u64 * 1024, heap_cost, true));
// assert exact 32K heap should be cost zero unit
assert_eq!(0, calculate_heap_cost(32_u64 * 1024, heap_cost, true));
// assert slightly more than 32K heap should cost 1 * heap_cost
assert_eq!(
heap_cost,
calculate_heap_cost(33_u64 * 1024, heap_cost, true)
);
// assert exact 64K heap should cost 1 * heap_cost
assert_eq!(
heap_cost,
calculate_heap_cost(64_u64 * 1024, heap_cost, true)
);
}
}
fn deploy_test_program(
invoke_context: &mut InvokeContext,
program_id: Pubkey,
) -> Result<(), InstructionError> {
let mut file = File::open("test_elfs/out/noop_unaligned.so").expect("file open failed");
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
deploy_program!(
invoke_context,
program_id,
&bpf_loader_upgradeable::id(),
elf.len(),
2,
{},
&elf
);
Ok(())
}
#[test]
fn test_program_usage_count_on_upgrade() {
let transaction_accounts = vec![];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let program_id = Pubkey::new_unique();
let env = Arc::new(BuiltinProgram::new_loader(Config::default()));
let program = LoadedProgram {
program: LoadedProgramType::Unloaded(env),
account_size: 0,
deployment_slot: 0,
effective_slot: 0,
maybe_expiration_slot: None,
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
};
invoke_context
.programs_modified_by_tx
.replenish(program_id, Arc::new(program));
assert_matches!(
deploy_test_program(&mut invoke_context, program_id,),
Ok(())
);
let updated_program = invoke_context
.programs_modified_by_tx
.find(&program_id)
.expect("Didn't find upgraded program in the cache");
assert_eq!(updated_program.deployment_slot, 2);
assert_eq!(
updated_program.tx_usage_counter.load(Ordering::Relaxed),
100
);
assert_eq!(
updated_program.ix_usage_counter.load(Ordering::Relaxed),
100
);
}
#[test]
fn test_program_usage_count_on_non_upgrade() {
let transaction_accounts = vec![];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let program_id = Pubkey::new_unique();
let env = Arc::new(BuiltinProgram::new_loader(Config::default()));
let program = LoadedProgram {
program: LoadedProgramType::Unloaded(env),
account_size: 0,
deployment_slot: 0,
effective_slot: 0,
maybe_expiration_slot: None,
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
};
invoke_context
.programs_modified_by_tx
.replenish(program_id, Arc::new(program));
let program_id2 = Pubkey::new_unique();
assert_matches!(
deploy_test_program(&mut invoke_context, program_id2),
Ok(())
);
let program2 = invoke_context
.programs_modified_by_tx
.find(&program_id2)
.expect("Didn't find upgraded program in the cache");
assert_eq!(program2.deployment_slot, 2);
assert_eq!(program2.tx_usage_counter.load(Ordering::Relaxed), 0);
assert_eq!(program2.ix_usage_counter.load(Ordering::Relaxed), 0);
}
}