Loosen CPI restrictions and charge compute for ix data len (#26653)

* Loosen CPI restrictions and charge compute for ix data len

* Address feedback

* use explicit casting

* more feedback
This commit is contained in:
Justin Starry 2022-07-24 20:20:16 +02:00 committed by GitHub
parent d9c7bc7e78
commit 2335f6908a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 297 additions and 62 deletions

View File

@ -18,8 +18,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_MAX_INSTRUCTION_DATA_LEN_EXCEEDED = 9;
static const uint8_t TEST_INSTRUCTION_META_TOO_LARGE = 10; static const uint8_t TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED = 10;
static const uint8_t TEST_RETURN_ERROR = 11; static const uint8_t TEST_RETURN_ERROR = 11;
static const uint8_t TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER = 12; static const uint8_t TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER = 12;
static const uint8_t TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 13; static const uint8_t TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 13;
@ -31,6 +31,7 @@ static const uint8_t ADD_LAMPORTS = 18;
static const uint8_t TEST_RETURN_DATA_TOO_LARGE = 19; static const uint8_t TEST_RETURN_DATA_TOO_LARGE = 19;
static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER = 20; static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER = 20;
static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE = 21; static const uint8_t TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE = 21;
static const uint8_t TEST_MAX_ACCOUNT_INFOS_EXCEEDED = 22;
static const int MINT_INDEX = 0; static const int MINT_INDEX = 0;
static const int ARGUMENT_INDEX = 1; static const int ARGUMENT_INDEX = 1;
@ -76,7 +77,7 @@ uint64_t do_nested_invokes(uint64_t num_nested_invokes,
} }
extern uint64_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
sol_log("Invoke C program"); sol_log("invoke C program");
SolAccountInfo accounts[13]; SolAccountInfo accounts[13];
SolParameters params = (SolParameters){.ka = accounts}; SolParameters params = (SolParameters){.ka = accounts};
@ -480,13 +481,14 @@ 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: { case TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED: {
sol_log("Test instruction data too large"); sol_log("Test max instruction data len exceeded");
SolAccountMeta arguments[] = {}; SolAccountMeta arguments[] = {};
uint8_t *data = sol_calloc(1500, 1); uint64_t data_len = MAX_CPI_INSTRUCTION_DATA_LEN + 1;
uint8_t *data = sol_calloc(data_len, 1);
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),
data, 1500}; data, data_len};
const SolSignerSeeds signers_seeds[] = {}; const SolSignerSeeds signers_seeds[] = {};
sol_assert(SUCCESS == sol_invoke_signed( sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts), &instruction, accounts, SOL_ARRAY_SIZE(accounts),
@ -494,14 +496,14 @@ extern uint64_t entrypoint(const uint8_t *input) {
break; break;
} }
case TEST_INSTRUCTION_META_TOO_LARGE: { case TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED: {
sol_log("Test instruction meta too large"); sol_log("Test max instruction accounts exceeded");
SolAccountMeta *arguments = sol_calloc(40, sizeof(SolAccountMeta)); uint64_t accounts_len = MAX_CPI_INSTRUCTION_ACCOUNTS + 1;
sol_log_64(0, 0, 0, 0, (uint64_t)arguments); SolAccountMeta *arguments = sol_calloc(accounts_len, sizeof(SolAccountMeta));
sol_assert(0 != arguments); sol_assert(0 != arguments);
uint8_t data[] = {}; uint8_t data[] = {};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, 40, data, arguments, accounts_len, data,
SOL_ARRAY_SIZE(data)}; SOL_ARRAY_SIZE(data)};
const SolSignerSeeds signers_seeds[] = {}; const SolSignerSeeds signers_seeds[] = {};
sol_assert(SUCCESS == sol_invoke_signed( sol_assert(SUCCESS == sol_invoke_signed(
@ -510,6 +512,23 @@ extern uint64_t entrypoint(const uint8_t *input) {
break; break;
} }
case TEST_MAX_ACCOUNT_INFOS_EXCEEDED: {
sol_log("Test max account infos exceeded");
SolAccountMeta arguments[] = {};
uint64_t account_infos_len = MAX_CPI_ACCOUNT_INFOS + 1;
SolAccountInfo *account_infos = sol_calloc(account_infos_len, sizeof(SolAccountInfo));
sol_assert(0 != account_infos);
uint8_t data[] = {};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
const SolSignerSeeds signers_seeds[] = {};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, account_infos, account_infos_len,
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
break;
}
case TEST_RETURN_ERROR: { case TEST_RETURN_ERROR: {
sol_log("Test return error"); sol_log("Test return error");
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, false, true}}; SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, false, true}};

View File

@ -8,8 +8,8 @@ pub const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5;
pub const TEST_CAP_SEEDS: u8 = 6; pub const TEST_CAP_SEEDS: u8 = 6;
pub const TEST_CAP_SIGNERS: u8 = 7; pub const TEST_CAP_SIGNERS: u8 = 7;
pub const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8; pub const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8;
pub const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9; pub const TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED: u8 = 9;
pub const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10; pub const TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED: u8 = 10;
pub const TEST_RETURN_ERROR: u8 = 11; pub const TEST_RETURN_ERROR: u8 = 11;
pub const TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 12; pub const TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 12;
pub const TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 13; pub const TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 13;
@ -21,6 +21,7 @@ pub const ADD_LAMPORTS: u8 = 18;
pub const TEST_RETURN_DATA_TOO_LARGE: u8 = 19; pub const TEST_RETURN_DATA_TOO_LARGE: u8 = 19;
pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER: u8 = 20; pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER: u8 = 20;
pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE: u8 = 21; pub const TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE: u8 = 21;
pub const TEST_MAX_ACCOUNT_INFOS_EXCEEDED: u8 = 22;
pub const MINT_INDEX: usize = 0; pub const MINT_INDEX: usize = 0;
pub const ARGUMENT_INDEX: usize = 1; pub const ARGUMENT_INDEX: usize = 1;

View File

@ -14,6 +14,9 @@ use {
program::{get_return_data, invoke, invoke_signed, set_return_data}, program::{get_return_data, invoke, invoke_signed, set_return_data},
program_error::ProgramError, program_error::ProgramError,
pubkey::{Pubkey, PubkeyError}, pubkey::{Pubkey, PubkeyError},
syscalls::{
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
},
system_instruction, system_instruction,
}, },
}; };
@ -554,21 +557,29 @@ fn process_instruction(
&[&[b"You pass butter", &[bump_seed1]]], &[&[b"You pass butter", &[bump_seed1]]],
)?; )?;
} }
TEST_INSTRUCTION_DATA_TOO_LARGE => { TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED => {
msg!("Test instruction data too large"); msg!("Test max instruction data len exceeded");
let data_len = MAX_CPI_INSTRUCTION_DATA_LEN.saturating_add(1) as usize;
let instruction = let instruction =
create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![0; 1500]); create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![0; data_len]);
invoke_signed(&instruction, &[], &[])?; invoke_signed(&instruction, &[], &[])?;
} }
TEST_INSTRUCTION_META_TOO_LARGE => { TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED => {
msg!("Test instruction metas too large"); msg!("Test max instruction accounts exceeded");
let instruction = create_instruction( let default_key = Pubkey::default();
*accounts[INVOKED_PROGRAM_INDEX].key, let account_metas_len = (MAX_CPI_INSTRUCTION_ACCOUNTS as usize).saturating_add(1);
&[(&Pubkey::default(), false, false); 40], let account_metas = vec![(&default_key, false, false); account_metas_len];
vec![], let instruction =
); create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &account_metas, vec![]);
invoke_signed(&instruction, &[], &[])?; invoke_signed(&instruction, &[], &[])?;
} }
TEST_MAX_ACCOUNT_INFOS_EXCEEDED => {
msg!("Test max account infos exceeded");
let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![]);
let account_infos_len = (MAX_CPI_ACCOUNT_INFOS as usize).saturating_add(1);
let account_infos = vec![accounts[0].clone(); account_infos_len];
invoke_signed(&instruction, &account_infos, &[])?;
}
TEST_RETURN_ERROR => { TEST_RETURN_ERROR => {
msg!("Test return error"); msg!("Test return error");
let instruction = create_instruction( let instruction = create_instruction(

View File

@ -347,7 +347,11 @@ fn run_program(name: &str) -> u64 {
fn process_transaction_and_record_inner( fn process_transaction_and_record_inner(
bank: &Bank, bank: &Bank,
tx: Transaction, tx: Transaction,
) -> (Result<(), TransactionError>, Vec<Vec<CompiledInstruction>>) { ) -> (
Result<(), TransactionError>,
Vec<Vec<CompiledInstruction>>,
Vec<String>,
) {
let signature = tx.signatures.get(0).unwrap().clone(); let signature = tx.signatures.get(0).unwrap().clone();
let txs = vec![tx]; let txs = vec![tx];
let tx_batch = bank.prepare_batch_for_tests(txs); let tx_batch = bank.prepare_batch_for_tests(txs);
@ -357,7 +361,7 @@ fn process_transaction_and_record_inner(
MAX_PROCESSING_AGE, MAX_PROCESSING_AGE,
false, false,
true, true,
false, true,
false, false,
&mut ExecuteTimings::default(), &mut ExecuteTimings::default(),
None, None,
@ -367,15 +371,19 @@ fn process_transaction_and_record_inner(
.fee_collection_results .fee_collection_results
.swap_remove(0) .swap_remove(0)
.and_then(|_| bank.get_signature_status(&signature).unwrap()); .and_then(|_| bank.get_signature_status(&signature).unwrap());
let inner_instructions = results let execution_details = results
.execution_results .execution_results
.swap_remove(0) .swap_remove(0)
.details() .details()
.expect("tx should be executed") .expect("tx should be executed")
.clone() .clone();
let inner_instructions = execution_details
.inner_instructions .inner_instructions
.expect("cpi recording should be enabled"); .expect("cpi recording should be enabled");
(result, inner_instructions) let log_messages = execution_details
.log_messages
.expect("log recording should be enabled");
(result, inner_instructions, log_messages)
} }
fn execute_transactions( fn execute_transactions(
@ -1064,7 +1072,8 @@ fn test_program_bpf_invoke_sanity() {
message.clone(), message.clone(),
bank.last_blockhash(), bank.last_blockhash(),
); );
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx); let (result, inner_instructions, _log_messages) =
process_transaction_and_record_inner(&bank, tx);
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
@ -1130,7 +1139,10 @@ fn test_program_bpf_invoke_sanity() {
// failure cases // failure cases
let do_invoke_failure_test_local = let do_invoke_failure_test_local =
|test: u8, expected_error: TransactionError, expected_invoked_programs: &[Pubkey]| { |test: u8,
expected_error: TransactionError,
expected_invoked_programs: &[Pubkey],
expected_log_messages: Option<Vec<String>>| {
println!("Running failure test #{:?}", test); println!("Running failure test #{:?}", test);
let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3]; let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3];
let signers = vec![ let signers = vec![
@ -1146,85 +1158,141 @@ fn test_program_bpf_invoke_sanity() {
); );
let message = Message::new(&[instruction], Some(&mint_pubkey)); let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(&signers, message.clone(), bank.last_blockhash()); let tx = Transaction::new(&signers, message.clone(), bank.last_blockhash());
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx); let (result, inner_instructions, log_messages) =
process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect(); .collect();
assert_eq!(result, Err(expected_error)); assert_eq!(result, Err(expected_error));
assert_eq!(invoked_programs, expected_invoked_programs); assert_eq!(invoked_programs, expected_invoked_programs);
if let Some(expected_log_messages) = expected_log_messages {
expected_log_messages
.into_iter()
.zip(log_messages)
.for_each(|(expected_log_message, log_message)| {
if expected_log_message != String::from("skip") {
assert_eq!(log_message, expected_log_message);
}
});
}
}; };
let program_lang = match program.0 {
Languages::Rust => "Rust",
Languages::C => "C",
};
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_PRIVILEGE_ESCALATION_SIGNER, TEST_PRIVILEGE_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_PRIVILEGE_ESCALATION_WRITABLE, TEST_PRIVILEGE_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_PPROGRAM_NOT_EXECUTABLE, TEST_PPROGRAM_NOT_EXECUTABLE,
TransactionError::InstructionError(0, InstructionError::AccountNotExecutable), TransactionError::InstructionError(0, InstructionError::AccountNotExecutable),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_EMPTY_ACCOUNTS_SLICE, TEST_EMPTY_ACCOUNTS_SLICE,
TransactionError::InstructionError(0, InstructionError::MissingAccount), TransactionError::InstructionError(0, InstructionError::MissingAccount),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_CAP_SEEDS, TEST_CAP_SEEDS,
TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded), TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_CAP_SIGNERS, TEST_CAP_SIGNERS,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_INSTRUCTION_DATA_TOO_LARGE, TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max instruction data len exceeded".into(),
"skip".into(), // don't compare compute consumption logs
"Program failed to complete: Invoked an instruction with data that is too large (10241 > 10240)".into(),
format!("Program {invoke_program_id} failed: Program failed to complete"),
]),
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_INSTRUCTION_META_TOO_LARGE, TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max instruction accounts exceeded".into(),
"skip".into(), // don't compare compute consumption logs
"Program failed to complete: Invoked an instruction with too many accounts (256 > 255)".into(),
format!("Program {invoke_program_id} failed: Program failed to complete"),
]),
);
do_invoke_failure_test_local(
TEST_MAX_ACCOUNT_INFOS_EXCEEDED,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
Some(vec![
format!("Program {invoke_program_id} invoke [1]"),
format!("Program log: invoke {program_lang} program"),
"Program log: Test max account infos exceeded".into(),
"skip".into(), // don't compare compute consumption logs
"Program failed to complete: Invoked an instruction with too many account info's (65 > 64)".into(),
format!("Program {invoke_program_id} failed: Program failed to complete"),
]),
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_RETURN_ERROR, TEST_RETURN_ERROR,
TransactionError::InstructionError(0, InstructionError::Custom(42)), TransactionError::InstructionError(0, InstructionError::Custom(42)),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER, TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE, TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_WRITABLE_DEESCALATION_WRITABLE, TEST_WRITABLE_DEESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
@ -1237,36 +1305,42 @@ fn test_program_bpf_invoke_sanity() {
invoked_program_id.clone(), invoked_program_id.clone(),
invoked_program_id.clone(), invoked_program_id.clone(),
], ],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_EXECUTABLE_LAMPORTS, TEST_EXECUTABLE_LAMPORTS,
TransactionError::InstructionError(0, InstructionError::ExecutableLamportChange), TransactionError::InstructionError(0, InstructionError::ExecutableLamportChange),
&[invoke_program_id.clone()], &[invoke_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_CALL_PRECOMPILE, TEST_CALL_PRECOMPILE,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_RETURN_DATA_TOO_LARGE, TEST_RETURN_DATA_TOO_LARGE,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[], &[],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER, TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
do_invoke_failure_test_local( do_invoke_failure_test_local(
TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE, TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE,
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
&[invoked_program_id.clone()], &[invoked_program_id.clone()],
None,
); );
// Check resulting state // Check resulting state
@ -1307,7 +1381,8 @@ fn test_program_bpf_invoke_sanity() {
message.clone(), message.clone(),
bank.last_blockhash(), bank.last_blockhash(),
); );
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx); let (result, inner_instructions, _log_messages) =
process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| message.account_keys[ix.program_id_index as usize].clone())
@ -2042,7 +2117,7 @@ fn test_program_bpf_upgrade_and_invoke_in_same_tx() {
message.clone(), message.clone(),
bank.last_blockhash(), bank.last_blockhash(),
); );
let (result, _) = process_transaction_and_record_inner(&bank, tx); let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete) TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete)
@ -2415,7 +2490,7 @@ fn test_program_bpf_upgrade_self_via_cpi() {
message.clone(), message.clone(),
bank.last_blockhash(), bank.last_blockhash(),
); );
let (result, _) = process_transaction_and_record_inner(&bank, tx); let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete) TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete)

