Allow programs to realloc their accounts within limits (#19475)
This commit is contained in:
@ -31,8 +31,8 @@ use solana_sdk::{
|
||||
clock::Clock,
|
||||
entrypoint::{HEAP_LENGTH, SUCCESS},
|
||||
feature_set::{
|
||||
add_missing_program_error_mappings, close_upgradeable_program_accounts, fix_write_privs,
|
||||
reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero,
|
||||
add_missing_program_error_mappings, close_upgradeable_program_accounts, do_support_realloc,
|
||||
fix_write_privs, reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero,
|
||||
},
|
||||
ic_logger_msg, ic_msg,
|
||||
instruction::{AccountMeta, InstructionError},
|
||||
@ -150,12 +150,19 @@ pub fn create_vm<'a>(
|
||||
program: &'a dyn Executable<BpfError, ThisInstructionMeter>,
|
||||
parameter_bytes: &mut [u8],
|
||||
invoke_context: &'a mut dyn InvokeContext,
|
||||
orig_data_lens: &'a [usize],
|
||||
) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> {
|
||||
let compute_budget = invoke_context.get_compute_budget();
|
||||
let mut heap =
|
||||
AlignedMemory::new_with_size(compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN);
|
||||
let mut vm = EbpfVm::new(program, heap.as_slice_mut(), parameter_bytes)?;
|
||||
syscalls::bind_syscall_context_objects(loader_id, &mut vm, invoke_context, heap)?;
|
||||
syscalls::bind_syscall_context_objects(
|
||||
loader_id,
|
||||
&mut vm,
|
||||
invoke_context,
|
||||
heap,
|
||||
orig_data_lens,
|
||||
)?;
|
||||
Ok(vm)
|
||||
}
|
||||
|
||||
@ -903,6 +910,7 @@ impl Executor for BpfExecutor {
|
||||
self.executable.as_ref(),
|
||||
parameter_bytes.as_slice_mut(),
|
||||
invoke_context,
|
||||
&account_lengths,
|
||||
) {
|
||||
Ok(info) => info,
|
||||
Err(e) => {
|
||||
@ -979,6 +987,7 @@ impl Executor for BpfExecutor {
|
||||
keyed_accounts,
|
||||
parameter_bytes.as_slice(),
|
||||
&account_lengths,
|
||||
invoke_context.is_feature_active(&do_support_realloc::id()),
|
||||
)?;
|
||||
deserialize_time.stop();
|
||||
invoke_context.update_timing(
|
||||
|
@ -7,6 +7,7 @@ use solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
keyed_account::KeyedAccount,
|
||||
pubkey::Pubkey,
|
||||
system_instruction::MAX_PERMITTED_DATA_LENGTH,
|
||||
};
|
||||
use std::{
|
||||
io::prelude::*,
|
||||
@ -48,11 +49,12 @@ pub fn deserialize_parameters(
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
buffer: &[u8],
|
||||
account_lengths: &[usize],
|
||||
do_support_realloc: bool,
|
||||
) -> Result<(), InstructionError> {
|
||||
if *loader_id == bpf_loader_deprecated::id() {
|
||||
deserialize_parameters_unaligned(keyed_accounts, buffer, account_lengths)
|
||||
} else {
|
||||
deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths)
|
||||
deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths, do_support_realloc)
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +263,7 @@ pub fn deserialize_parameters_aligned(
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
buffer: &[u8],
|
||||
account_lengths: &[usize],
|
||||
do_support_realloc: bool,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut start = size_of::<u64>(); // number of accounts
|
||||
for (i, (keyed_account, pre_len)) in keyed_accounts
|
||||
@ -285,13 +288,22 @@ pub fn deserialize_parameters_aligned(
|
||||
start += size_of::<u64>(); // lamports
|
||||
let post_len = LittleEndian::read_u64(&buffer[start..]) as usize;
|
||||
start += size_of::<u64>(); // data length
|
||||
let mut data_end = start + *pre_len;
|
||||
if post_len != *pre_len
|
||||
&& (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE
|
||||
{
|
||||
data_end = start + post_len;
|
||||
}
|
||||
|
||||
let data_end = if do_support_realloc {
|
||||
if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE
|
||||
|| post_len > MAX_PERMITTED_DATA_LENGTH as usize
|
||||
{
|
||||
return Err(InstructionError::InvalidRealloc);
|
||||
}
|
||||
start + post_len
|
||||
} else {
|
||||
let mut data_end = start + *pre_len;
|
||||
if post_len != *pre_len
|
||||
&& (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE
|
||||
{
|
||||
data_end = start + post_len;
|
||||
}
|
||||
data_end
|
||||
};
|
||||
account.set_data_from_slice(&buffer[start..data_end]);
|
||||
start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
|
||||
start += (start as *const u8).align_offset(align_of::<u128>());
|
||||
@ -467,6 +479,7 @@ mod tests {
|
||||
&de_keyed_accounts,
|
||||
serialized.as_slice(),
|
||||
&account_lengths,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
for ((account, de_keyed_account), key) in
|
||||
@ -520,6 +533,7 @@ mod tests {
|
||||
&de_keyed_accounts,
|
||||
serialized.as_slice(),
|
||||
&account_lengths,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
for ((account, de_keyed_account), key) in
|
||||
|
@ -21,8 +21,9 @@ use solana_sdk::{
|
||||
feature_set::{
|
||||
allow_native_ids, blake3_syscall_enabled, check_seed_length,
|
||||
close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar,
|
||||
libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix, return_data_syscall_enabled,
|
||||
secp256k1_recover_syscall_enabled, sol_log_data_syscall_enabled,
|
||||
do_support_realloc, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
|
||||
return_data_syscall_enabled, secp256k1_recover_syscall_enabled,
|
||||
sol_log_data_syscall_enabled,
|
||||
},
|
||||
hash::{Hasher, HASH_BYTES},
|
||||
ic_msg,
|
||||
@ -209,6 +210,7 @@ pub fn bind_syscall_context_objects<'a>(
|
||||
vm: &mut EbpfVm<'a, BpfError, crate::ThisInstructionMeter>,
|
||||
invoke_context: &'a mut dyn InvokeContext,
|
||||
heap: AlignedMemory,
|
||||
orig_data_lens: &'a [usize],
|
||||
) -> Result<(), EbpfError<BpfError>> {
|
||||
let compute_budget = invoke_context.get_compute_budget();
|
||||
|
||||
@ -430,6 +432,7 @@ pub fn bind_syscall_context_objects<'a>(
|
||||
vm.bind_syscall_context_object(
|
||||
Box::new(SyscallInvokeSignedC {
|
||||
invoke_context: invoke_context.clone(),
|
||||
orig_data_lens,
|
||||
loader_id,
|
||||
}),
|
||||
None,
|
||||
@ -437,6 +440,7 @@ pub fn bind_syscall_context_objects<'a>(
|
||||
vm.bind_syscall_context_object(
|
||||
Box::new(SyscallInvokeSignedRust {
|
||||
invoke_context: invoke_context.clone(),
|
||||
orig_data_lens,
|
||||
loader_id,
|
||||
}),
|
||||
None,
|
||||
@ -1519,9 +1523,10 @@ impl<'a> SyscallObject<BpfError> for SyscallBlake3<'a> {
|
||||
|
||||
// Cross-program invocation syscalls
|
||||
|
||||
struct AccountReferences<'a> {
|
||||
struct CallerAccount<'a> {
|
||||
lamports: &'a mut u64,
|
||||
owner: &'a mut Pubkey,
|
||||
original_data_len: usize,
|
||||
data: &'a mut [u8],
|
||||
vm_data_addr: u64,
|
||||
ref_to_len_in_vm: &'a mut u64,
|
||||
@ -1531,10 +1536,7 @@ struct AccountReferences<'a> {
|
||||
}
|
||||
type TranslatedAccounts<'a> = (
|
||||
Vec<usize>,
|
||||
Vec<(
|
||||
Rc<RefCell<AccountSharedData>>,
|
||||
Option<AccountReferences<'a>>,
|
||||
)>,
|
||||
Vec<(Rc<RefCell<AccountSharedData>>, Option<CallerAccount<'a>>)>,
|
||||
);
|
||||
|
||||
/// Implemented by language specific data structure translators
|
||||
@ -1567,6 +1569,7 @@ trait SyscallInvokeSigned<'a> {
|
||||
/// Cross-program invocation called from Rust
|
||||
pub struct SyscallInvokeSignedRust<'a> {
|
||||
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||
orig_data_lens: &'a [usize],
|
||||
loader_id: &'a Pubkey,
|
||||
}
|
||||
impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
||||
@ -1694,9 +1697,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
||||
)
|
||||
};
|
||||
|
||||
Ok(AccountReferences {
|
||||
Ok(CallerAccount {
|
||||
lamports,
|
||||
owner,
|
||||
original_data_len: 0, // set later
|
||||
data,
|
||||
vm_data_addr,
|
||||
ref_to_len_in_vm,
|
||||
@ -1711,6 +1715,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
||||
&account_info_keys,
|
||||
account_infos,
|
||||
invoke_context,
|
||||
self.orig_data_lens,
|
||||
translate,
|
||||
)
|
||||
}
|
||||
@ -1839,6 +1844,7 @@ struct SolSignerSeedsC {
|
||||
/// Cross-program invocation called from C
|
||||
pub struct SyscallInvokeSignedC<'a> {
|
||||
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
|
||||
orig_data_lens: &'a [usize],
|
||||
loader_id: &'a Pubkey,
|
||||
}
|
||||
impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
||||
@ -1964,9 +1970,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
||||
self.loader_id,
|
||||
)?;
|
||||
|
||||
Ok(AccountReferences {
|
||||
Ok(CallerAccount {
|
||||
lamports,
|
||||
owner,
|
||||
original_data_len: 0, // set later
|
||||
data,
|
||||
vm_data_addr,
|
||||
ref_to_len_in_vm,
|
||||
@ -1981,6 +1988,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
||||
&account_info_keys,
|
||||
account_infos,
|
||||
invoke_context,
|
||||
self.orig_data_lens,
|
||||
translate,
|
||||
)
|
||||
}
|
||||
@ -2065,10 +2073,11 @@ fn get_translated_accounts<'a, T, F>(
|
||||
account_info_keys: &[&Pubkey],
|
||||
account_infos: &[T],
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
orig_data_lens: &[usize],
|
||||
do_translate: F,
|
||||
) -> Result<TranslatedAccounts<'a>, EbpfError<BpfError>>
|
||||
where
|
||||
F: Fn(&T, &mut dyn InvokeContext) -> Result<AccountReferences<'a>, EbpfError<BpfError>>,
|
||||
F: Fn(&T, &mut dyn InvokeContext) -> Result<CallerAccount<'a>, EbpfError<BpfError>>,
|
||||
{
|
||||
let demote_program_write_locks =
|
||||
invoke_context.is_feature_active(&demote_program_write_locks::id());
|
||||
@ -2083,25 +2092,27 @@ where
|
||||
account_indices.push(account_index);
|
||||
accounts.push((account, None));
|
||||
continue;
|
||||
} else if let Some(account_ref_index) =
|
||||
} else if let Some(caller_account_index) =
|
||||
account_info_keys.iter().position(|key| *key == account_key)
|
||||
{
|
||||
let account_ref = do_translate(&account_infos[account_ref_index], invoke_context)?;
|
||||
let mut caller_account =
|
||||
do_translate(&account_infos[caller_account_index], invoke_context)?;
|
||||
{
|
||||
let mut account = account.borrow_mut();
|
||||
account.copy_into_owner_from_slice(account_ref.owner.as_ref());
|
||||
account.set_data_from_slice(account_ref.data);
|
||||
account.set_lamports(*account_ref.lamports);
|
||||
account.set_executable(account_ref.executable);
|
||||
account.set_rent_epoch(account_ref.rent_epoch);
|
||||
account.copy_into_owner_from_slice(caller_account.owner.as_ref());
|
||||
caller_account.original_data_len = orig_data_lens[caller_account_index];
|
||||
account.set_data_from_slice(caller_account.data);
|
||||
account.set_lamports(*caller_account.lamports);
|
||||
account.set_executable(caller_account.executable);
|
||||
account.set_rent_epoch(caller_account.rent_epoch);
|
||||
}
|
||||
let account_ref = if message.is_writable(i, demote_program_write_locks) {
|
||||
Some(account_ref)
|
||||
let caller_account = if message.is_writable(i, demote_program_write_locks) {
|
||||
Some(caller_account)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
account_indices.push(account_index);
|
||||
accounts.push((account, account_ref));
|
||||
accounts.push((account, caller_account));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -2176,6 +2187,7 @@ fn call<'a>(
|
||||
invoke_context
|
||||
.get_compute_meter()
|
||||
.consume(invoke_context.get_compute_budget().invoke_units)?;
|
||||
let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id());
|
||||
|
||||
// Translate and verify caller's data
|
||||
let instruction =
|
||||
@ -2219,13 +2231,14 @@ fn call<'a>(
|
||||
.map_err(SyscallError::InstructionError)?;
|
||||
|
||||
// Copy results back to caller
|
||||
for (account, account_ref) in accounts.iter_mut() {
|
||||
let account = account.borrow();
|
||||
if let Some(account_ref) = account_ref {
|
||||
*account_ref.lamports = account.lamports();
|
||||
*account_ref.owner = *account.owner();
|
||||
if account_ref.data.len() != account.data().len() {
|
||||
if !account_ref.data.is_empty() {
|
||||
for (callee_account, caller_account) in accounts.iter_mut() {
|
||||
if let Some(caller_account) = caller_account {
|
||||
let callee_account = callee_account.borrow();
|
||||
*caller_account.lamports = callee_account.lamports();
|
||||
*caller_account.owner = *callee_account.owner();
|
||||
let new_len = callee_account.data().len();
|
||||
if caller_account.data.len() != new_len {
|
||||
if !do_support_realloc && !caller_account.data.is_empty() {
|
||||
// Only support for `CreateAccount` at this time.
|
||||
// Need a way to limit total realloc size across multiple CPI calls
|
||||
ic_msg!(
|
||||
@ -2236,28 +2249,36 @@ fn call<'a>(
|
||||
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
|
||||
);
|
||||
}
|
||||
if account.data().len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE {
|
||||
let data_overflow = if do_support_realloc {
|
||||
new_len > caller_account.original_data_len + MAX_PERMITTED_DATA_INCREASE
|
||||
} else {
|
||||
new_len > caller_account.data.len() + MAX_PERMITTED_DATA_INCREASE
|
||||
};
|
||||
if data_overflow {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"SystemProgram::CreateAccount data size limited to {} in inner instructions",
|
||||
"Account data size realloc limited to {} in inner instructions",
|
||||
MAX_PERMITTED_DATA_INCREASE
|
||||
);
|
||||
return Err(
|
||||
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
|
||||
);
|
||||
}
|
||||
account_ref.data = translate_slice_mut::<u8>(
|
||||
if new_len < caller_account.data.len() {
|
||||
caller_account.data[new_len..].fill(0);
|
||||
}
|
||||
caller_account.data = translate_slice_mut::<u8>(
|
||||
memory_mapping,
|
||||
account_ref.vm_data_addr,
|
||||
account.data().len() as u64,
|
||||
caller_account.vm_data_addr,
|
||||
new_len as u64,
|
||||
&bpf_loader_deprecated::id(), // Don't care since it is byte aligned
|
||||
)?;
|
||||
*account_ref.ref_to_len_in_vm = account.data().len() as u64;
|
||||
*account_ref.serialized_len_ptr = account.data().len() as u64;
|
||||
*caller_account.ref_to_len_in_vm = new_len as u64;
|
||||
*caller_account.serialized_len_ptr = new_len as u64;
|
||||
}
|
||||
account_ref
|
||||
caller_account
|
||||
.data
|
||||
.copy_from_slice(&account.data()[0..account_ref.data.len()]);
|
||||
.copy_from_slice(&callee_account.data()[0..new_len]);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user