Fix duplicate account translation in CPI (#23701)

* Use visit_each_account_once() in CPI syscall to prevent duplicate accounts from being translated twice.

* remove unwrap() from runtime

Co-authored-by: Jack May <jack@solana.com>
This commit is contained in:
Alexander Meißner
2022-03-16 23:45:01 +01:00
committed by GitHub
parent be0aeea01a
commit dda3a463a2
2 changed files with 99 additions and 76 deletions

View File

@ -352,7 +352,11 @@ impl<'a> InvokeContext<'a> {
} }
Err(InstructionError::MissingAccount) Err(InstructionError::MissingAccount)
}; };
visit_each_account_once(instruction_accounts, &mut work)?; visit_each_account_once(
instruction_accounts,
&mut work,
InstructionError::NotEnoughAccountKeys,
)?;
} else { } else {
let contains = (0..self let contains = (0..self
.transaction_context .transaction_context
@ -517,7 +521,11 @@ impl<'a> InvokeContext<'a> {
Ok(()) Ok(())
}; };
visit_each_account_once(instruction_accounts, &mut work)?; visit_each_account_once(
instruction_accounts,
&mut work,
InstructionError::NotEnoughAccountKeys,
)?;
// Verify that the total sum of all the lamports did not change // Verify that the total sum of all the lamports did not change
if pre_sum != post_sum { if pre_sum != post_sum {
@ -615,7 +623,11 @@ impl<'a> InvokeContext<'a> {
} }
Err(InstructionError::MissingAccount) Err(InstructionError::MissingAccount)
}; };
visit_each_account_once(instruction_accounts, &mut work)?; visit_each_account_once(
instruction_accounts,
&mut work,
InstructionError::NotEnoughAccountKeys,
)?;
// Verify that the total sum of all the lamports did not change // Verify that the total sum of all the lamports did not change
if pre_sum != post_sum { if pre_sum != post_sum {
@ -1154,23 +1166,25 @@ pub fn mock_process_instruction(
} }
/// Visit each unique instruction account index once /// Visit each unique instruction account index once
fn visit_each_account_once( pub fn visit_each_account_once<E>(
instruction_accounts: &[InstructionAccount], instruction_accounts: &[InstructionAccount],
work: &mut dyn FnMut(usize, &InstructionAccount) -> Result<(), InstructionError>, work: &mut dyn FnMut(usize, &InstructionAccount) -> Result<(), E>,
) -> Result<(), InstructionError> { inner_error: E,
) -> Result<(), E> {
// Note: This is an O(n^2) algorithm,
// but performed on a very small slice and requires no heap allocations
'root: for (index, instruction_account) in instruction_accounts.iter().enumerate() { 'root: for (index, instruction_account) in instruction_accounts.iter().enumerate() {
// Note: This is an O(n^2) algorithm, match instruction_accounts.get(..index) {
// but performed on a very small slice and requires no heap allocations Some(range) => {
for before in instruction_accounts for before in range.iter() {
.get(..index) if before.index_in_transaction == instruction_account.index_in_transaction {
.ok_or(InstructionError::NotEnoughAccountKeys)? continue 'root; // skip dups
.iter() }
{ }
if before.index_in_transaction == instruction_account.index_in_transaction { work(index, instruction_account)?;
continue 'root; // skip dups
} }
None => return Err(inner_error),
} }
work(index, instruction_account)?;
} }
Ok(()) Ok(())
} }
@ -1211,7 +1225,8 @@ mod tests {
index_sum_b += entry.index_in_transaction; index_sum_b += entry.index_in_transaction;
Ok(()) Ok(())
}; };
visit_each_account_once(accounts, &mut work).unwrap(); visit_each_account_once(accounts, &mut work, InstructionError::NotEnoughAccountKeys)
.unwrap();
(unique_entries, index_sum_a, index_sum_b) (unique_entries, index_sum_a, index_sum_b)
}; };

View File