View File

@ -1,4 +1,10 @@
use {super::*, crate::declare_syscall}; use {
super::*,
crate::declare_syscall,
solana_sdk::syscalls::{
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
},
};
struct CallerAccount<'a> { struct CallerAccount<'a> {
lamports: &'a mut u64, lamports: &'a mut u64,
@ -94,10 +100,22 @@ impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> {
invoke_context.get_check_size(), invoke_context.get_check_size(),
)? )?
.to_vec(); .to_vec();
let ix_data_len = ix.data.len() as u64;
if invoke_context
.feature_set
.is_active(&feature_set::loosen_cpi_size_restriction::id())
{
invoke_context.get_compute_meter().consume(
(ix_data_len)
.saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit),
)?;
}
let data = translate_slice::<u8>( let data = translate_slice::<u8>(
memory_mapping, memory_mapping,
ix.data.as_ptr() as u64, ix.data.as_ptr() as u64,
ix.data.len() as u64, ix_data_len,
invoke_context.get_check_aligned(), invoke_context.get_check_aligned(),
invoke_context.get_check_size(), invoke_context.get_check_size(),
)? )?
@ -383,10 +401,22 @@ impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedC<'a, 'b> {
invoke_context.get_check_aligned(), invoke_context.get_check_aligned(),
invoke_context.get_check_size(), invoke_context.get_check_size(),
)?; )?;
let ix_data_len = ix_c.data_len as u64;
if invoke_context
.feature_set
.is_active(&feature_set::loosen_cpi_size_restriction::id())
{
invoke_context.get_compute_meter().consume(
(ix_data_len)
.saturating_div(invoke_context.get_compute_budget().cpi_bytes_per_unit),
)?;
}
let data = translate_slice::<u8>( let data = translate_slice::<u8>(
memory_mapping, memory_mapping,
ix_c.data_addr, ix_c.data_addr,
ix_c.data_len as u64, ix_data_len,
invoke_context.get_check_aligned(), invoke_context.get_check_aligned(),
invoke_context.get_check_size(), invoke_context.get_check_size(),
)? )?
@ -719,36 +749,76 @@ fn check_instruction_size(
data_len: usize, data_len: usize,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
) -> Result<(), EbpfError<BpfError>> { ) -> Result<(), EbpfError<BpfError>> {
let size = num_accounts if invoke_context
.saturating_mul(size_of::<AccountMeta>()) .feature_set
.saturating_add(data_len); .is_active(&feature_set::loosen_cpi_size_restriction::id())
let max_size = invoke_context.get_compute_budget().max_cpi_instruction_size; {
if size > max_size { let data_len = data_len as u64;
return Err(SyscallError::InstructionTooLarge(size, max_size).into()); let max_data_len = MAX_CPI_INSTRUCTION_DATA_LEN;
if data_len > max_data_len {
return Err(SyscallError::MaxInstructionDataLenExceeded {
data_len,
max_data_len,
}
.into());
}
let num_accounts = num_accounts as u64;
let max_accounts = MAX_CPI_INSTRUCTION_ACCOUNTS as u64;
if num_accounts > max_accounts {
return Err(SyscallError::MaxInstructionAccountsExceeded {
num_accounts,
max_accounts,
}
.into());
}
} else {
let max_size = invoke_context.get_compute_budget().max_cpi_instruction_size;
let size = num_accounts
.saturating_mul(size_of::<AccountMeta>())
.saturating_add(data_len);
if size > max_size {
return Err(SyscallError::InstructionTooLarge(size, max_size).into());
}
} }
Ok(()) Ok(())
} }
fn check_account_infos( fn check_account_infos(
len: usize, num_account_infos: usize,
invoke_context: &mut InvokeContext, invoke_context: &mut InvokeContext,
) -> Result<(), EbpfError<BpfError>> { ) -> Result<(), EbpfError<BpfError>> {
let adjusted_len = if invoke_context if invoke_context
.feature_set .feature_set
.is_active(&syscall_saturated_math::id()) .is_active(&feature_set::loosen_cpi_size_restriction::id())
{ {
len.saturating_mul(size_of::<Pubkey>()) let num_account_infos = num_account_infos as u64;
} else { let max_account_infos = MAX_CPI_ACCOUNT_INFOS as u64;
#[allow(clippy::integer_arithmetic)] if num_account_infos > max_account_infos {
{ return Err(SyscallError::MaxInstructionAccountInfosExceeded {
len * size_of::<Pubkey>() num_account_infos,
max_account_infos,
}
.into());
} }
}; } else {
if adjusted_len > invoke_context.get_compute_budget().max_cpi_instruction_size { let adjusted_len = if invoke_context
// Cap the number of account_infos a caller can pass to approximate .feature_set
// maximum that accounts that could be passed in an instruction .is_active(&syscall_saturated_math::id())
return Err(SyscallError::TooManyAccounts.into()); {
}; num_account_infos.saturating_mul(size_of::<Pubkey>())
} else {
#[allow(clippy::integer_arithmetic)]
{
num_account_infos * size_of::<Pubkey>()
}
};
if adjusted_len > invoke_context.get_compute_budget().max_cpi_instruction_size {
// 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::TooManyAccounts.into());
};
}
Ok(()) Ok(())
} }

