Don't use caller passed executable account (#14276)
This commit is contained in:
@ -291,7 +291,8 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
|||||||
case TEST_EMPTY_ACCOUNTS_SLICE: {
|
case TEST_EMPTY_ACCOUNTS_SLICE: {
|
||||||
sol_log("Empty accounts slice");
|
sol_log("Empty accounts slice");
|
||||||
|
|
||||||
SolAccountMeta arguments[] = {};
|
SolAccountMeta arguments[] = {
|
||||||
|
{accounts[INVOKED_ARGUMENT_INDEX].key, false, false}};
|
||||||
uint8_t data[] = {};
|
uint8_t data[] = {};
|
||||||
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
|
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
|
||||||
arguments, SOL_ARRAY_SIZE(arguments),
|
arguments, SOL_ARRAY_SIZE(arguments),
|
||||||
|
@ -385,7 +385,11 @@ fn process_instruction(
|
|||||||
}
|
}
|
||||||
TEST_EMPTY_ACCOUNTS_SLICE => {
|
TEST_EMPTY_ACCOUNTS_SLICE => {
|
||||||
msg!("Empty accounts slice");
|
msg!("Empty accounts slice");
|
||||||
let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![]);
|
let instruction = create_instruction(
|
||||||
|
*accounts[INVOKED_PROGRAM_INDEX].key,
|
||||||
|
&[(accounts[INVOKED_ARGUMENT_INDEX].key, false, false)],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
invoke(&instruction, &[])?;
|
invoke(&instruction, &[])?;
|
||||||
}
|
}
|
||||||
TEST_CAP_SEEDS => {
|
TEST_CAP_SEEDS => {
|
||||||
|
@ -989,7 +989,7 @@ fn test_program_bpf_invoke() {
|
|||||||
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().unwrap(),
|
result.unwrap_err().unwrap(),
|
||||||
TransactionError::InstructionError(0, InstructionError::ModifiedProgramId)
|
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
|
||||||
);
|
);
|
||||||
assert_eq!(10, bank.get_balance(&from_pubkey));
|
assert_eq!(10, bank.get_balance(&from_pubkey));
|
||||||
assert_eq!(0, bank.get_balance(&to_pubkey));
|
assert_eq!(0, bank.get_balance(&to_pubkey));
|
||||||
|
@ -23,7 +23,6 @@ use solana_sdk::{
|
|||||||
hash::{Hasher, HASH_BYTES},
|
hash::{Hasher, HASH_BYTES},
|
||||||
instruction::{AccountMeta, Instruction, InstructionError},
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
keyed_account::KeyedAccount,
|
keyed_account::KeyedAccount,
|
||||||
message::Message,
|
|
||||||
native_loader,
|
native_loader,
|
||||||
process_instruction::{stable_log, ComputeMeter, InvokeContext, Logger},
|
process_instruction::{stable_log, ComputeMeter, InvokeContext, Logger},
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
@ -834,7 +833,10 @@ struct AccountReferences<'a> {
|
|||||||
ref_to_len_in_vm: &'a mut u64,
|
ref_to_len_in_vm: &'a mut u64,
|
||||||
serialized_len_ptr: &'a mut u64,
|
serialized_len_ptr: &'a mut u64,
|
||||||
}
|
}
|
||||||
type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<AccountReferences<'a>>);
|
type TranslatedAccounts<'a> = (
|
||||||
|
Vec<Rc<RefCell<Account>>>,
|
||||||
|
Vec<Option<AccountReferences<'a>>>,
|
||||||
|
);
|
||||||
|
|
||||||
/// Implemented by language specific data structure translators
|
/// Implemented by language specific data structure translators
|
||||||
trait SyscallInvokeSigned<'a> {
|
trait SyscallInvokeSigned<'a> {
|
||||||
@ -847,7 +849,8 @@ trait SyscallInvokeSigned<'a> {
|
|||||||
) -> Result<Instruction, EbpfError<BPFError>>;
|
) -> Result<Instruction, EbpfError<BPFError>>;
|
||||||
fn translate_accounts(
|
fn translate_accounts(
|
||||||
&self,
|
&self,
|
||||||
message: &Message,
|
account_keys: &[Pubkey],
|
||||||
|
program_account_index: usize,
|
||||||
account_infos_addr: u64,
|
account_infos_addr: u64,
|
||||||
account_infos_len: u64,
|
account_infos_len: u64,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
@ -905,7 +908,8 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
|||||||
|
|
||||||
fn translate_accounts(
|
fn translate_accounts(
|
||||||
&self,
|
&self,
|
||||||
message: &Message,
|
account_keys: &[Pubkey],
|
||||||
|
program_account_index: usize,
|
||||||
account_infos_addr: u64,
|
account_infos_addr: u64,
|
||||||
account_infos_len: u64,
|
account_infos_len: u64,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
@ -921,9 +925,13 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
|||||||
&[]
|
&[]
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut accounts = Vec::with_capacity(message.account_keys.len());
|
let mut accounts = Vec::with_capacity(account_keys.len());
|
||||||
let mut refs = Vec::with_capacity(message.account_keys.len());
|
let mut refs = Vec::with_capacity(account_keys.len());
|
||||||
'root: for account_key in message.account_keys.iter() {
|
'root: for (i, account_key) in account_keys.iter().enumerate() {
|
||||||
|
if i == program_account_index {
|
||||||
|
// Don't look for caller passed executable, runtime already has it
|
||||||
|
continue 'root;
|
||||||
|
}
|
||||||
for account_info in account_infos.iter() {
|
for account_info in account_infos.iter() {
|
||||||
let key = translate_type::<Pubkey>(
|
let key = translate_type::<Pubkey>(
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
@ -986,14 +994,14 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
|||||||
owner: *owner,
|
owner: *owner,
|
||||||
rent_epoch: account_info.rent_epoch,
|
rent_epoch: account_info.rent_epoch,
|
||||||
})));
|
})));
|
||||||
refs.push(AccountReferences {
|
refs.push(Some(AccountReferences {
|
||||||
lamports,
|
lamports,
|
||||||
owner,
|
owner,
|
||||||
data,
|
data,
|
||||||
vm_data_addr,
|
vm_data_addr,
|
||||||
ref_to_len_in_vm,
|
ref_to_len_in_vm,
|
||||||
serialized_len_ptr,
|
serialized_len_ptr,
|
||||||
});
|
}));
|
||||||
continue 'root;
|
continue 'root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1182,7 +1190,8 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
|||||||
|
|
||||||
fn translate_accounts(
|
fn translate_accounts(
|
||||||
&self,
|
&self,
|
||||||
message: &Message,
|
account_keys: &[Pubkey],
|
||||||
|
program_account_index: usize,
|
||||||
account_infos_addr: u64,
|
account_infos_addr: u64,
|
||||||
account_infos_len: u64,
|
account_infos_len: u64,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
@ -1193,9 +1202,13 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
|||||||
account_infos_len,
|
account_infos_len,
|
||||||
self.loader_id,
|
self.loader_id,
|
||||||
)?;
|
)?;
|
||||||
let mut accounts = Vec::with_capacity(message.account_keys.len());
|
let mut accounts = Vec::with_capacity(account_keys.len());
|
||||||
let mut refs = Vec::with_capacity(message.account_keys.len());
|
let mut refs = Vec::with_capacity(account_keys.len());
|
||||||
'root: for account_key in message.account_keys.iter() {
|
'root: for (i, account_key) in account_keys.iter().enumerate() {
|
||||||
|
if i == program_account_index {
|
||||||
|
// Don't look for caller passed executable, runtime already has it
|
||||||
|
continue 'root;
|
||||||
|
}
|
||||||
for account_info in account_infos.iter() {
|
for account_info in account_infos.iter() {
|
||||||
let key = translate_type::<Pubkey>(
|
let key = translate_type::<Pubkey>(
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
@ -1247,14 +1260,14 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
|||||||
owner: *owner,
|
owner: *owner,
|
||||||
rent_epoch: account_info.rent_epoch,
|
rent_epoch: account_info.rent_epoch,
|
||||||
})));
|
})));
|
||||||
refs.push(AccountReferences {
|
refs.push(Some(AccountReferences {
|
||||||
lamports,
|
lamports,
|
||||||
owner,
|
owner,
|
||||||
data,
|
data,
|
||||||
vm_data_addr,
|
vm_data_addr,
|
||||||
ref_to_len_in_vm,
|
ref_to_len_in_vm,
|
||||||
serialized_len_ptr,
|
serialized_len_ptr,
|
||||||
});
|
}));
|
||||||
continue 'root;
|
continue 'root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1381,12 +1394,13 @@ fn call<'a>(
|
|||||||
.get_callers_keyed_accounts()
|
.get_callers_keyed_accounts()
|
||||||
.iter()
|
.iter()
|
||||||
.collect::<Vec<&KeyedAccount>>();
|
.collect::<Vec<&KeyedAccount>>();
|
||||||
let (message, callee_program_id) =
|
let (message, callee_program_id, program_id_index) =
|
||||||
MessageProcessor::create_message(&instruction, &keyed_account_refs, &signers)
|
MessageProcessor::create_message(&instruction, &keyed_account_refs, &signers)
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.map_err(SyscallError::InstructionError)?;
|
||||||
is_authorized_program(&callee_program_id)?;
|
is_authorized_program(&callee_program_id)?;
|
||||||
let (accounts, account_refs) = syscall.translate_accounts(
|
let (mut accounts, mut account_refs) = syscall.translate_accounts(
|
||||||
&message,
|
&message.account_keys,
|
||||||
|
program_id_index,
|
||||||
account_infos_addr,
|
account_infos_addr,
|
||||||
account_infos_len,
|
account_infos_len,
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
@ -1426,6 +1440,8 @@ fn call<'a>(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
accounts.insert(program_id_index, Rc::new(program_account.clone()));
|
||||||
|
account_refs.insert(program_id_index, None);
|
||||||
let mut executable_accounts = vec![(callee_program_id, program_account)];
|
let mut executable_accounts = vec![(callee_program_id, program_account)];
|
||||||
if let Some(programdata) = programdata_executable {
|
if let Some(programdata) = programdata_executable {
|
||||||
executable_accounts.push(programdata);
|
executable_accounts.push(programdata);
|
||||||
@ -1446,37 +1462,40 @@ 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() {
|
for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() {
|
||||||
let account = account.borrow();
|
let account = account.borrow();
|
||||||
if message.is_writable(i) && !account.executable {
|
if let Some(account_ref) = account_ref {
|
||||||
*account_ref.lamports = account.lamports;
|
if message.is_writable(i) && !account.executable {
|
||||||
*account_ref.owner = account.owner;
|
*account_ref.lamports = account.lamports;
|
||||||
if account_ref.data.len() != account.data.len() {
|
*account_ref.owner = account.owner;
|
||||||
if !account_ref.data.is_empty() {
|
if account_ref.data.len() != account.data.len() {
|
||||||
// Only support for `CreateAccount` at this time.
|
if !account_ref.data.is_empty() {
|
||||||
// Need a way to limit total realloc size across multiple CPI calls
|
// Only support for `CreateAccount` at this time.
|
||||||
return Err(
|
// Need a way to limit total realloc size across multiple CPI calls
|
||||||
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
|
return Err(SyscallError::InstructionError(
|
||||||
);
|
InstructionError::InvalidRealloc,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if account.data.len() > account_ref.data.len() + 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(
|
.data
|
||||||
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
|
.clone_from_slice(&account.data[0..account_ref.data.len()]);
|
||||||
);
|
|
||||||
}
|
|
||||||
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()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,58 +509,11 @@ impl MessageProcessor {
|
|||||||
Err(InstructionError::UnsupportedProgramId)
|
Err(InstructionError::UnsupportedProgramId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify instruction against the caller
|
|
||||||
pub fn verify_instruction(
|
|
||||||
keyed_accounts: &[&KeyedAccount],
|
|
||||||
instruction: &Instruction,
|
|
||||||
signers: &[Pubkey],
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
// Check for privilege escalation
|
|
||||||
for account in instruction.accounts.iter() {
|
|
||||||
let keyed_account = keyed_accounts
|
|
||||||
.iter()
|
|
||||||
.find_map(|keyed_account| {
|
|
||||||
if &account.pubkey == keyed_account.unsigned_key() {
|
|
||||||
Some(keyed_account)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok_or(InstructionError::MissingAccount)?;
|
|
||||||
// Readonly account cannot become writable
|
|
||||||
if account.is_writable && !keyed_account.is_writable() {
|
|
||||||
return Err(InstructionError::PrivilegeEscalation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.is_signer && // If message indicates account is signed
|
|
||||||
!( // one of the following needs to be true:
|
|
||||||
keyed_account.signer_key().is_some() // Signed in the parent instruction
|
|
||||||
|| signers.contains(&account.pubkey) // Signed by the program
|
|
||||||
) {
|
|
||||||
return Err(InstructionError::PrivilegeEscalation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the caller has access to the program account
|
|
||||||
let _ = keyed_accounts
|
|
||||||
.iter()
|
|
||||||
.find_map(|keyed_account| {
|
|
||||||
if &instruction.program_id == keyed_account.unsigned_key() {
|
|
||||||
Some(keyed_account)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok_or(InstructionError::MissingAccount)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_message(
|
pub fn create_message(
|
||||||
instruction: &Instruction,
|
instruction: &Instruction,
|
||||||
keyed_accounts: &[&KeyedAccount],
|
keyed_accounts: &[&KeyedAccount],
|
||||||
signers: &[Pubkey],
|
signers: &[Pubkey],
|
||||||
) -> Result<(Message, Pubkey), 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() {
|
||||||
let keyed_account = keyed_accounts
|
let keyed_account = keyed_accounts
|
||||||
@ -588,6 +541,7 @@ impl MessageProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate the caller has access to the program account
|
// validate the caller has access to the program account
|
||||||
|
let program_id = instruction.program_id;
|
||||||
let _ = keyed_accounts
|
let _ = keyed_accounts
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|keyed_account| {
|
.find_map(|keyed_account| {
|
||||||
@ -600,10 +554,9 @@ impl MessageProcessor {
|
|||||||
.ok_or(InstructionError::MissingAccount)?;
|
.ok_or(InstructionError::MissingAccount)?;
|
||||||
|
|
||||||
let message = Message::new(&[instruction.clone()], None);
|
let message = Message::new(&[instruction.clone()], None);
|
||||||
let id = *message
|
let program_id_index = message.instructions[0].program_id_index as usize;
|
||||||
.program_id(0)
|
|
||||||
.ok_or(InstructionError::MissingAccount)?;
|
Ok((message, program_id, program_id_index))
|
||||||
Ok((message, id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entrypoint for a cross-program invocation from a native program
|
/// Entrypoint for a cross-program invocation from a native program
|
||||||
@ -621,7 +574,7 @@ impl MessageProcessor {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id))
|
.map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id))
|
||||||
.collect::<Result<Vec<_>, solana_sdk::pubkey::PubkeyError>>()?;
|
.collect::<Result<Vec<_>, solana_sdk::pubkey::PubkeyError>>()?;
|
||||||
let (message, callee_program_id) =
|
let (message, callee_program_id, _) =
|
||||||
Self::create_message(&instruction, &keyed_accounts, &signers)?;
|
Self::create_message(&instruction, &keyed_accounts, &signers)?;
|
||||||
let mut accounts = vec![];
|
let mut accounts = vec![];
|
||||||
let mut account_refs = vec![];
|
let mut account_refs = vec![];
|
||||||
|
Reference in New Issue
Block a user