Limit CPI instruction size (#14317)
This commit is contained in:
@ -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_SEEDS = 6;
|
||||||
static const uint8_t TEST_CAP_SIGNERS = 7;
|
static const uint8_t TEST_CAP_SIGNERS = 7;
|
||||||
static const uint8_t TEST_ALLOC_ACCESS_VIOLATION = 8;
|
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 MINT_INDEX = 0;
|
||||||
static const int ARGUMENT_INDEX = 1;
|
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)));
|
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
|
||||||
break;
|
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:
|
default:
|
||||||
sol_panic();
|
sol_panic();
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5;
|
|||||||
const TEST_CAP_SEEDS: u8 = 6;
|
const TEST_CAP_SEEDS: u8 = 6;
|
||||||
const TEST_CAP_SIGNERS: u8 = 7;
|
const TEST_CAP_SIGNERS: u8 = 7;
|
||||||
const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8;
|
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 MINT_INDEX: usize = 0;
|
||||||
const ARGUMENT_INDEX: usize = 1;
|
const ARGUMENT_INDEX: usize = 1;
|
||||||
@ -497,6 +499,21 @@ fn process_instruction(
|
|||||||
&[&[b"You pass butter", &[bump_seed1]]],
|
&[&[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!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +566,8 @@ fn test_program_bpf_invoke() {
|
|||||||
const TEST_CAP_SEEDS: u8 = 6;
|
const TEST_CAP_SEEDS: u8 = 6;
|
||||||
const TEST_CAP_SIGNERS: u8 = 7;
|
const TEST_CAP_SIGNERS: u8 = 7;
|
||||||
const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8;
|
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)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -894,6 +896,70 @@ fn test_program_bpf_invoke() {
|
|||||||
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
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
|
// Check final state
|
||||||
|
|
||||||
assert_eq!(43, bank.get_balance(&derived_key1));
|
assert_eq!(43, bank.get_balance(&derived_key1));
|
||||||
|
@ -1018,6 +1018,7 @@ mod tests {
|
|||||||
max_call_depth: 20,
|
max_call_depth: 20,
|
||||||
stack_frame_size: 4096,
|
stack_frame_size: 4096,
|
||||||
log_pubkey_units: 100,
|
log_pubkey_units: 100,
|
||||||
|
max_cpi_instruction_size: usize::MAX,
|
||||||
},
|
},
|
||||||
Rc::new(RefCell::new(Executors::default())),
|
Rc::new(RefCell::new(Executors::default())),
|
||||||
None,
|
None,
|
||||||
|
@ -845,6 +845,7 @@ trait SyscallInvokeSigned<'a> {
|
|||||||
fn translate_instruction(
|
fn translate_instruction(
|
||||||
&self,
|
&self,
|
||||||
addr: u64,
|
addr: u64,
|
||||||
|
max_size: usize,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
) -> Result<Instruction, EbpfError<BPFError>>;
|
) -> Result<Instruction, EbpfError<BPFError>>;
|
||||||
fn translate_accounts(
|
fn translate_accounts(
|
||||||
@ -882,9 +883,12 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
|
|||||||
fn translate_instruction(
|
fn translate_instruction(
|
||||||
&self,
|
&self,
|
||||||
addr: u64,
|
addr: u64,
|
||||||
|
max_size: usize,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
) -> Result<Instruction, EbpfError<BPFError>> {
|
) -> Result<Instruction, EbpfError<BPFError>> {
|
||||||
let ix = translate_type::<Instruction>(memory_mapping, addr, self.loader_id)?;
|
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>(
|
let accounts = translate_slice::<AccountMeta>(
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
ix.accounts.as_ptr() as u64,
|
ix.accounts.as_ptr() as u64,
|
||||||
@ -1144,15 +1148,19 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
|
|||||||
.try_borrow_mut()
|
.try_borrow_mut()
|
||||||
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
|
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
|
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
|
||||||
self.callers_keyed_accounts
|
self.callers_keyed_accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_instruction(
|
fn translate_instruction(
|
||||||
&self,
|
&self,
|
||||||
addr: u64,
|
addr: u64,
|
||||||
|
max_size: usize,
|
||||||
memory_mapping: &MemoryMapping,
|
memory_mapping: &MemoryMapping,
|
||||||
) -> Result<Instruction, EbpfError<BPFError>> {
|
) -> Result<Instruction, EbpfError<BPFError>> {
|
||||||
let ix_c = translate_type::<SolInstruction>(memory_mapping, addr, self.loader_id)?;
|
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 =
|
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)?;
|
||||||
let meta_cs = translate_slice::<SolAccountMeta>(
|
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)
|
if native_loader::check_id(program_id)
|
||||||
|| bpf_loader::check_id(program_id)
|
|| bpf_loader::check_id(program_id)
|
||||||
|| bpf_loader_deprecated::check_id(program_id)
|
|| bpf_loader_deprecated::check_id(program_id)
|
||||||
@ -1380,7 +1401,13 @@ fn call<'a>(
|
|||||||
|
|
||||||
// Translate and verify caller's data
|
// 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
|
let caller_program_id = invoke_context
|
||||||
.get_caller()
|
.get_caller()
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.map_err(SyscallError::InstructionError)?;
|
||||||
@ -1397,7 +1424,7 @@ fn call<'a>(
|
|||||||
let (message, callee_program_id, program_id_index) =
|
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)?;
|
check_authorized_program(&callee_program_id)?;
|
||||||
let (mut accounts, mut account_refs) = syscall.translate_accounts(
|
let (mut accounts, mut account_refs) = syscall.translate_accounts(
|
||||||
&message.account_keys,
|
&message.account_keys,
|
||||||
program_id_index,
|
program_id_index,
|
||||||
|
@ -118,6 +118,10 @@ pub mod stake_program_v3 {
|
|||||||
solana_sdk::declare_id!("Ego6nTu7WsBcZBvVqJQKp6Yku2N3mrfG8oYCfaLZkAeK");
|
solana_sdk::declare_id!("Ego6nTu7WsBcZBvVqJQKp6Yku2N3mrfG8oYCfaLZkAeK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod max_cpi_instruction_size_ipv6_mtu {
|
||||||
|
solana_sdk::declare_id!("5WLtuUJA5VVA1Cc28qULPfGs8anhoBev8uNqaaXeasnf");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
@ -149,6 +153,7 @@ lazy_static! {
|
|||||||
(try_find_program_address_syscall_enabled::id(), "add try_find_program_address syscall"),
|
(try_find_program_address_syscall_enabled::id(), "add try_find_program_address syscall"),
|
||||||
(warp_testnet_timestamp::id(), "warp testnet timestamp to current #14210"),
|
(warp_testnet_timestamp::id(), "warp testnet timestamp to current #14210"),
|
||||||
(stake_program_v3::id(), "solana_stake_program v3"),
|
(stake_program_v3::id(), "solana_stake_program v3"),
|
||||||
|
(max_cpi_instruction_size_ipv6_mtu::id(), "Max cross-program invocation size 1280"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
feature_set::{
|
feature_set::{
|
||||||
bpf_compute_budget_balancing, max_invoke_depth_4, max_program_call_depth_64,
|
bpf_compute_budget_balancing, max_cpi_instruction_size_ipv6_mtu, max_invoke_depth_4,
|
||||||
pubkey_log_syscall_enabled, FeatureSet,
|
max_program_call_depth_64, pubkey_log_syscall_enabled, FeatureSet,
|
||||||
},
|
},
|
||||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||||
keyed_account::KeyedAccount,
|
keyed_account::KeyedAccount,
|
||||||
@ -91,6 +91,8 @@ pub struct BpfComputeBudget {
|
|||||||
pub stack_frame_size: usize,
|
pub stack_frame_size: usize,
|
||||||
/// Number of compute units consumed by logging a `Pubkey`
|
/// Number of compute units consumed by logging a `Pubkey`
|
||||||
pub log_pubkey_units: u64,
|
pub log_pubkey_units: u64,
|
||||||
|
/// Maximum cross-program invocation instruction size
|
||||||
|
pub max_cpi_instruction_size: usize,
|
||||||
}
|
}
|
||||||
impl Default for BpfComputeBudget {
|
impl Default for BpfComputeBudget {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -113,6 +115,7 @@ impl BpfComputeBudget {
|
|||||||
max_call_depth: 20,
|
max_call_depth: 20,
|
||||||
stack_frame_size: 4_096,
|
stack_frame_size: 4_096,
|
||||||
log_pubkey_units: 0,
|
log_pubkey_units: 0,
|
||||||
|
max_cpi_instruction_size: std::usize::MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
if feature_set.is_active(&bpf_compute_budget_balancing::id()) {
|
if feature_set.is_active(&bpf_compute_budget_balancing::id()) {
|
||||||
@ -144,6 +147,12 @@ impl BpfComputeBudget {
|
|||||||
..bpf_compute_budget
|
..bpf_compute_budget
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if feature_set.is_active(&max_cpi_instruction_size_ipv6_mtu::id()) {
|
||||||
|
bpf_compute_budget = BpfComputeBudget {
|
||||||
|
max_cpi_instruction_size: 1280, // IPv6 Min MTU size
|
||||||
|
..bpf_compute_budget
|
||||||
|
};
|
||||||
|
}
|
||||||
bpf_compute_budget
|
bpf_compute_budget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user