From 77ea8b9b3eaf678290b6cdf4d7c6897670216f3b Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Tue, 16 Jul 2019 10:45:32 -0600 Subject: [PATCH] Add LoaderInstruction::InvokeMain (#5116) * Remove unreachable, untested runtime check * tx_data -> ix_data * Add LoaderInstruction::InvokeMain * Add test and allow loaders to be registered statically. * Fix clippy error --- programs/bpf_loader_api/src/lib.rs | 83 +++++++++++++++------------- runtime/src/message_processor.rs | 78 ++++++++++++++++++++++++--- runtime/src/native_loader.rs | 87 ++++++++++++++---------------- sdk/src/loader_instruction.rs | 5 ++ 4 files changed, 163 insertions(+), 90 deletions(-) diff --git a/programs/bpf_loader_api/src/lib.rs b/programs/bpf_loader_api/src/lib.rs index 033a24a42e..07317d70a8 100644 --- a/programs/bpf_loader_api/src/lib.rs +++ b/programs/bpf_loader_api/src/lib.rs @@ -85,47 +85,17 @@ fn deserialize_parameters(keyed_accounts: &mut [KeyedAccount], buffer: &[u8]) { pub fn process_instruction( program_id: &Pubkey, keyed_accounts: &mut [KeyedAccount], - tx_data: &[u8], + ix_data: &[u8], ) -> Result<(), InstructionError> { solana_logger::setup(); - if keyed_accounts[0].account.executable { - let (progs, params) = keyed_accounts.split_at_mut(1); - let prog = &progs[0].account.data; - info!("Call BPF program"); - let (mut vm, heap_region) = match create_vm(prog) { - Ok(info) => info, - Err(e) => { - warn!("Failed to create BPF VM: {}", e); - return Err(InstructionError::GenericError); - } - }; - let mut v = serialize_parameters(program_id, params, &tx_data); - - match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) { - Ok(status) => { - if 0 == status { - warn!("BPF program failed: {}", status); - return Err(InstructionError::GenericError); - } - } - Err(e) => { - warn!("BPF VM failed to run program: {}", e); - return Err(InstructionError::GenericError); - } - } - deserialize_parameters(params, &v); - info!( - "BPF program executed {} instructions", - vm.get_last_instruction_count() - ); - } else if let Ok(instruction) = bincode::deserialize(tx_data) { - if keyed_accounts[0].signer_key().is_none() { - warn!("key[0] did not sign the transaction"); - return Err(InstructionError::GenericError); - } + if let Ok(instruction) = bincode::deserialize(ix_data) { match instruction { LoaderInstruction::Write { offset, bytes } => { + if keyed_accounts[0].signer_key().is_none() { + warn!("key[0] did not sign the transaction"); + return Err(InstructionError::GenericError); + } let offset = offset as usize; let len = bytes.len(); debug!("Write: offset={} length={}", offset, len); @@ -140,15 +110,54 @@ pub fn process_instruction( keyed_accounts[0].account.data[offset..offset + len].copy_from_slice(&bytes); } LoaderInstruction::Finalize => { + if keyed_accounts[0].signer_key().is_none() { + warn!("key[0] did not sign the transaction"); + return Err(InstructionError::GenericError); + } keyed_accounts[0].account.executable = true; info!( "Finalize: account {:?}", keyed_accounts[0].signer_key().unwrap() ); } + LoaderInstruction::InvokeMain { data } => { + if !keyed_accounts[0].account.executable { + warn!("BPF account not executable"); + return Err(InstructionError::GenericError); + } + let (progs, params) = keyed_accounts.split_at_mut(1); + let prog = &progs[0].account.data; + info!("Call BPF program"); + let (mut vm, heap_region) = match create_vm(prog) { + Ok(info) => info, + Err(e) => { + warn!("Failed to create BPF VM: {}", e); + return Err(InstructionError::GenericError); + } + }; + let mut v = serialize_parameters(program_id, params, &data); + + match vm.execute_program(v.as_mut_slice(), &[], &[heap_region]) { + Ok(status) => { + if 0 == status { + warn!("BPF program failed: {}", status); + return Err(InstructionError::GenericError); + } + } + Err(e) => { + warn!("BPF VM failed to run program: {}", e); + return Err(InstructionError::GenericError); + } + } + deserialize_parameters(params, &v); + info!( + "BPF program executed {} instructions", + vm.get_last_instruction_count() + ); + } } } else { - warn!("Invalid program transaction: {:?}", tx_data); + warn!("Invalid instruction data: {:?}", ix_data); return Err(InstructionError::GenericError); } Ok(()) diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index bb9a7c65eb..6ac903ca03 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -6,11 +6,13 @@ use solana_sdk::account::{ }; use solana_sdk::instruction::{CompiledInstruction, InstructionError}; use solana_sdk::instruction_processor_utils; +use solana_sdk::loader_instruction::LoaderInstruction; use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::system_program; use solana_sdk::transaction::TransactionError; use std::collections::HashMap; +use std::io::Write; use std::sync::RwLock; #[cfg(unix)] @@ -92,6 +94,27 @@ fn verify_instruction( Ok(()) } +/// Return instruction data to pass to process_instruction(). +/// When a loader is detected, the instruction data is wrapped with a LoaderInstruction +/// to signal to the loader that the instruction data should be used as arguments when +/// invoking a "main()" function. +fn get_loader_instruction_data<'a>( + loaders: &[(Pubkey, Account)], + ix_data: &'a [u8], + loader_ix_data: &'a mut Vec, +) -> &'a [u8] { + if loaders.len() > 1 { + let ix = LoaderInstruction::InvokeMain { + data: ix_data.to_vec(), + }; + let ix_data = bincode::serialize(&ix).unwrap(); + loader_ix_data.write_all(&ix_data).unwrap(); + loader_ix_data + } else { + ix_data + } +} + pub type ProcessInstruction = fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), InstructionError>; @@ -141,6 +164,13 @@ impl MessageProcessor { ) -> Result<(), InstructionError> { let program_id = instruction.program_id(&message.account_keys); + let mut loader_ix_data = vec![]; + let ix_data = get_loader_instruction_data( + executable_accounts, + &instruction.data, + &mut loader_ix_data, + ); + let mut keyed_accounts = create_keyed_credit_only_accounts(executable_accounts); let mut keyed_accounts2: Vec<_> = instruction .accounts @@ -168,18 +198,19 @@ impl MessageProcessor { for (id, process_instruction) in &self.instruction_processors { if id == program_id { - return process_instruction( - &program_id, - &mut keyed_accounts[1..], - &instruction.data, - ); + return process_instruction(&program_id, &mut keyed_accounts[1..], &ix_data); } } - native_loader::entrypoint( + assert!( + keyed_accounts[0].account.executable, + "loader not executable" + ); + + native_loader::invoke_entrypoint( &program_id, &mut keyed_accounts, - &instruction.data, + ix_data, &self.symbol_cache, ) } @@ -523,4 +554,37 @@ mod tests { )) ); } + + #[test] + fn test_get_loader_instruction_data() { + // First ensure the ix_data is unaffected if not invoking via a loader. + let ix_data = [1]; + let mut loader_ix_data = vec![]; + + let native_pubkey = Pubkey::new_rand(); + let native_loader = (native_pubkey, Account::new(0, 0, &native_pubkey)); + assert_eq!( + get_loader_instruction_data(&[native_loader.clone()], &ix_data, &mut loader_ix_data), + &ix_data + ); + + // Now ensure the ix_data is wrapped when there's a loader present. + let acme_pubkey = Pubkey::new_rand(); + let acme_loader = (acme_pubkey, Account::new(0, 0, &native_pubkey)); + let expected_ix = LoaderInstruction::InvokeMain { + data: ix_data.to_vec(), + }; + let expected_ix_data = bincode::serialize(&expected_ix).unwrap(); + assert_eq!( + get_loader_instruction_data( + &[native_loader.clone(), acme_loader.clone()], + &ix_data, + &mut loader_ix_data + ), + &expected_ix_data[..] + ); + + // Note there was an allocation in the input vector. + assert_eq!(loader_ix_data, expected_ix_data); + } } diff --git a/runtime/src/native_loader.rs b/runtime/src/native_loader.rs index 63a988cd73..e288a8bc51 100644 --- a/runtime/src/native_loader.rs +++ b/runtime/src/native_loader.rs @@ -64,58 +64,53 @@ fn library_open(path: &PathBuf) -> std::io::Result { Library::open(Some(path), libc::RTLD_NODELETE | libc::RTLD_NOW) } -pub fn entrypoint( +pub fn invoke_entrypoint( program_id: &Pubkey, keyed_accounts: &mut [KeyedAccount], ix_data: &[u8], symbol_cache: &SymbolCache, ) -> Result<(), InstructionError> { - if keyed_accounts[0].account.executable { - // dispatch it - let (names, params) = keyed_accounts.split_at_mut(1); - let name_vec = &names[0].account.data; - if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) { - unsafe { - return entrypoint(program_id, params, ix_data); - } + // dispatch it + let (names, params) = keyed_accounts.split_at_mut(1); + let name_vec = &names[0].account.data; + if let Some(entrypoint) = symbol_cache.read().unwrap().get(name_vec) { + unsafe { + return entrypoint(program_id, params, ix_data); } - let name = match str::from_utf8(name_vec) { - Ok(v) => v, - Err(e) => { - warn!("Invalid UTF-8 sequence: {}", e); - return Err(InstructionError::GenericError); - } - }; - trace!("Call native {:?}", name); - let path = create_path(&name); - match library_open(&path) { - Ok(library) => unsafe { - let entrypoint: Symbol = - match library.get(instruction_processor_utils::ENTRYPOINT.as_bytes()) { - Ok(s) => s, - Err(e) => { - warn!( - "{:?}: Unable to find {:?} in program", - e, - instruction_processor_utils::ENTRYPOINT - ); - return Err(InstructionError::GenericError); - } - }; - let ret = entrypoint(program_id, params, ix_data); - symbol_cache - .write() - .unwrap() - .insert(name_vec.to_vec(), entrypoint); - ret - }, - Err(e) => { - warn!("Unable to load: {:?}", e); - Err(InstructionError::GenericError) - } + } + let name = match str::from_utf8(name_vec) { + Ok(v) => v, + Err(e) => { + warn!("Invalid UTF-8 sequence: {}", e); + return Err(InstructionError::GenericError); + } + }; + trace!("Call native {:?}", name); + let path = create_path(&name); + match library_open(&path) { + Ok(library) => unsafe { + let entrypoint: Symbol = + match library.get(instruction_processor_utils::ENTRYPOINT.as_bytes()) { + Ok(s) => s, + Err(e) => { + warn!( + "{:?}: Unable to find {:?} in program", + e, + instruction_processor_utils::ENTRYPOINT + ); + return Err(InstructionError::GenericError); + } + }; + let ret = entrypoint(program_id, params, ix_data); + symbol_cache + .write() + .unwrap() + .insert(name_vec.to_vec(), entrypoint); + ret + }, + Err(e) => { + warn!("Unable to load: {:?}", e); + Err(InstructionError::GenericError) } - } else { - warn!("Invalid data in instruction: {:?}", ix_data); - Err(InstructionError::GenericError) } } diff --git a/sdk/src/loader_instruction.rs b/sdk/src/loader_instruction.rs index 4bbc946709..77b0c49daa 100644 --- a/sdk/src/loader_instruction.rs +++ b/sdk/src/loader_instruction.rs @@ -18,6 +18,11 @@ pub enum LoaderInstruction { /// /// The transaction must be signed by key[0] Finalize, + + /// Invoke the "main" entrypoint with the given data. + /// + /// * key[0] - an executable account + InvokeMain { data: Vec }, } pub fn write(