Richer runtime failure logging (#14875)

This commit is contained in:
Jack May 2021-01-28 10:04:54 -08:00 committed by GitHub
parent a53b8558cd
commit 0b1015f7d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 246 additions and 147 deletions

View File

@ -916,13 +916,13 @@ fn test_program_bpf_invoke_sanity() {
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_INSTRUCTION_DATA_TOO_LARGE, TEST_INSTRUCTION_DATA_TOO_LARGE,
TransactionError::InstructionError(0, InstructionError::ComputationalBudgetExceeded), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_INSTRUCTION_META_TOO_LARGE, TEST_INSTRUCTION_META_TOO_LARGE,
TransactionError::InstructionError(0, InstructionError::ComputationalBudgetExceeded), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
); );

View File

@ -779,17 +779,15 @@ impl Executor for BPFExecutor {
} }
} }
Err(error) => { Err(error) => {
ic_logger_msg!(logger, "Program {} BPF VM error: {}", program_id, error);
let error = match error { let error = match error {
EbpfError::UserError(BPFError::SyscallError( EbpfError::UserError(BPFError::SyscallError(
SyscallError::InstructionError(error), SyscallError::InstructionError(error),
)) => error, )) => error,
err => { err => {
ic_logger_msg!(logger, "Program failed to complete: {:?}", err); ic_logger_msg!(logger, "Program failed to complete: {}", err);
InstructionError::ProgramFailedToComplete InstructionError::ProgramFailedToComplete
} }
}; };
stable_log::program_failure(&logger, program_id, &error); stable_log::program_failure(&logger, program_id, &error);
return Err(error); return Err(error);
} }

View File