@ -4,7 +4,7 @@ use {
alloc::Alloc, alloc::Alloc,
solana_program_runtime::{ solana_program_runtime::{
ic_logger_msg, ic_msg, ic_logger_msg, ic_msg,
invoke_context::{ComputeMeter, InvokeContext}, invoke_context::{visit_each_account_once, ComputeMeter, InvokeContext},
stable_log, stable_log,
timings::ExecuteTimings, timings::ExecuteTimings,
}, },
@ -2574,68 +2574,76 @@ where
))?; ))?;
accounts.push((*program_account_index, None)); accounts.push((*program_account_index, None));
for instruction_account in instruction_accounts.iter() { visit_each_account_once::<EbpfError<BpfError>>(
let account = invoke_context instruction_accounts,
.transaction_context &mut |_index: usize, instruction_account: &InstructionAccount| {
.get_account_at_index(instruction_account.index_in_transaction) let account = invoke_context
.map_err(SyscallError::InstructionError)?; .transaction_context
let account_key = invoke_context .get_account_at_index(instruction_account.index_in_transaction)
.transaction_context .map_err(SyscallError::InstructionError)?;
.get_key_of_account_at_index(instruction_account.index_in_transaction) let account_key = invoke_context
.map_err(SyscallError::InstructionError)?; .transaction_context
if account.borrow().executable() { .get_key_of_account_at_index(instruction_account.index_in_transaction)
// Use the known account .map_err(SyscallError::InstructionError)?;
accounts.push((instruction_account.index_in_transaction, None)); if account.borrow().executable() {
} else if let Some(caller_account_index) = // Use the known account
account_info_keys.iter().position(|key| *key == account_key) accounts.push((instruction_account.index_in_transaction, None));
{ } else if let Some(caller_account_index) =
let mut caller_account = do_translate( account_info_keys.iter().position(|key| *key == account_key)
account_infos
.get(caller_account_index)
.ok_or(SyscallError::InvalidLength)?,
invoke_context,
)?;
{ {
let mut account = account.borrow_mut(); let mut caller_account = do_translate(
account.copy_into_owner_from_slice(caller_account.owner.as_ref()); account_infos
account.set_data_from_slice(caller_account.data); .get(caller_account_index)
account.set_lamports(*caller_account.lamports); .ok_or(SyscallError::InvalidLength)?,
account.set_executable(caller_account.executable); invoke_context,
account.set_rent_epoch(caller_account.rent_epoch); )?;
} {
let caller_account = if instruction_account.is_writable { let mut account = account.borrow_mut();
let orig_data_len_index = instruction_account account.copy_into_owner_from_slice(caller_account.owner.as_ref());
.index_in_caller account.set_data_from_slice(caller_account.data);
.saturating_sub(instruction_context.get_number_of_program_accounts()); account.set_lamports(*caller_account.lamports);
if orig_data_len_index < orig_data_lens.len() { account.set_executable(caller_account.executable);
caller_account.original_data_len = *orig_data_lens account.set_rent_epoch(caller_account.rent_epoch);
.get(orig_data_len_index)
.ok_or(SyscallError::InvalidLength)?;
} else {
ic_msg!(
invoke_context,
"Internal error: index mismatch for account {}",
account_key
);
return Err(
SyscallError::InstructionError(InstructionError::MissingAccount).into(),
);
} }
let caller_account = if instruction_account.is_writable {
let orig_data_len_index = instruction_account
.index_in_caller
.saturating_sub(instruction_context.get_number_of_program_accounts());
if orig_data_len_index < orig_data_lens.len() {
caller_account.original_data_len = *orig_data_lens
.get(orig_data_len_index)
.ok_or(SyscallError::InvalidLength)?;
} else {
ic_msg!(
invoke_context,
"Internal error: index mismatch for account {}",
account_key
);
return Err(SyscallError::InstructionError(
InstructionError::MissingAccount,
)
.into());
}
Some(caller_account) Some(caller_account)
} else {
None
};
accounts.push((instruction_account.index_in_transaction, caller_account));
} else { } else {
None ic_msg!(
}; invoke_context,
accounts.push((instruction_account.index_in_transaction, caller_account)); "Instruction references an unknown account {}",
} else { account_key
ic_msg!( );
invoke_context, return Err(
"Instruction references an unknown account {}", SyscallError::InstructionError(InstructionError::MissingAccount).into(),
account_key );
); }
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into()); Ok(())
} },
} SyscallError::InstructionError(InstructionError::NotEnoughAccountKeys).into(),
)?;
Ok(accounts) Ok(accounts)
} }