diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index 88c745f015..0c8927b0b4 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -7,6 +7,7 @@ static const uint8_t TEST_SUCCESS = 1; static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2; static const uint8_t TEST_PRIVILEGE_ESCALATION_WRITABLE = 3; +static const uint8_t TEST_PPROGRAM_NOT_EXECUTABLE = 4; static const int MINT_INDEX = 0; static const int ARGUMENT_INDEX = 1; @@ -272,6 +273,16 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)); break; } + case TEST_PPROGRAM_NOT_EXECUTABLE: { + sol_log("Test program not executable"); + SolAccountMeta arguments[] = { + {accounts[DERIVED_KEY3_INDEX].key, false, false}}; + uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION}; + const SolInstruction instruction = {accounts[ARGUMENT_INDEX].key, arguments, + SOL_ARRAY_SIZE(arguments), data, + SOL_ARRAY_SIZE(data)}; + return sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)); + } default: sol_panic(); } diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index d1ba88fe86..4fd842cc1c 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -19,6 +19,7 @@ use solana_sdk::{ const TEST_SUCCESS: u8 = 1; const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; +const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; // const MINT_INDEX: usize = 0; const ARGUMENT_INDEX: usize = 1; @@ -241,10 +242,7 @@ fn process_instruction( // Signer privilege escalation will always fail the whole transaction invoked_instruction.accounts[0].is_signer = true; - assert_eq!( - invoke(&invoked_instruction, accounts), - Err(ProgramError::Custom(0x0b9f_0002)) - ); + invoke(&invoked_instruction, accounts)?; } TEST_PRIVILEGE_ESCALATION_WRITABLE => { info!("Test privilege escalation writable"); @@ -257,10 +255,17 @@ fn process_instruction( // Writable privilege escalation will always fail the whole transaction invoked_instruction.accounts[0].is_writable = true; - assert_eq!( - invoke(&invoked_instruction, accounts), - Err(ProgramError::Custom(0x0b9f_0002)) + + invoke(&invoked_instruction, accounts)?; + } + TEST_PPROGRAM_NOT_EXECUTABLE => { + info!("Test program not executable"); + let instruction = create_instruction( + *accounts[ARGUMENT_INDEX].key, + &[(accounts[ARGUMENT_INDEX].key, true, true)], + vec![TEST_RETURN_ERROR], ); + invoke(&instruction, accounts)?; } _ => panic!(), } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 20bfd81e11..d68f0aa195 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -433,6 +433,7 @@ fn test_program_bpf_invoke() { const TEST_SUCCESS: u8 = 1; const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2; const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; + const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; let mut programs = Vec::new(); #[cfg(feature = "bpf_c")] @@ -593,6 +594,35 @@ fn test_program_bpf_invoke() { TransactionError::InstructionError(0, InstructionError::Custom(194969602)) ); + let instruction = Instruction::new( + invoke_program_id, + &[TEST_PPROGRAM_NOT_EXECUTABLE, nonce1, nonce2, nonce3], + 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 = inner_instructions[0] + .iter() + .map(|ix| message.account_keys[ix.program_id_index as usize].clone()) + .collect(); + assert_eq!(invoked_programs, vec![argument_keypair.pubkey().clone()]); + assert_eq!( + result.unwrap_err(), + TransactionError::InstructionError(0, InstructionError::AccountNotExecutable) + ); + + // Check final state + assert_eq!(43, bank.get_balance(&derived_key1)); let account = bank.get_account(&derived_key1).unwrap(); assert_eq!(invoke_program_id, account.owner); @@ -630,12 +660,12 @@ fn assert_instruction_count() { ("solana_bpf_rust_128bit", 543), ("solana_bpf_rust_alloc", 19082), ("solana_bpf_rust_dep_crate", 2), - ("solana_bpf_rust_external_spend", 477), + ("solana_bpf_rust_external_spend", 485), ("solana_bpf_rust_iter", 723), ("solana_bpf_rust_many_args", 231), - ("solana_bpf_rust_noop", 451), + ("solana_bpf_rust_noop", 459), ("solana_bpf_rust_param_passing", 54), - ("solana_bpf_rust_sanity", 2215), + ("solana_bpf_rust_sanity", 2223), ]); } diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 827b628504..8feb1c3ae1 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -1037,6 +1037,9 @@ fn call<'a>( // Process instruction let program_account = (*accounts[callee_program_id_index]).clone(); + if !program_account.borrow().executable { + return Err(SyscallError::InstructionError(InstructionError::AccountNotExecutable).into()); + } let executable_accounts = vec![(callee_program_id, program_account)]; let mut message_processor = MessageProcessor::default(); for (program_id, process_instruction) in invoke_context.get_programs().iter() { diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 7aae214cb4..64d657a72f 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -463,7 +463,6 @@ impl MessageProcessor { }) .collect(); keyed_accounts.append(&mut keyed_accounts2); - assert!(keyed_accounts[0].executable()?, "account not executable"); Ok(keyed_accounts) }