Limit CPI instruction size (#14317)

This commit is contained in:
Jack May
2020-12-28 17:14:17 -08:00
committed by GitHub
parent 7893e2e307
commit 5524938a50
7 changed files with 162 additions and 5 deletions

View File

@@ -12,6 +12,8 @@ static const uint8_t TEST_EMPTY_ACCOUNTS_SLICE = 5;
static const uint8_t TEST_CAP_SEEDS = 6;
static const uint8_t TEST_CAP_SIGNERS = 7;
static const uint8_t TEST_ALLOC_ACCESS_VIOLATION = 8;
static const uint8_t TEST_INSTRUCTION_DATA_TOO_LARGE = 9;
static const uint8_t TEST_INSTRUCTION_META_TOO_LARGE = 10;
static const int MINT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
@@ -405,6 +407,36 @@ extern uint64_t entrypoint(const uint8_t *input) {
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
break;
}
case TEST_INSTRUCTION_DATA_TOO_LARGE: {
sol_log("Test instruction data too large");
SolAccountMeta arguments[] = {};
uint8_t *data = sol_calloc(1500, 1);
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, 1500};
const SolSignerSeeds signers_seeds[] = {};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
break;
}
case TEST_INSTRUCTION_META_TOO_LARGE: {
sol_log("Test instruction meta too large");
SolAccountMeta *arguments = sol_calloc(40, sizeof(SolAccountMeta));
sol_log_64(0, 0, 0, 0, (uint64_t)arguments);
sol_assert(0 != arguments);
uint8_t data[] = {};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, 40, data,
SOL_ARRAY_SIZE(data)};
const SolSignerSeeds signers_seeds[] = {};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
break;
}
default:
sol_panic();
}

View File

@@ -24,6 +24,8 @@ const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5;
const TEST_CAP_SEEDS: u8 = 6;
const TEST_CAP_SIGNERS: u8 = 7;
const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8;
const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9;
const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10;
// const MINT_INDEX: usize = 0;
const ARGUMENT_INDEX: usize = 1;
@@ -497,6 +499,21 @@ fn process_instruction(
&[&[b"You pass butter", &[bump_seed1]]],
)?;
}
TEST_INSTRUCTION_DATA_TOO_LARGE => {
msg!("Test instruction data too large");
let instruction =
create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![0; 1500]);
invoke_signed(&instruction, &[], &[])?;
}
TEST_INSTRUCTION_META_TOO_LARGE => {
msg!("Test instruction metas too large");
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(&Pubkey::default(), false, false); 40],
vec![],
);
invoke_signed(&instruction, &[], &[])?;
}
_ => panic!(),
}

View File

@@ -566,6 +566,8 @@ fn test_program_bpf_invoke() {
const TEST_CAP_SEEDS: u8 = 6;
const TEST_CAP_SIGNERS: u8 = 7;
const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8;
const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9;
const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10;
#[allow(dead_code)]
#[derive(Debug)]
@@ -894,6 +896,70 @@ fn test_program_bpf_invoke() {
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
let instruction = Instruction::new(
invoke_program_id,
&[
TEST_INSTRUCTION_DATA_TOO_LARGE,
bump_seed1,
bump_seed2,
bump_seed3,
],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![]);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::ComputationalBudgetExceeded)
);
let instruction = Instruction::new(
invoke_program_id,
&[
TEST_INSTRUCTION_META_TOO_LARGE,
bump_seed1,
bump_seed2,
bump_seed3,
],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![]);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::ComputationalBudgetExceeded)
);
// Check final state
assert_eq!(43, bank.get_balance(&derived_key1));

View File

@@ -1018,6 +1018,7 @@ mod tests {
max_call_depth: 20,
stack_frame_size: 4096,
log_pubkey_units: 100,
max_cpi_instruction_size: usize::MAX,
},
Rc::new(RefCell::new(Executors::default())),
None,

View File

@@ -845,6 +845,7 @@ trait SyscallInvokeSigned<'a> {
fn translate_instruction(
&self,
addr: u64,
max_size: usize,
memory_mapping: &MemoryMapping,
) -> Result<Instruction, EbpfError<BPFError>>;
fn translate_accounts(
@@ -882,9 +883,12 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
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)?;
let accounts = translate_slice::<AccountMeta>(
memory_mapping,
ix.accounts.as_ptr() as u64,
@@ -1144,15 +1148,19 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
.try_borrow_mut()
.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_c = translate_type::<SolInstruction>(memory_mapping, addr, self.loader_id)?;
check_instruction_size(ix_c.accounts_len, ix_c.data_len, max_size)?;
let program_id =
translate_type::<Pubkey>(memory_mapping, ix_c.program_id_addr, self.loader_id)?;
let meta_cs = translate_slice::<SolAccountMeta>(
@@ -1352,7 +1360,20 @@ impl<'a> SyscallObject<BPFError> for SyscallInvokeSignedC<'a> {
}
}
fn is_authorized_program(program_id: &Pubkey) -> Result<(), EbpfError<BPFError>> {
fn check_instruction_size(
num_accounts: usize,
data_len: usize,
max_size: usize,
) -> Result<(), EbpfError<BPFError>> {
if max_size < num_accounts * size_of::<AccountMeta>() + data_len {
return Err(
SyscallError::InstructionError(InstructionError::ComputationalBudgetExceeded).into(),
);
}
Ok(())
}
fn check_authorized_program(program_id: &Pubkey) -> Result<(), EbpfError<BPFError>> {
if native_loader::check_id(program_id)
|| bpf_loader::check_id(program_id)
|| bpf_loader_deprecated::check_id(program_id)
@@ -1380,7 +1401,13 @@ fn call<'a>(
// Translate and verify caller's data
let instruction = syscall.translate_instruction(instruction_addr, &memory_mapping)?;
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)?;
@@ -1397,7 +1424,7 @@ fn call<'a>(
let (message, callee_program_id, program_id_index) =
MessageProcessor::create_message(&instruction, &keyed_account_refs, &signers)
.map_err(SyscallError::InstructionError)?;
is_authorized_program(&callee_program_id)?;
check_authorized_program(&callee_program_id)?;
let (mut accounts, mut account_refs) = syscall.translate_accounts(
&message.account_keys,
program_id_index,