@ -23,6 +23,7 @@ use solana_sdk::{
use_loaded_program_accounts, use_loaded_program_accounts,
}, },
hash::{Hasher, HASH_BYTES}, hash::{Hasher, HASH_BYTES},
ic_msg,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, Instruction, InstructionError},
keyed_account::KeyedAccount, keyed_account::KeyedAccount,
native_loader, native_loader,
@ -59,14 +60,18 @@ pub enum SyscallError {
MalformedSignerSeed(Utf8Error, Vec<u8>), MalformedSignerSeed(Utf8Error, Vec<u8>),
#[error("Could not create program address with signer seeds: {0}")] #[error("Could not create program address with signer seeds: {0}")]
BadSeeds(PubkeyError), BadSeeds(PubkeyError),
#[error("Program id is not supported by cross-program invocations")] #[error("Program {0} supported by inner instructions")]
ProgramNotSupported, ProgramNotSupported(Pubkey),
#[error("{0}")] #[error("{0}")]
InstructionError(InstructionError), InstructionError(InstructionError),
#[error("Unaligned pointer")] #[error("Unaligned pointer")]
UnalignedPointer, UnalignedPointer,
#[error("Too many signers")] #[error("Too many signers")]
TooManySigners, TooManySigners,
#[error("Instruction passed to inner instruction is too large")]
InstructionTooLarge,
#[error("Too many accounts passed to inner instruction")]
TooManyAccounts,
} }
impl From<SyscallError> for EbpfError<BPFError> { impl From<SyscallError> for EbpfError<BPFError> {
fn from(error: SyscallError) -> Self { fn from(error: SyscallError) -> Self {
@ -898,10 +903,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
check_instruction_size( check_instruction_size(
ix.accounts.len(), ix.accounts.len(),
ix.data.len(), ix.data.len(),
self.invoke_context &self.invoke_context.borrow(),
.borrow()
.get_bpf_compute_budget()
.max_cpi_instruction_size,
)?; )?;
let accounts = translate_slice::<AccountMeta>( let accounts = translate_slice::<AccountMeta>(
@ -1186,10 +1188,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
check_instruction_size( check_instruction_size(
ix_c.accounts_len, ix_c.accounts_len,
ix_c.data_len, ix_c.data_len,
self.invoke_context &self.invoke_context.borrow(),
.borrow()
.get_bpf_compute_budget()
.max_cpi_instruction_size,
)?; )?;
let program_id = let program_id =
translate_type::<Pubkey>(memory_mapping, ix_c.program_id_addr, self.loader_id)?; translate_type::<Pubkey>(memory_mapping, ix_c.program_id_addr, self.loader_id)?;
@ -1408,12 +1407,14 @@ where
let mut accounts = Vec::with_capacity(account_keys.len()); let mut accounts = Vec::with_capacity(account_keys.len());
let mut refs = 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() { for (i, ref account_key) in account_keys.iter().enumerate() {
let account = let account = invoke_context.get_account(&account_key).ok_or_else(|| {
invoke_context ic_msg!(
.get_account(&account_key) invoke_context,
.ok_or(SyscallError::InstructionError( "Instruction references an unknown account {:?}",
InstructionError::MissingAccount, account_key
))?; );
SyscallError::InstructionError(InstructionError::MissingAccount)
})?;
if (invoke_context.is_feature_active(&use_loaded_program_accounts::id()) if (invoke_context.is_feature_active(&use_loaded_program_accounts::id())
&& i == program_account_index) && i == program_account_index)
@ -1439,6 +1440,11 @@ where
accounts.push(account); accounts.push(account);
refs.push(account_ref); refs.push(account_ref);
} else { } else {
ic_msg!(
invoke_context,
"Instruction references an unknown account {:?}",
account_key
);
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into()); return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
} }
} }
@ -1449,12 +1455,14 @@ where
fn check_instruction_size( fn check_instruction_size(
num_accounts: usize, num_accounts: usize,
data_len: usize, data_len: usize,
max_size: usize, invoke_context: &Ref<&mut dyn InvokeContext>,
) -> Result<(), EbpfError<BPFError>> { ) -> Result<(), EbpfError<BPFError>> {
if max_size < num_accounts * size_of::<AccountMeta>() + data_len { if invoke_context
return Err( .get_bpf_compute_budget()
SyscallError::InstructionError(InstructionError::ComputationalBudgetExceeded).into(), .max_cpi_instruction_size
); < num_accounts * size_of::<AccountMeta>() + data_len
{
return Err(SyscallError::InstructionTooLarge.into());
} }
Ok(()) Ok(())
} }
@ -1471,9 +1479,7 @@ fn check_account_infos(
{ {
// Cap the number of account_infos a caller can pass to approximate // Cap the number of account_infos a caller can pass to approximate
// maximum that accounts that could be passed in an instruction // maximum that accounts that could be passed in an instruction
return Err( return Err(SyscallError::TooManyAccounts.into());
SyscallError::InstructionError(InstructionError::ComputationalBudgetExceeded).into(),
);
}; };
Ok(()) Ok(())
} }
@ -1488,30 +1494,40 @@ fn check_authorized_program(
|| (bpf_loader_upgradeable::check_id(program_id) || (bpf_loader_upgradeable::check_id(program_id)
&& !bpf_loader_upgradeable::is_upgrade_instruction(instruction_data)) && !bpf_loader_upgradeable::is_upgrade_instruction(instruction_data))
{ {
return Err(SyscallError::InstructionError(InstructionError::UnsupportedProgramId).into()); return Err(SyscallError::ProgramNotSupported(*program_id).into());
} }
Ok(()) Ok(())
} }
fn get_upgradeable_executable( fn get_upgradeable_executable(
callee_program_id: &Pubkey,
program_account: &RefCell<Account>, program_account: &RefCell<Account>,
invoke_context: &Ref<&mut dyn InvokeContext>, invoke_context: &Ref<&mut dyn InvokeContext>,
) -> Result<Option<(Pubkey, RefCell<Account>)>, EbpfError<BPFError>> { ) -> Result<Option<(Pubkey, RefCell<Account>)>, EbpfError<BPFError>> {
if program_account.borrow().owner == bpf_loader_upgradeable::id() { if program_account.borrow().owner == bpf_loader_upgradeable::id() {
if let UpgradeableLoaderState::Program { match program_account.borrow().state() {
programdata_address, Ok(UpgradeableLoaderState::Program {
} = program_account programdata_address,
.borrow() }) => {
.state() if let Some(account) = invoke_context.get_account(&programdata_address) {
.map_err(SyscallError::InstructionError)? Ok(Some((programdata_address, account)))
{ } else {
if let Some(account) = invoke_context.get_account(&programdata_address) { ic_msg!(
Ok(Some((programdata_address, account))) invoke_context,
} else { "Unknown upgradeable programdata account {:?}",
Err(SyscallError::InstructionError(InstructionError::MissingAccount).into()) programdata_address,
);
Err(SyscallError::InstructionError(InstructionError::MissingAccount).into())
}
}
_ => {
ic_msg!(
invoke_context,
"Invalid upgradeable program account {:?}",
callee_program_id,
);
Err(SyscallError::InstructionError(InstructionError::InvalidAccountData).into())
} }
} else {
Err(SyscallError::InstructionError(InstructionError::MissingAccount).into())
} }
} else { } else {
Ok(None) Ok(None)
@ -1560,8 +1576,13 @@ fn call<'a>(
.iter() .iter()
.collect::<Vec<&KeyedAccount>>(); .collect::<Vec<&KeyedAccount>>();
let (message, callee_program_id, callee_program_id_index) = let (message, callee_program_id, callee_program_id_index) =
MessageProcessor::create_message(&instruction, &keyed_account_refs, &signers) MessageProcessor::create_message(
.map_err(SyscallError::InstructionError)?; &instruction,
&keyed_account_refs,
&signers,
&invoke_context,
)
.map_err(SyscallError::InstructionError)?;
let caller_privileges = message let caller_privileges = message
.account_keys .account_keys
.iter() .iter()
@ -1589,14 +1610,13 @@ fn call<'a>(
// Construct executables // Construct executables
let program_account = let program_account = (**accounts.get(callee_program_id_index).ok_or_else(|| {
(**accounts ic_msg!(invoke_context, "Unknown program {:?}", callee_program_id,);
.get(callee_program_id_index) SyscallError::InstructionError(InstructionError::MissingAccount)
.ok_or(SyscallError::InstructionError( })?)
InstructionError::MissingAccount, .clone();
))?) let programdata_executable =
.clone(); get_upgradeable_executable(&callee_program_id, &program_account, &invoke_context)?;
let programdata_executable = get_upgradeable_executable(&program_account, &invoke_context)?;
let mut executables = vec![(callee_program_id, program_account)]; let mut executables = vec![(callee_program_id, program_account)];
if let Some(executable) = programdata_executable { if let Some(executable) = programdata_executable {
executables.push(executable); executables.push(executable);
@ -1640,39 +1660,52 @@ fn call<'a>(
} }
// Copy results back to caller // Copy results back to caller
for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() { {
let account = account.borrow(); let invoke_context = syscall.get_context()?;
if let Some(account_ref) = account_ref { for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() {
if message.is_writable(i) && !account.executable { let account = account.borrow();
*account_ref.lamports = account.lamports; if let Some(account_ref) = account_ref {
*account_ref.owner = account.owner; if message.is_writable(i) && !account.executable {
if account_ref.data.len() != account.data.len() { *account_ref.lamports = account.lamports;
if !account_ref.data.is_empty() { *account_ref.owner = account.owner;
// Only support for `CreateAccount` at this time. if account_ref.data.len() != account.data.len() {
// Need a way to limit total realloc size across multiple CPI calls if !account_ref.data.is_empty() {
return Err(SyscallError::InstructionError( // Only support for `CreateAccount` at this time.
InstructionError::InvalidRealloc, // Need a way to limit total realloc size across multiple CPI calls
) ic_msg!(
.into()); invoke_context,
"Inner instructions do not support realloc, only SystemProgram::CreateAccount",
);
return Err(SyscallError::InstructionError(
InstructionError::InvalidRealloc,
)
.into());
}
if account.data.len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE
{
ic_msg!(
invoke_context,
"SystemProgram::CreateAccount data size limited to {:?} in inner instructions",
MAX_PERMITTED_DATA_INCREASE
);
return Err(SyscallError::InstructionError(
InstructionError::InvalidRealloc,
)
.into());
}
let _ = translate(
memory_mapping,
AccessType::Store,
account_ref.vm_data_addr,
account.data.len() as u64,
)?;
*account_ref.ref_to_len_in_vm = account.data.len() as u64;
*account_ref.serialized_len_ptr = account.data.len() as u64;
} }
if account.data.len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE { account_ref
return Err(SyscallError::InstructionError( .data
InstructionError::InvalidRealloc, .clone_from_slice(&account.data[0..account_ref.data.len()]);
)
.into());
}
let _ = translate(
memory_mapping,
AccessType::Store,
account_ref.vm_data_addr,
account.data.len() as u64,
)?;
*account_ref.ref_to_len_in_vm = account.data.len() as u64;
*account_ref.serialized_len_ptr = account.data.len() as u64;
} }
account_ref
.data
.clone_from_slice(&account.data[0..account_ref.data.len()]);
} }
} }
} }

View File

@ -9,6 +9,7 @@ use solana_sdk::{
account_utils::StateMut, account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState}, bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{instructions_sysvar_enabled, track_writable_deescalation, FeatureSet}, feature_set::{instructions_sysvar_enabled, track_writable_deescalation, FeatureSet},
ic_msg,
instruction::{CompiledInstruction, Instruction, InstructionError}, instruction::{CompiledInstruction, Instruction, InstructionError},
keyed_account::{create_keyed_readonly_accounts, KeyedAccount}, keyed_account::{create_keyed_readonly_accounts, KeyedAccount},
message::Message, message::Message,
@ -22,7 +23,12 @@ use solana_sdk::{
system_program, system_program,
transaction::TransactionError, transaction::TransactionError,
}; };
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use std::{
cell::{Ref, RefCell},
collections::HashMap,
rc::Rc,
sync::Arc,
};
pub struct Executors { pub struct Executors {
pub executors: HashMap<Pubkey, Arc<dyn Executor>>, pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
@ -525,6 +531,7 @@ impl MessageProcessor {
instruction: &Instruction, instruction: &Instruction,
keyed_accounts: &[&KeyedAccount], keyed_accounts: &[&KeyedAccount],
signers: &[Pubkey], signers: &[Pubkey],
invoke_context: &Ref<&mut dyn InvokeContext>,
) -> Result<(Message, Pubkey, usize), InstructionError> { ) -> Result<(Message, Pubkey, usize), InstructionError> {
// Check for privilege escalation // Check for privilege escalation
for account in instruction.accounts.iter() { for account in instruction.accounts.iter() {
@ -537,9 +544,21 @@ impl MessageProcessor {
None None
} }
}) })
.ok_or(InstructionError::MissingAccount)?; .ok_or_else(|| {
ic_msg!(
invoke_context,
"Instruction references an unknown account {:?}",
account.pubkey
);
InstructionError::MissingAccount
})?;
// Readonly account cannot become writable // Readonly account cannot become writable
if account.is_writable && !keyed_account.is_writable() { if account.is_writable && !keyed_account.is_writable() {
ic_msg!(
invoke_context,
"{:?}'s writable priviledge escalated ",
account.pubkey
);
return Err(InstructionError::PrivilegeEscalation); return Err(InstructionError::PrivilegeEscalation);
} }
@ -548,6 +567,11 @@ impl MessageProcessor {
keyed_account.signer_key().is_some() // Signed in the parent instruction keyed_account.signer_key().is_some() // Signed in the parent instruction
|| signers.contains(&account.pubkey) // Signed by the program || signers.contains(&account.pubkey) // Signed by the program
) { ) {
ic_msg!(
invoke_context,
"{:?}'s signer priviledge escalated ",
account.pubkey
);
return Err(InstructionError::PrivilegeEscalation); return Err(InstructionError::PrivilegeEscalation);
} }
} }
@ -563,7 +587,10 @@ impl MessageProcessor {
return Err(InstructionError::AccountNotExecutable); return Err(InstructionError::AccountNotExecutable);
} }
} }
None => return Err(InstructionError::MissingAccount), None => {
ic_msg!(invoke_context, "Unknown program {:?}", program_id);
return Err(InstructionError::MissingAccount);
}
} }
let message = Message::new(&[instruction.clone()], None); let message = Message::new(&[instruction.clone()], None);
@ -579,87 +606,128 @@ impl MessageProcessor {
keyed_accounts: &[&KeyedAccount], keyed_accounts: &[&KeyedAccount],
signers_seeds: &[&[&[u8]]], signers_seeds: &[&[&[u8]]],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let caller_program_id = invoke_context.get_caller()?; let invoke_context = RefCell::new(invoke_context);
// Translate and verify caller's data let (message, executables, accounts, account_refs, caller_privileges) = {
let invoke_context = invoke_context.borrow();
let signers = signers_seeds let caller_program_id = invoke_context.get_caller()?;
.iter()
.map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id)) // Translate and verify caller's data
.collect::<Result<Vec<_>, solana_sdk::pubkey::PubkeyError>>()?;
let mut caller_privileges = keyed_accounts let signers = signers_seeds
.iter() .iter()
.map(|keyed_account| keyed_account.is_writable()) .map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id))
.collect::<Vec<bool>>(); .collect::<Result<Vec<_>, solana_sdk::pubkey::PubkeyError>>()?;
caller_privileges.insert(0, false); let mut caller_privileges = keyed_accounts
let (message, callee_program_id, _) = .iter()
Self::create_message(&instruction, &keyed_accounts, &signers)?; .map(|keyed_account| keyed_account.is_writable())
let mut accounts = vec![]; .collect::<Vec<bool>>();
let mut account_refs = vec![]; caller_privileges.insert(0, false);
'root: for account_key in message.account_keys.iter() { let (message, callee_program_id, _) =
for keyed_account in keyed_accounts { Self::create_message(&instruction, &keyed_accounts, &signers, &invoke_context)?;
if account_key == keyed_account.unsigned_key() { let mut accounts = vec![];
accounts.push(Rc::new(keyed_account.account.clone())); let mut account_refs = vec![];
account_refs.push(keyed_account); 'root: for account_key in message.account_keys.iter() {
continue 'root; for keyed_account in keyed_accounts {
if account_key == keyed_account.unsigned_key() {
accounts.push(Rc::new(keyed_account.account.clone()));
account_refs.push(keyed_account);
continue 'root;
}
} }
ic_msg!(
invoke_context,
"Instruction references an unknown account {:?}",
account_key
);
return Err(InstructionError::MissingAccount);
} }
return Err(InstructionError::MissingAccount);
}
// Process instruction // Process instruction
invoke_context.record_instruction(&instruction); invoke_context.record_instruction(&instruction);
let program_account = invoke_context let program_account =
.get_account(&callee_program_id) invoke_context
.ok_or(InstructionError::MissingAccount)?; .get_account(&callee_program_id)
if !program_account.borrow().executable { .ok_or_else(|| {
return Err(InstructionError::AccountNotExecutable); ic_msg!(invoke_context, "Unknown program {:?}", callee_program_id);
} InstructionError::MissingAccount
let programdata_executable = })?;
if program_account.borrow().owner == bpf_loader_upgradeable::id() { if !program_account.borrow().executable {
if let UpgradeableLoaderState::Program { return Err(InstructionError::AccountNotExecutable);
programdata_address, }
} = program_account.borrow().state()? let programdata_executable =
{ if program_account.borrow().owner == bpf_loader_upgradeable::id() {
if let Some(account) = invoke_context.get_account(&programdata_address) { if let UpgradeableLoaderState::Program {
Some((programdata_address, account)) programdata_address,
} = program_account.borrow().state()?
{
if let Some(account) = invoke_context.get_account(&programdata_address) {
Some((programdata_address, account))
} else {
ic_msg!(
invoke_context,
"Unknown upgradeable programdata account {:?}",
programdata_address,
);
return Err(InstructionError::MissingAccount);
}
} else { } else {
ic_msg!(
invoke_context,
"Upgradeable program account state not valid {:?}",
callee_program_id,
);
return Err(InstructionError::MissingAccount); return Err(InstructionError::MissingAccount);
} }
} else { } else {
return Err(InstructionError::MissingAccount); None
} };
} else { let mut executables = vec![(callee_program_id, program_account)];
None if let Some(programdata) = programdata_executable {
}; executables.push(programdata);
let mut executable_accounts = vec![(callee_program_id, program_account)]; }
if let Some(programdata) = programdata_executable { (
executable_accounts.push(programdata); message,
} executables,
accounts,
account_refs,
caller_privileges,
)
};
#[allow(clippy::deref_addrof)]
MessageProcessor::process_cross_program_instruction( MessageProcessor::process_cross_program_instruction(
&message, &message,
&executable_accounts, &executables,
&accounts, &accounts,
&caller_privileges, &caller_privileges,
invoke_context, *(&mut *(invoke_context.borrow_mut())),
)?; )?;
// Copy results back to caller // Copy results back to caller
for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() { {
let account = account.borrow(); let invoke_context = invoke_context.borrow();
if message.is_writable(i) && !account.executable { for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() {
account_ref.try_account_ref_mut()?.lamports = account.lamports; let account = account.borrow();
account_ref.try_account_ref_mut()?.owner = account.owner; if message.is_writable(i) && !account.executable {
if account_ref.data_len()? != account.data.len() && account_ref.data_len()? != 0 { account_ref.try_account_ref_mut()?.lamports = account.lamports;
// Only support for `CreateAccount` at this time. account_ref.try_account_ref_mut()?.owner = account.owner;
// Need a way to limit total realloc size across multiple CPI calls if account_ref.data_len()? != account.data.len() && account_ref.data_len()? != 0
return Err(InstructionError::InvalidRealloc); {
// Only support for `CreateAccount` at this time.
// Need a way to limit total realloc size across multiple CPI calls
ic_msg!(
invoke_context,
"Inner instructions do not support realloc, only SystemProgram::CreateAccount",
);
return Err(InstructionError::InvalidRealloc);
}
account_ref.try_account_ref_mut()?.data = account.data.clone();
} }
account_ref.try_account_ref_mut()?.data = account.data.clone();
} }
} }