From e1a4251b0790d0cf5de8b49ea76d5ddcfb3a22ef Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 9 Dec 2020 02:14:53 -0800 Subject: [PATCH] Cap CPI signers (#14021) --- programs/bpf/build.rs | 18 +++--- programs/bpf/c/src/invoke/invoke.c | 85 +++++++++++++++++++++-------- programs/bpf/rust/invoke/src/lib.rs | 28 ++++++++++ programs/bpf/tests/programs.rs | 41 +++++++++++++- programs/bpf_loader/src/syscalls.rs | 11 ++++ 5 files changed, 147 insertions(+), 36 deletions(-) diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 7c5068b9ee..f284d589f7 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -37,17 +37,17 @@ fn rerun_if_changed(files: &[&str], directories: &[&str], excludes: &[&str]) { fn main() { let bpf_c = env::var("CARGO_FEATURE_BPF_C").is_ok(); if bpf_c { - let install_dir = - "OUT_DIR=../target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); + let install_dir = + "OUT_DIR=../target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); println!("cargo:warning=(not a warning) Building C-based BPF programs"); - assert!(Command::new("make") - .current_dir("c") - .arg("programs") - .arg(&install_dir) - .status() - .expect("Failed to build C-based BPF programs") - .success()); + assert!(Command::new("make") + .current_dir("c") + .arg("programs") + .arg(&install_dir) + .status() + .expect("Failed to build C-based BPF programs") + .success()); rerun_if_changed(&["c/makefile"], &["c/src", "../../sdk"], &["/target/"]); } diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index 9062477cd8..33e646d4db 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -10,6 +10,7 @@ static const uint8_t TEST_PRIVILEGE_ESCALATION_WRITABLE = 3; static const uint8_t TEST_PPROGRAM_NOT_EXECUTABLE = 4; 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 int MINT_INDEX = 0; static const int ARGUMENT_INDEX = 1; @@ -285,30 +286,66 @@ extern uint64_t entrypoint(const uint8_t *input) { } case TEST_CAP_SEEDS: { sol_log("Test cap seeds"); - { - SolAccountMeta arguments[] = {}; - uint8_t data[] = {}; - const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, - arguments, SOL_ARRAY_SIZE(arguments), - data, SOL_ARRAY_SIZE(data)}; - uint8_t seed[] = {"seed"}; - const SolSignerSeed seeds[] = { - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, - {seed, SOL_ARRAY_SIZE(seed)}, - }; - const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}}; - sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, - SOL_ARRAY_SIZE(accounts), - signers_seeds, - SOL_ARRAY_SIZE(signers_seeds))); - } + SolAccountMeta arguments[] = {}; + uint8_t data[] = {}; + const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, + arguments, SOL_ARRAY_SIZE(arguments), + data, SOL_ARRAY_SIZE(data)}; + uint8_t seed[] = {"seed"}; + const SolSignerSeed seeds[] = { + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, {seed, SOL_ARRAY_SIZE(seed)}, + {seed, SOL_ARRAY_SIZE(seed)}, + }; + const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}}; + sol_assert(SUCCESS == sol_invoke_signed( + &instruction, accounts, SOL_ARRAY_SIZE(accounts), + signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); + } + case TEST_CAP_SIGNERS: { + sol_log("Test cap signers"); + SolAccountMeta arguments[] = {}; + uint8_t data[] = {}; + const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, + arguments, SOL_ARRAY_SIZE(arguments), + data, SOL_ARRAY_SIZE(data)}; + uint8_t seed[] = {"seed"}; + const SolSignerSeed seed1[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed2[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed3[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed4[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed5[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed6[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed7[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed8[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed9[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed10[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed11[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed12[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed13[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed14[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed15[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed16[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeed seed17[] = {{seed, SOL_ARRAY_SIZE(seed)}}; + const SolSignerSeeds signers_seeds[] = { + {seed1, SOL_ARRAY_SIZE(seed1)}, {seed2, SOL_ARRAY_SIZE(seed2)}, + {seed3, SOL_ARRAY_SIZE(seed3)}, {seed4, SOL_ARRAY_SIZE(seed4)}, + {seed5, SOL_ARRAY_SIZE(seed5)}, {seed6, SOL_ARRAY_SIZE(seed6)}, + {seed7, SOL_ARRAY_SIZE(seed7)}, {seed8, SOL_ARRAY_SIZE(seed8)}, + {seed9, SOL_ARRAY_SIZE(seed9)}, {seed10, SOL_ARRAY_SIZE(seed10)}, + {seed11, SOL_ARRAY_SIZE(seed11)}, {seed12, SOL_ARRAY_SIZE(seed12)}, + {seed13, SOL_ARRAY_SIZE(seed13)}, {seed14, SOL_ARRAY_SIZE(seed14)}, + {seed15, SOL_ARRAY_SIZE(seed15)}, {seed16, SOL_ARRAY_SIZE(seed16)}, + {seed17, SOL_ARRAY_SIZE(seed17)}}; + sol_assert(SUCCESS == sol_invoke_signed( + &instruction, accounts, SOL_ARRAY_SIZE(accounts), + signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); } default: sol_panic(); diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index 5a81b09b43..eae04bc495 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -22,6 +22,7 @@ const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3; const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5; const TEST_CAP_SEEDS: u8 = 6; +const TEST_CAP_SIGNERS: u8 = 7; // const MINT_INDEX: usize = 0; const ARGUMENT_INDEX: usize = 1; @@ -385,6 +386,33 @@ fn process_instruction( ]], )?; } + TEST_CAP_SIGNERS => { + msg!("Test program max signers"); + let instruction = create_instruction(*accounts[INVOKED_PROGRAM_INDEX].key, &[], vec![]); + invoke_signed( + &instruction, + accounts, + &[ + &[b"1"], + &[b"2"], + &[b"3"], + &[b"4"], + &[b"5"], + &[b"6"], + &[b"7"], + &[b"8"], + &[b"9"], + &[b"0"], + &[b"1"], + &[b"2"], + &[b"3"], + &[b"4"], + &[b"5"], + &[b"6"], + &[b"7"], + ], + )?; + } _ => panic!(), } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index f4a913f127..4f0ccb1b73 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -24,12 +24,10 @@ use solana_sdk::{ entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, keyed_account::KeyedAccount, - loader_instruction, message::Message, - process_instruction::{BpfComputeBudget, InvokeContext, MockInvokeContext}, + process_instruction::{InvokeContext, MockInvokeContext}, pubkey::Pubkey, signature::{Keypair, Signer}, - system_instruction, sysvar::{clock, fees, rent, slot_hashes, stake_history}, transaction::{Transaction, TransactionError}, }; @@ -71,6 +69,7 @@ fn read_bpf_program(name: &str) -> Vec { elf } +#[cfg(feature = "bpf_rust")] fn write_bpf_program( bank_client: &BankClient, loader_id: &Pubkey, @@ -78,6 +77,8 @@ fn write_bpf_program( program_keypair: &Keypair, elf: &[u8], ) { + use solana_sdk::loader_instruction; + let chunk_size = 256; // Size of chunk just needs to fit into tx let mut offset = 0; for chunk in elf.chunks(chunk_size) { @@ -522,6 +523,7 @@ fn test_program_bpf_invoke() { const TEST_PPROGRAM_NOT_EXECUTABLE: u8 = 4; const TEST_EMPTY_ACCOUNTS_SLICE: u8 = 5; const TEST_CAP_SEEDS: u8 = 6; + const TEST_CAP_SIGNERS: u8 = 7; #[allow(dead_code)] #[derive(Debug)] @@ -823,6 +825,33 @@ fn test_program_bpf_invoke() { TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded) ); + let instruction = Instruction::new( + invoke_program_id, + &[TEST_CAP_SIGNERS, 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 = 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::Custom(194969602)) + ); + // Check final state assert_eq!(43, bank.get_balance(&derived_key1)); @@ -986,6 +1015,8 @@ fn test_program_bpf_ro_modify() { #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_call_depth() { + use solana_sdk::process_instruction::BpfComputeBudget; + solana_logger::setup(); println!("Test program: solana_bpf_rust_call_depth"); @@ -1139,6 +1170,8 @@ fn test_program_bpf_instruction_introspection() { #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_test_use_latest_executor() { + use solana_sdk::{loader_instruction, system_instruction}; + solana_logger::setup(); let GenesisConfigInfo { @@ -1229,6 +1262,8 @@ fn test_program_bpf_test_use_latest_executor() { #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_test_use_latest_executor2() { + use solana_sdk::{loader_instruction, system_instruction}; + solana_logger::setup(); let GenesisConfigInfo { diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index bbf1ff1e68..a4fbe850d2 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -37,6 +37,9 @@ use std::{ }; use thiserror::Error as ThisError; +/// Maximum signers +pub const MAX_SIGNERS: usize = 16; + /// Error definitions #[derive(Debug, ThisError, PartialEq)] pub enum SyscallError { @@ -60,6 +63,8 @@ pub enum SyscallError { PrivilegeEscalation, #[error("Unaligned pointer")] UnalignedPointer, + #[error("Too many signers")] + TooManySigners, } impl From for EbpfError { fn from(error: SyscallError) -> Self { @@ -923,6 +928,9 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { signers_seeds_len, self.loader_id, )?; + if signers_seeds.len() > MAX_SIGNERS { + return Err(SyscallError::TooManySigners.into()); + } for signer_seeds in signers_seeds.iter() { let untranslated_seeds = translate_slice::<&[u8]>( memory_mapping, @@ -1178,6 +1186,9 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { signers_seeds_len, self.loader_id, )?; + if signers_seeds.len() > MAX_SIGNERS { + return Err(SyscallError::TooManySigners.into()); + } Ok(signers_seeds .iter() .map(|signer_seeds| {