View File

@ -33,7 +33,7 @@ use {
blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS},
feature_set::{ feature_set::{
blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size, self, blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size,
curve25519_syscall_enabled, disable_fees_sysvar, curve25519_syscall_enabled, disable_fees_sysvar,
enable_early_verification_of_account_modifications, libsecp256k1_0_5_upgrade_enabled, enable_early_verification_of_account_modifications, libsecp256k1_0_5_upgrade_enabled,
limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs, limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs,
@ -110,6 +110,18 @@ pub enum SyscallError {
TooManySlices, TooManySlices,
#[error("InvalidLength")] #[error("InvalidLength")]
InvalidLength, InvalidLength,
#[error("Invoked an instruction with data that is too large ({data_len} > {max_data_len})")]
MaxInstructionDataLenExceeded { data_len: u64, max_data_len: u64 },
#[error("Invoked an instruction with too many accounts ({num_accounts} > {max_accounts})")]
MaxInstructionAccountsExceeded {
num_accounts: u64,
max_accounts: u64,
},
#[error("Invoked an instruction with too many account info's ({num_account_infos} > {max_account_infos})")]
MaxInstructionAccountInfosExceeded {
num_account_infos: u64,
max_account_infos: u64,
},
} }
impl From<SyscallError> for EbpfError<BpfError> { impl From<SyscallError> for EbpfError<BpfError> {
fn from(error: SyscallError) -> Self { fn from(error: SyscallError) -> Self {

View File

@ -11,6 +11,28 @@
extern "C" { extern "C" {
#endif #endif
/**
* Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI
* instructions are not more limited than transaction instructions if the size
* of transactions is doubled in the future.
*/
static const uint64_t MAX_CPI_INSTRUCTION_DATA_LEN = 10240;
/**
* Maximum CPI instruction accounts. 255 was chosen to ensure that instruction
* accounts are always within the maximum instruction account limit for BPF
* program instructions.
*/
static const uint8_t MAX_CPI_INSTRUCTION_ACCOUNTS = 255;
/**
* Maximum number of account info structs that can be used in a single CPI
* invocation. A limit on account info structs is effectively the same as
* limiting the number of unique accounts. 64 was chosen to match the max
* number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS).
*/
static const uint8_t MAX_CPI_ACCOUNT_INFOS = 64;
/** /**
* Account Meta * Account Meta
*/ */

View File

@ -603,7 +603,6 @@ pub mod slot_hashes;
pub mod slot_history; pub mod slot_history;
pub mod stake; pub mod stake;
pub mod stake_history; pub mod stake_history;
#[cfg(target_os = "solana")]
pub mod syscalls; pub mod syscalls;
pub mod system_instruction; pub mod system_instruction;
pub mod system_program; pub mod system_program;

View File

@ -0,0 +1,21 @@
#[cfg(target_os = "solana")]
mod definitions;
#[cfg(target_os = "solana")]
pub use definitions::*;
/// Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI
/// instructions are not more limited than transaction instructions if the size
/// of transactions is doubled in the future.
pub const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024;
/// Maximum CPI instruction accounts. 255 was chosen to ensure that instruction
/// accounts are always within the maximum instruction account limit for BPF
/// program instructions.
pub const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX;
/// Maximum number of account info structs that can be used in a single CPI
/// invocation. A limit on account info structs is effectively the same as
/// limiting the number of unique accounts. 64 was chosen to match the max
/// number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS).
pub const MAX_CPI_ACCOUNT_INFOS: usize = 64;

View File

@ -468,6 +468,10 @@ pub mod cap_bpf_program_instruction_accounts {
solana_sdk::declare_id!("9k5ijzTbYPtjzu8wj2ErH9v45xecHzQ1x4PMYMMxFgdM"); solana_sdk::declare_id!("9k5ijzTbYPtjzu8wj2ErH9v45xecHzQ1x4PMYMMxFgdM");
} }
pub mod loosen_cpi_size_restriction {
solana_sdk::declare_id!("GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm");
}
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> = [
@ -579,6 +583,7 @@ lazy_static! {
(enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"), (enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"),
(prevent_crediting_accounts_that_end_rent_paying::id(), "prevent crediting rent paying accounts #26606"), (prevent_crediting_accounts_that_end_rent_paying::id(), "prevent crediting rent paying accounts #26606"),
(cap_bpf_program_instruction_accounts::id(), "enforce max number of accounts per bpf program instruction #26628"), (cap_bpf_program_instruction_accounts::id(), "enforce max number of accounts per bpf program instruction #26628"),
(loosen_cpi_size_restriction::id(), "loosen cpi size restrictions #26641"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()