diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index 3ed95533d460d3..cfc8075cef0a71 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -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_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 uint8_t TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED = 9; +static const uint8_t TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED = 10; 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_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_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER = 20; 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 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) { - sol_log("Invoke C program"); + sol_log("invoke C program"); SolAccountInfo accounts[13]; SolParameters params = (SolParameters){.ka = accounts}; @@ -480,13 +481,14 @@ 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"); + case TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED: { + sol_log("Test max instruction data len exceeded"); 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, arguments, SOL_ARRAY_SIZE(arguments), - data, 1500}; + data, data_len}; const SolSignerSeeds signers_seeds[] = {}; sol_assert(SUCCESS == sol_invoke_signed( &instruction, accounts, SOL_ARRAY_SIZE(accounts), @@ -494,14 +496,14 @@ extern uint64_t entrypoint(const uint8_t *input) { 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); + case TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED: { + sol_log("Test max instruction accounts exceeded"); + uint64_t accounts_len = MAX_CPI_INSTRUCTION_ACCOUNTS + 1; + SolAccountMeta *arguments = sol_calloc(accounts_len, sizeof(SolAccountMeta)); sol_assert(0 != arguments); uint8_t data[] = {}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, - arguments, 40, data, + arguments, accounts_len, data, SOL_ARRAY_SIZE(data)}; const SolSignerSeeds signers_seeds[] = {}; sol_assert(SUCCESS == sol_invoke_signed( @@ -510,6 +512,23 @@ extern uint64_t entrypoint(const uint8_t *input) { 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: { sol_log("Test return error"); SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, false, true}}; diff --git a/programs/bpf/rust/invoke/src/instructions.rs b/programs/bpf/rust/invoke/src/instructions.rs index 97c1b75317e697..08a1fa3216413a 100644 --- a/programs/bpf/rust/invoke/src/instructions.rs +++ b/programs/bpf/rust/invoke/src/instructions.rs @@ -8,8 +8,8 @@ pub const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5; pub const TEST_CAP_SEEDS: u8 = 6; pub const TEST_CAP_SIGNERS: u8 = 7; pub const TEST_ALLOC_ACCESS_VIOLATION: u8 = 8; -pub const TEST_INSTRUCTION_DATA_TOO_LARGE: u8 = 9; -pub const TEST_INSTRUCTION_META_TOO_LARGE: u8 = 10; +pub const TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED: u8 = 9; +pub const TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED: u8 = 10; pub const TEST_RETURN_ERROR: u8 = 11; pub const TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 12; 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_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER: u8 = 20; 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 ARGUMENT_INDEX: usize = 1; diff --git a/programs/bpf/rust/invoke/src/processor.rs b/programs/bpf/rust/invoke/src/processor.rs index 6a6b1c1f0d2bbc..c3c92cf2f1cd02 100644 --- a/programs/bpf/rust/invoke/src/processor.rs +++ b/programs/bpf/rust/invoke/src/processor.rs @@ -14,6 +14,9 @@ use { program::{get_return_data, invoke, invoke_signed, set_return_data}, program_error::ProgramError, pubkey::{Pubkey, PubkeyError}, + syscalls::{ + MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN, + }, system_instruction, }, }; @@ -554,21 +557,29 @@ fn process_instruction( &[&[b"You pass butter", &[bump_seed1]]], )?; } - TEST_INSTRUCTION_DATA_TOO_LARGE => { - msg!("Test instruction data too large"); + TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED => { + msg!("Test max instruction data len exceeded"); + let data_len = MAX_CPI_INSTRUCTION_DATA_LEN.saturating_add(1) as usize; 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, &[], &[])?; } - 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![], - ); + TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED => { + msg!("Test max instruction accounts exceeded"); + let default_key = Pubkey::default(); + let account_metas_len = (MAX_CPI_INSTRUCTION_ACCOUNTS as usize).saturating_add(1); + let account_metas = vec![(&default_key, false, false); account_metas_len]; + let instruction = + create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &account_metas, vec![]); 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 => { msg!("Test return error"); let instruction = create_instruction( diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index cf5760427bad1e..e14e452bac70c7 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -347,7 +347,11 @@ fn run_program(name: &str) -> u64 { fn process_transaction_and_record_inner( bank: &Bank, tx: Transaction, -) -> (Result<(), TransactionError>, Vec>) { +) -> ( + Result<(), TransactionError>, + Vec>, + Vec, +) { let signature = tx.signatures.get(0).unwrap().clone(); let txs = vec![tx]; let tx_batch = bank.prepare_batch_for_tests(txs); @@ -357,7 +361,7 @@ fn process_transaction_and_record_inner( MAX_PROCESSING_AGE, false, true, - false, + true, false, &mut ExecuteTimings::default(), None, @@ -367,15 +371,19 @@ fn process_transaction_and_record_inner( .fee_collection_results .swap_remove(0) .and_then(|_| bank.get_signature_status(&signature).unwrap()); - let inner_instructions = results + let execution_details = results .execution_results .swap_remove(0) .details() .expect("tx should be executed") - .clone() + .clone(); + let inner_instructions = execution_details .inner_instructions .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( @@ -1064,7 +1072,8 @@ fn test_program_bpf_invoke_sanity() { 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); assert_eq!(result, Ok(())); let invoked_programs: Vec = inner_instructions[0] @@ -1130,7 +1139,10 @@ fn test_program_bpf_invoke_sanity() { // failure cases 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>| { println!("Running failure test #{:?}", test); let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3]; let signers = vec![ @@ -1146,85 +1158,141 @@ fn test_program_bpf_invoke_sanity() { ); let message = Message::new(&[instruction], Some(&mint_pubkey)); 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 = inner_instructions[0] .iter() .map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .collect(); assert_eq!(result, Err(expected_error)); 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( TEST_PRIVILEGE_ESCALATION_SIGNER, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_PRIVILEGE_ESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_PPROGRAM_NOT_EXECUTABLE, TransactionError::InstructionError(0, InstructionError::AccountNotExecutable), &[], + None, ); do_invoke_failure_test_local( TEST_EMPTY_ACCOUNTS_SLICE, TransactionError::InstructionError(0, InstructionError::MissingAccount), &[], + None, ); do_invoke_failure_test_local( TEST_CAP_SEEDS, TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded), &[], + None, ); do_invoke_failure_test_local( TEST_CAP_SIGNERS, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], + None, ); do_invoke_failure_test_local( - TEST_INSTRUCTION_DATA_TOO_LARGE, + TEST_MAX_INSTRUCTION_DATA_LEN_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 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( - TEST_INSTRUCTION_META_TOO_LARGE, + TEST_MAX_INSTRUCTION_ACCOUNTS_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 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( TEST_RETURN_ERROR, TransactionError::InstructionError(0, InstructionError::Custom(42)), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_WRITABLE_DEESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( @@ -1237,36 +1305,42 @@ fn test_program_bpf_invoke_sanity() { invoked_program_id.clone(), invoked_program_id.clone(), ], + None, ); do_invoke_failure_test_local( TEST_EXECUTABLE_LAMPORTS, TransactionError::InstructionError(0, InstructionError::ExecutableLamportChange), &[invoke_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_CALL_PRECOMPILE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], + None, ); do_invoke_failure_test_local( TEST_RETURN_DATA_TOO_LARGE, TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete), &[], + None, ); do_invoke_failure_test_local( TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); do_invoke_failure_test_local( TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE, TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation), &[invoked_program_id.clone()], + None, ); // Check resulting state @@ -1307,7 +1381,8 @@ fn test_program_bpf_invoke_sanity() { 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 = inner_instructions[0] .iter() .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(), bank.last_blockhash(), ); - let (result, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete) @@ -2415,7 +2490,7 @@ fn test_program_bpf_upgrade_self_via_cpi() { message.clone(), bank.last_blockhash(), ); - let (result, _) = process_transaction_and_record_inner(&bank, tx); + let (result, _, _) = process_transaction_and_record_inner(&bank, tx); assert_eq!( result.unwrap_err(), TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete) diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 261aaaf0f58080..f569812565d3e3 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -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> { lamports: &'a mut u64, @@ -94,10 +100,22 @@ impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> { invoke_context.get_check_size(), )? .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::( memory_mapping, ix.data.as_ptr() as u64, - ix.data.len() as u64, + ix_data_len, invoke_context.get_check_aligned(), 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_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::( memory_mapping, ix_c.data_addr, - ix_c.data_len as u64, + ix_data_len, invoke_context.get_check_aligned(), invoke_context.get_check_size(), )? @@ -719,36 +749,76 @@ fn check_instruction_size( data_len: usize, invoke_context: &mut InvokeContext, ) -> Result<(), EbpfError> { - let size = num_accounts - .saturating_mul(size_of::()) - .saturating_add(data_len); - let max_size = invoke_context.get_compute_budget().max_cpi_instruction_size; - if size > max_size { - return Err(SyscallError::InstructionTooLarge(size, max_size).into()); + if invoke_context + .feature_set + .is_active(&feature_set::loosen_cpi_size_restriction::id()) + { + let data_len = data_len as u64; + 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::()) + .saturating_add(data_len); + if size > max_size { + return Err(SyscallError::InstructionTooLarge(size, max_size).into()); + } } Ok(()) } fn check_account_infos( - len: usize, + num_account_infos: usize, invoke_context: &mut InvokeContext, ) -> Result<(), EbpfError> { - let adjusted_len = if invoke_context + if invoke_context .feature_set - .is_active(&syscall_saturated_math::id()) + .is_active(&feature_set::loosen_cpi_size_restriction::id()) { - len.saturating_mul(size_of::()) + let num_account_infos = num_account_infos as u64; + let max_account_infos = MAX_CPI_ACCOUNT_INFOS as u64; + if num_account_infos > max_account_infos { + return Err(SyscallError::MaxInstructionAccountInfosExceeded { + num_account_infos, + max_account_infos, + } + .into()); + } } else { - #[allow(clippy::integer_arithmetic)] + let adjusted_len = if invoke_context + .feature_set + .is_active(&syscall_saturated_math::id()) { - len * size_of::() - } - }; - 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()); - }; + num_account_infos.saturating_mul(size_of::()) + } else { + #[allow(clippy::integer_arithmetic)] + { + num_account_infos * size_of::() + } + }; + 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(()) } diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index d96345c98deb5f..2c33dd96d1725c 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -33,7 +33,7 @@ use { blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, 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, enable_early_verification_of_account_modifications, libsecp256k1_0_5_upgrade_enabled, limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs, @@ -110,6 +110,18 @@ pub enum SyscallError { TooManySlices, #[error("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 for EbpfError { fn from(error: SyscallError) -> Self { diff --git a/sdk/bpf/c/inc/sol/cpi.h b/sdk/bpf/c/inc/sol/cpi.h index a963e84287143e..a1c4c21de5e7f7 100644 --- a/sdk/bpf/c/inc/sol/cpi.h +++ b/sdk/bpf/c/inc/sol/cpi.h @@ -11,6 +11,28 @@ extern "C" { #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 */ diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 6a46ab90391bce..65b951014cfe84 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -603,7 +603,6 @@ pub mod slot_hashes; pub mod slot_history; pub mod stake; pub mod stake_history; -#[cfg(target_os = "solana")] pub mod syscalls; pub mod system_instruction; pub mod system_program; diff --git a/sdk/program/src/syscalls.rs b/sdk/program/src/syscalls/definitions.rs similarity index 100% rename from sdk/program/src/syscalls.rs rename to sdk/program/src/syscalls/definitions.rs diff --git a/sdk/program/src/syscalls/mod.rs b/sdk/program/src/syscalls/mod.rs new file mode 100644 index 00000000000000..aa0a85c23f4590 --- /dev/null +++ b/sdk/program/src/syscalls/mod.rs @@ -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; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 995ad8916771b3..020bd46a52058b 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -468,6 +468,10 @@ pub mod cap_bpf_program_instruction_accounts { solana_sdk::declare_id!("9k5ijzTbYPtjzu8wj2ErH9v45xecHzQ1x4PMYMMxFgdM"); } +pub mod loosen_cpi_size_restriction { + solana_sdk::declare_id!("GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -579,6 +583,7 @@ lazy_static! { (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"), (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 ***************/ ] .iter()