Load executable accounts from invoke context (#14574)

This commit is contained in:
Jack May
2021-01-14 00:19:22 -08:00
committed by GitHub
parent cfcca1cd3c
commit 6e8a1ba7de
5 changed files with 378 additions and 272 deletions

View File

@ -127,9 +127,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts,
params.ka_num, signers_seeds,
SOL_ARRAY_SIZE(signers_seeds)));
break;
}
@ -189,8 +189,7 @@ extern uint64_t entrypoint(const uint8_t *input) {
data, SOL_ARRAY_SIZE(data)};
sol_log("Invoke again");
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_assert(SUCCESS == sol_invoke(&instruction, accounts, params.ka_num));
} else {
sol_log("Last invoked");
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {

View File

@ -804,7 +804,7 @@ fn test_program_bpf_invoke() {
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![argument_keypair.pubkey().clone()]);
assert_eq!(invoked_programs, vec![]);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::AccountNotExecutable)

View File

@ -19,7 +19,8 @@ use solana_sdk::{
feature_set::{
abort_on_all_cpi_failures, limit_cpi_loader_invoke, pubkey_log_syscall_enabled,
ristretto_mul_syscall_enabled, sha256_syscall_enabled, sol_log_compute_units_syscall,
try_find_program_address_syscall_enabled, use_loaded_program_accounts,
try_find_program_address_syscall_enabled, use_loaded_executables,
use_loaded_program_accounts,
},
hash::{Hasher, HASH_BYTES},
instruction::{AccountMeta, Instruction, InstructionError},
@ -31,7 +32,7 @@ use solana_sdk::{
};
use std::{
alloc::Layout,
cell::{RefCell, RefMut},
cell::{Ref, RefCell, RefMut},
convert::TryFrom,
mem::{align_of, size_of},
rc::Rc,
@ -834,6 +835,7 @@ struct AccountReferences<'a> {
ref_to_len_in_vm: &'a mut u64,
serialized_len_ptr: &'a mut u64,
}
type TranslatedAccount<'a> = (Rc<RefCell<Account>>, Option<AccountReferences<'a>>);
type TranslatedAccounts<'a> = (
Vec<Rc<RefCell<Account>>>,
Vec<Option<AccountReferences<'a>>>,
@ -842,16 +844,15 @@ type TranslatedAccounts<'a> = (
/// Implemented by language specific data structure translators
trait SyscallInvokeSigned<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn get_context(&self) -> Result<Ref<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>];
fn translate_instruction(
&self,
addr: u64,
max_size: usize,
memory_mapping: &MemoryMapping,
) -> Result<Instruction, EbpfError<BPFError>>;
fn translate_accounts(
&self,
skip_program: bool,
account_keys: &[Pubkey],
program_account_index: usize,
account_infos_addr: u64,
@ -879,17 +880,29 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_context(&self) -> Result<Ref<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
self.callers_keyed_accounts
}
fn translate_instruction(
&self,
addr: u64,
max_size: usize,
memory_mapping: &MemoryMapping,
) -> Result<Instruction, EbpfError<BPFError>> {
let ix = translate_type::<Instruction>(memory_mapping, addr, self.loader_id)?;
check_instruction_size(ix.accounts.len(), ix.data.len(), max_size)?;
check_instruction_size(
ix.accounts.len(),
ix.data.len(),
self.invoke_context
.borrow()
.get_bpf_compute_budget()
.max_cpi_instruction_size,
)?;
let accounts = translate_slice::<AccountMeta>(
memory_mapping,
@ -914,38 +927,35 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
fn translate_accounts(
&self,
skip_program: bool,
account_keys: &[Pubkey],
program_account_index: usize,
account_infos_addr: u64,
account_infos_len: u64,
memory_mapping: &MemoryMapping,
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = if account_infos_len > 0 {
translate_slice::<AccountInfo>(
let invoke_context = self.invoke_context.borrow();
let account_infos = translate_slice::<AccountInfo>(
memory_mapping,
account_infos_addr,
account_infos_len,
self.loader_id,
)?
} else {
&[]
};
let mut accounts = Vec::with_capacity(account_keys.len());
let mut refs = Vec::with_capacity(account_keys.len());
'root: for (i, account_key) in account_keys.iter().enumerate() {
if skip_program && i == program_account_index {
// Don't look for caller passed executable, runtime already has it
continue 'root;
}
for account_info in account_infos.iter() {
let key = translate_type::<Pubkey>(
)?;
check_account_infos(account_infos.len(), &invoke_context)?;
let account_info_keys = account_infos
.iter()
.map(|account_info| {
translate_type::<Pubkey>(
memory_mapping,
account_info.key as *const _ as u64,
self.loader_id,
)?;
if account_key == key {
)
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
let translate = |account_info: &AccountInfo| {
// Translate the account from user space
let lamports = {
// Double translate lamports out of RefCell
let ptr = translate_type::<u64>(
@ -994,28 +1004,33 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
)
};
accounts.push(Rc::new(RefCell::new(Account {
Ok((
Rc::new(RefCell::new(Account {
lamports: *lamports,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
})));
refs.push(Some(AccountReferences {
})),
Some(AccountReferences {
lamports,
owner,
data,
vm_data_addr,
ref_to_len_in_vm,
serialized_len_ptr,
}));
continue 'root;
}
}
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
}
}),
))
};
Ok((accounts, refs))
get_translated_accounts(
account_keys,
program_account_index,
&account_info_keys,
account_infos,
&invoke_context,
translate,
)
}
fn translate_signers(
@ -1151,6 +1166,11 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_context(&self) -> Result<Ref<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
self.callers_keyed_accounts
@ -1159,11 +1179,18 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
fn translate_instruction(
&self,
addr: u64,
max_size: usize,
memory_mapping: &MemoryMapping,
) -> Result<Instruction, EbpfError<BPFError>> {
let ix_c = translate_type::<SolInstruction>(memory_mapping, addr, self.loader_id)?;
check_instruction_size(ix_c.accounts_len, ix_c.data_len, max_size)?;
check_instruction_size(
ix_c.accounts_len,
ix_c.data_len,
self.invoke_context
.borrow()
.get_bpf_compute_budget()
.max_cpi_instruction_size,
)?;
let program_id =
translate_type::<Pubkey>(memory_mapping, ix_c.program_id_addr, self.loader_id)?;
let meta_cs = translate_slice::<SolAccountMeta>(
@ -1201,33 +1228,31 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
fn translate_accounts(
&self,
skip_program: bool,
account_keys: &[Pubkey],
program_account_index: usize,
account_infos_addr: u64,
account_infos_len: u64,
memory_mapping: &MemoryMapping,
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let invoke_context = self.invoke_context.borrow();
let account_infos = translate_slice::<SolAccountInfo>(
memory_mapping,
account_infos_addr,
account_infos_len,
self.loader_id,
)?;
let mut accounts = Vec::with_capacity(account_keys.len());
let mut refs = Vec::with_capacity(account_keys.len());
'root: for (i, account_key) in account_keys.iter().enumerate() {
if skip_program && i == program_account_index {
// Don't look for caller passed executable, runtime already has it
continue 'root;
}
for account_info in account_infos.iter() {
let key = translate_type::<Pubkey>(
memory_mapping,
account_info.key_addr,
self.loader_id,
)?;
if account_key == key {
check_account_infos(account_infos.len(), &invoke_context)?;
let account_info_keys = account_infos
.iter()
.map(|account_info| {
translate_type::<Pubkey>(memory_mapping, account_info.key_addr, self.loader_id)
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
let translate = |account_info: &SolAccountInfo| {
// Translate the account from user space
let lamports = translate_type_mut::<u64>(
memory_mapping,
account_info.lamports_addr,
@ -1265,28 +1290,33 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
self.loader_id,
)?;
accounts.push(Rc::new(RefCell::new(Account {
Ok((
Rc::new(RefCell::new(Account {
lamports: *lamports,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
})));
refs.push(Some(AccountReferences {
})),
Some(AccountReferences {
lamports,
owner,
data,
vm_data_addr,
ref_to_len_in_vm,
serialized_len_ptr,
}));
continue 'root;
}
}
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
}
}),
))
};
Ok((accounts, refs))
get_translated_accounts(
account_keys,
program_account_index,
&account_info_keys,
account_infos,
&invoke_context,
translate,
)
}
fn translate_signers(
@ -1364,6 +1394,58 @@ impl<'a> SyscallObject<BPFError> for SyscallInvokeSignedC<'a> {
}
}
fn get_translated_accounts<'a, T, F>(
account_keys: &[Pubkey],
program_account_index: usize,
account_info_keys: &[&Pubkey],
account_infos: &[T],
invoke_context: &Ref<&mut dyn InvokeContext>,
do_translate: F,
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>>
where
F: Fn(&T) -> Result<TranslatedAccount<'a>, EbpfError<BPFError>>,
{
let mut accounts = Vec::with_capacity(account_keys.len());
let mut refs = Vec::with_capacity(account_keys.len());
for (i, ref account_key) in account_keys.iter().enumerate() {
let account =
invoke_context
.get_account(&account_key)
.ok_or(SyscallError::InstructionError(
InstructionError::MissingAccount,
))?;
if (invoke_context.is_feature_active(&use_loaded_program_accounts::id())
&& i == program_account_index)
|| (invoke_context.is_feature_active(&use_loaded_executables::id())
&& account.borrow().executable)
{
// Use the known executable
accounts.push(Rc::new(account));
refs.push(None);
} else if let Some(account_info) =
account_info_keys
.iter()
.zip(account_infos)
.find_map(|(key, account_info)| {
if key == account_key {
Some(account_info)
} else {
None
}
})
{
let (account, account_ref) = do_translate(account_info)?;
accounts.push(account);
refs.push(account_ref);
} else {
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
}
}
Ok((accounts, refs))
}
fn check_instruction_size(
num_accounts: usize,
data_len: usize,
@ -1377,6 +1459,25 @@ fn check_instruction_size(
Ok(())
}
fn check_account_infos(
len: usize,
invoke_context: &Ref<&mut dyn InvokeContext>,
) -> Result<(), EbpfError<BPFError>> {
if len * size_of::<Pubkey>()
> invoke_context
.get_bpf_compute_budget()
.max_cpi_instruction_size
&& invoke_context.is_feature_active(&use_loaded_program_accounts::id())
{
// Cap the number of account_infos a caller can pass to approximate
// maximum that accounts that could be passed in an instruction
return Err(
SyscallError::InstructionError(InstructionError::ComputationalBudgetExceeded).into(),
);
};
Ok(())
}
fn check_authorized_program(
program_id: &Pubkey,
instruction_data: &[u8],
@ -1392,6 +1493,31 @@ fn check_authorized_program(
Ok(())
}
fn get_upgradeable_executable(
program_account: &RefCell<Account>,
invoke_context: &Ref<&mut dyn InvokeContext>,
) -> Result<Option<(Pubkey, RefCell<Account>)>, EbpfError<BPFError>> {
if program_account.borrow().owner == bpf_loader_upgradeable::id() {
if let UpgradeableLoaderState::Program {
programdata_address,
} = program_account
.borrow()
.state()
.map_err(SyscallError::InstructionError)?
{
if let Some(account) = invoke_context.get_account(&programdata_address) {
Ok(Some((programdata_address, account)))
} else {
Err(SyscallError::InstructionError(InstructionError::MissingAccount).into())
}
} else {
Err(SyscallError::InstructionError(InstructionError::MissingAccount).into())
}
} else {
Ok(None)
}
}
/// Call process instruction, common to both Rust and C
fn call<'a>(
syscall: &mut dyn SyscallInvokeSigned<'a>,
@ -1402,26 +1528,20 @@ fn call<'a>(
signers_seeds_len: u64,
memory_mapping: &MemoryMapping,
) -> Result<u64, EbpfError<BPFError>> {
let mut invoke_context = syscall.get_context_mut()?;
let (message, executables, accounts, account_refs, abort_on_all_cpi_failures) = {
let invoke_context = syscall.get_context()?;
invoke_context
.get_compute_meter()
.consume(invoke_context.get_bpf_compute_budget().invoke_units)?;
let use_loaded_program_accounts =
invoke_context.is_feature_active(&use_loaded_program_accounts::id());
// Translate and verify caller's data
let instruction = syscall.translate_instruction(
instruction_addr,
invoke_context
.get_bpf_compute_budget()
.max_cpi_instruction_size,
&memory_mapping,
)?;
let caller_program_id = invoke_context
.get_caller()
.map_err(SyscallError::InstructionError)?;
// Translate and verify caller's data
let instruction = syscall.translate_instruction(instruction_addr, &memory_mapping)?;
let signers = syscall.translate_signers(
caller_program_id,
signers_seeds_addr,
@ -1438,8 +1558,7 @@ fn call<'a>(
if invoke_context.is_feature_active(&limit_cpi_loader_invoke::id()) {
check_authorized_program(&callee_program_id, &instruction.data)?;
}
let (mut accounts, mut account_refs) = syscall.translate_accounts(
use_loaded_program_accounts,
let (accounts, account_refs) = syscall.translate_accounts(
&message.account_keys,
callee_program_id_index,
account_infos_addr,
@ -1447,64 +1566,46 @@ fn call<'a>(
memory_mapping,
)?;
// Process instruction
// Construct executables
invoke_context.record_instruction(&instruction);
let program_account = if use_loaded_program_accounts {
let program_account = invoke_context.get_account(&callee_program_id).ok_or(
SyscallError::InstructionError(InstructionError::MissingAccount),
)?;
accounts.insert(callee_program_id_index, Rc::new(program_account.clone()));
account_refs.insert(callee_program_id_index, None);
program_account
} else {
let program_account =
(**accounts
.get(callee_program_id_index)
.ok_or(SyscallError::InstructionError(
InstructionError::MissingAccount,
))?)
.clone()
.clone();
let programdata_executable = get_upgradeable_executable(&program_account, &invoke_context)?;
let mut executables = vec![(callee_program_id, program_account)];
if let Some(executable) = programdata_executable {
executables.push(executable);
}
// Record the instruction
invoke_context.record_instruction(&instruction);
(
message,
executables,
accounts,
account_refs,
invoke_context.is_feature_active(&abort_on_all_cpi_failures::id()),
)
};
if !program_account.borrow().executable {
return Err(SyscallError::InstructionError(InstructionError::AccountNotExecutable).into());
}
let programdata_executable = if program_account.borrow().owner == bpf_loader_upgradeable::id() {
if let UpgradeableLoaderState::Program {
programdata_address,
} = program_account
.borrow()
.state()
.map_err(SyscallError::InstructionError)?
{
if let Some(account) = invoke_context.get_account(&programdata_address) {
Some((programdata_address, account))
} else {
return Err(
SyscallError::InstructionError(InstructionError::MissingAccount).into(),
);
}
} else {
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
}
} else {
None
};
let mut executable_accounts = vec![(callee_program_id, program_account)];
if let Some(programdata) = programdata_executable {
executable_accounts.push(programdata);
}
// Process instruction
#[allow(clippy::deref_addrof)]
match MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&executables,
&accounts,
*(&mut *invoke_context),
*(&mut *(syscall.get_context_mut()?)),
) {
Ok(()) => (),
Err(err) => {
if invoke_context.is_feature_active(&abort_on_all_cpi_failures::id()) {
if abort_on_all_cpi_failures {
return Err(SyscallError::InstructionError(err).into());
} else {
match ProgramError::try_from(err) {
@ -2118,7 +2219,7 @@ mod tests {
addr: 8192,
len: bytes2.len(),
};
let bytes_to_hash = [mock_slice1, mock_slice2]; // TODO
let bytes_to_hash = [mock_slice1, mock_slice2];
let hash_result = [0; HASH_BYTES];
let ro_len = bytes_to_hash.len() as u64;
let ro_va = 96;

View File

@ -541,18 +541,19 @@ impl MessageProcessor {
}
}
// validate the caller has access to the program account
// validate the caller has access to the program account and that it is executable
let program_id = instruction.program_id;
let _ = keyed_accounts
match keyed_accounts
.iter()
.find_map(|keyed_account| {
if &program_id == keyed_account.unsigned_key() {
Some(keyed_account)
} else {
None
.find(|keyed_account| &program_id == keyed_account.unsigned_key())
{
Some(keyed_account) => {
if !keyed_account.executable()? {
return Err(InstructionError::AccountNotExecutable);
}
}
None => return Err(InstructionError::MissingAccount),
}
})
.ok_or(InstructionError::MissingAccount)?;
let message = Message::new(&[instruction.clone()], None);
let program_id_index = message.instructions[0].program_id_index as usize;

View File

@ -134,6 +134,10 @@ pub mod abort_on_all_cpi_failures {
solana_sdk::declare_id!("ED5D5a2hQaECHaMmKpnU48GdsfafdCjkb3pgAw5RKbb2");
}
pub mod use_loaded_executables {
solana_sdk::declare_id!("2SLL2KLakB83YAnF1TwFb1hpycrWeHAfHYyLhwk2JRGn");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -169,6 +173,7 @@ lazy_static! {
(limit_cpi_loader_invoke::id(), "Loader not authorized via CPI"),
(use_loaded_program_accounts::id(), "Use loaded program accounts"),
(abort_on_all_cpi_failures::id(), "Abort on all CPI failures"),
(use_loaded_executables::id(), "Use loaded executable accounts"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()