diff --git a/Cargo.lock b/Cargo.lock index 5d32aa7905..5cab51ada0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4492,6 +4492,7 @@ dependencies = [ "rustversion", "sha3", "solana-measure", + "solana-program-runtime", "solana-runtime", "solana-sdk", "solana_rbpf", @@ -4735,6 +4736,7 @@ dependencies = [ "solana-net-utils", "solana-perf", "solana-poh", + "solana-program-runtime", "solana-program-test", "solana-rayon-threadlimit", "solana-rpc", @@ -5440,6 +5442,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-program-runtime" +version = "1.8.0" +dependencies = [ + "libc", + "libloading", + "log 0.4.14", + "num-derive", + "num-traits", + "rustc_version 0.4.0", + "serde", + "solana-frozen-abi 1.8.0", + "solana-frozen-abi-macro 1.8.0", + "solana-logger 1.8.0", + "solana-sdk", + "thiserror", +] + [[package]] name = "solana-program-test" version = "1.8.0" @@ -5458,6 +5478,7 @@ dependencies = [ "solana-banks-server", "solana-bpf-loader-program", "solana-logger 1.8.0", + "solana-program-runtime", "solana-runtime", "solana-sdk", "solana-vote-program", @@ -5600,8 +5621,6 @@ dependencies = [ "fnv", "itertools 0.10.1", "lazy_static", - "libc", - "libloading", "log 0.4.14", "memmap2 0.3.1", "num-derive", @@ -5622,6 +5641,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-noop-program", + "solana-program-runtime", "solana-rayon-threadlimit", "solana-sdk", "solana-secp256k1-program", diff --git a/core/Cargo.toml b/core/Cargo.toml index 90d63b63d5..829e7c9953 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -83,6 +83,7 @@ num_cpus = "1.13.0" reqwest = { version = "0.11.4", default-features = false, features = ["blocking", "rustls-tls", "json"] } serde_json = "1.0.66" serial_test = "0.5.1" +solana-program-runtime = { path = "../program-runtime", version = "=1.8.0" } solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } static_assertions = "1.1.0" diff --git a/core/src/cost_update_service.rs b/core/src/cost_update_service.rs index 96e002c5df..6f367867c3 100644 --- a/core/src/cost_update_service.rs +++ b/core/src/cost_update_service.rs @@ -191,7 +191,7 @@ impl CostUpdateService { #[cfg(test)] mod tests { use super::*; - use solana_runtime::message_processor::ProgramTiming; + use solana_program_runtime::ProgramTiming; use solana_sdk::pubkey::Pubkey; #[test] diff --git a/program-runtime/Cargo.toml b/program-runtime/Cargo.toml new file mode 100644 index 0000000000..aff0bad571 --- /dev/null +++ b/program-runtime/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "solana-program-runtime" +version = "1.8.0" +description = "Solana program runtime" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-program-runtime" +edition = "2018" + +[dependencies] +libc = "0.2.101" +libloading = "0.7.0" +log = "0.4.14" +num-derive = { version = "0.3" } +num-traits = { version = "0.2" } +serde = { version = "1.0.129", features = ["derive", "rc"] } +solana-frozen-abi = { path = "../frozen-abi", version = "=1.8.0" } +solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.8.0" } +solana-logger = { path = "../logger", version = "=1.8.0" } +solana-sdk = { path = "../sdk", version = "=1.8.0" } +thiserror = "1.0" + +[lib] +crate-type = ["lib"] +name = "solana_program_runtime" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +rustc_version = "0.4" diff --git a/runtime/benches/message_processor.rs b/program-runtime/benches/instruction_processor.rs similarity index 97% rename from runtime/benches/message_processor.rs rename to program-runtime/benches/instruction_processor.rs index 1a3665607f..5de050e806 100644 --- a/runtime/benches/message_processor.rs +++ b/program-runtime/benches/instruction_processor.rs @@ -3,7 +3,7 @@ extern crate test; use log::*; -use solana_runtime::message_processor::{ExecuteDetailsTimings, PreAccount}; +use solana_program_runtime::{ExecuteDetailsTimings, PreAccount}; use solana_sdk::{account::AccountSharedData, pubkey, rent::Rent}; use test::Bencher; diff --git a/program-runtime/build.rs b/program-runtime/build.rs new file mode 120000 index 0000000000..ae66c237c5 --- /dev/null +++ b/program-runtime/build.rs @@ -0,0 +1 @@ +../frozen-abi/build.rs \ No newline at end of file diff --git a/program-runtime/src/instruction_processor.rs b/program-runtime/src/instruction_processor.rs new file mode 100644 index 0000000000..7b5ed32b57 --- /dev/null +++ b/program-runtime/src/instruction_processor.rs @@ -0,0 +1,1229 @@ +use crate::native_loader::NativeLoader; +use serde::{Deserialize, Serialize}; +use solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + account_utils::StateMut, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + ic_logger_msg, ic_msg, + instruction::{CompiledInstruction, Instruction, InstructionError}, + keyed_account::{keyed_account_at_index, KeyedAccount}, + message::Message, + process_instruction::{Executor, InvokeContext, Logger, ProcessInstructionWithContext}, + pubkey::Pubkey, + rent::Rent, + system_program, +}; +use std::{ + cell::{Ref, RefCell}, + collections::HashMap, + rc::Rc, + sync::Arc, +}; + +pub struct Executors { + pub executors: HashMap>, + pub is_dirty: bool, +} +impl Default for Executors { + fn default() -> Self { + Self { + executors: HashMap::default(), + is_dirty: false, + } + } +} +impl Executors { + pub fn insert(&mut self, key: Pubkey, executor: Arc) { + let _ = self.executors.insert(key, executor); + self.is_dirty = true; + } + pub fn get(&self, key: &Pubkey) -> Option> { + self.executors.get(key).cloned() + } +} + +#[derive(Default, Debug)] +pub struct ProgramTiming { + pub accumulated_us: u64, + pub accumulated_units: u64, + pub count: u32, +} + +#[derive(Default, Debug)] +pub struct ExecuteDetailsTimings { + pub serialize_us: u64, + pub create_vm_us: u64, + pub execute_us: u64, + pub deserialize_us: u64, + pub changed_account_count: u64, + pub total_account_count: u64, + pub total_data_size: usize, + pub data_size_changed: usize, + pub per_program_timings: HashMap, +} +impl ExecuteDetailsTimings { + pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) { + self.serialize_us += other.serialize_us; + self.create_vm_us += other.create_vm_us; + self.execute_us += other.execute_us; + self.deserialize_us += other.deserialize_us; + self.changed_account_count += other.changed_account_count; + self.total_account_count += other.total_account_count; + self.total_data_size += other.total_data_size; + self.data_size_changed += other.data_size_changed; + for (id, other) in &other.per_program_timings { + let program_timing = self.per_program_timings.entry(*id).or_default(); + program_timing.accumulated_us = program_timing + .accumulated_us + .saturating_add(other.accumulated_us); + program_timing.accumulated_units = program_timing + .accumulated_units + .saturating_add(other.accumulated_units); + program_timing.count = program_timing.count.saturating_add(other.count); + } + } + pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) { + let program_timing = self.per_program_timings.entry(*program_id).or_default(); + program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us); + program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units); + program_timing.count = program_timing.count.saturating_add(1); + } +} + +// The relevant state of an account before an Instruction executes, used +// to verify account integrity after the Instruction completes +#[derive(Clone, Debug, Default)] +pub struct PreAccount { + key: Pubkey, + account: Rc>, + changed: bool, +} +impl PreAccount { + pub fn new(key: &Pubkey, account: &AccountSharedData) -> Self { + Self { + key: *key, + account: Rc::new(RefCell::new(account.clone())), + changed: false, + } + } + + pub fn verify( + &self, + program_id: &Pubkey, + is_writable: bool, + rent: &Rent, + post: &AccountSharedData, + timings: &mut ExecuteDetailsTimings, + outermost_call: bool, + updated_verify_policy: bool, + ) -> Result<(), InstructionError> { + let pre = self.account.borrow(); + + // Only the owner of the account may change owner and + // only if the account is writable and + // only if the account is not executable and + // only if the data is zero-initialized or empty + let owner_changed = pre.owner() != post.owner(); + if owner_changed + && (!is_writable // line coverage used to get branch coverage + || pre.executable() + || program_id != pre.owner() + || !Self::is_zeroed(post.data())) + { + return Err(InstructionError::ModifiedProgramId); + } + + // An account not assigned to the program cannot have its balance decrease. + if program_id != pre.owner() // line coverage used to get branch coverage + && pre.lamports() > post.lamports() + { + return Err(InstructionError::ExternalAccountLamportSpend); + } + + // The balance of read-only and executable accounts may not change + let lamports_changed = pre.lamports() != post.lamports(); + if lamports_changed { + if !is_writable { + return Err(InstructionError::ReadonlyLamportChange); + } + if pre.executable() { + return Err(InstructionError::ExecutableLamportChange); + } + } + + // Only the system program can change the size of the data + // and only if the system program owns the account + let data_len_changed = pre.data().len() != post.data().len(); + if data_len_changed + && (!system_program::check_id(program_id) // line coverage used to get branch coverage + || !system_program::check_id(pre.owner())) + { + return Err(InstructionError::AccountDataSizeChanged); + } + + // Only the owner may change account data + // and if the account is writable + // and if the account is not executable + if !(program_id == pre.owner() + && is_writable // line coverage used to get branch coverage + && !pre.executable()) + && pre.data() != post.data() + { + if pre.executable() { + return Err(InstructionError::ExecutableDataModified); + } else if is_writable { + return Err(InstructionError::ExternalAccountDataModified); + } else { + return Err(InstructionError::ReadonlyDataModified); + } + } + + // executable is one-way (false->true) and only the account owner may set it. + let executable_changed = pre.executable() != post.executable(); + if executable_changed { + if !rent.is_exempt(post.lamports(), post.data().len()) { + return Err(InstructionError::ExecutableAccountNotRentExempt); + } + let owner = if updated_verify_policy { + post.owner() + } else { + pre.owner() + }; + if !is_writable // line coverage used to get branch coverage + || pre.executable() + || program_id != owner + { + return Err(InstructionError::ExecutableModified); + } + } + + // No one modifies rent_epoch (yet). + let rent_epoch_changed = pre.rent_epoch() != post.rent_epoch(); + if rent_epoch_changed { + return Err(InstructionError::RentEpochModified); + } + + if outermost_call { + timings.total_account_count += 1; + timings.total_data_size += post.data().len(); + if owner_changed + || lamports_changed + || data_len_changed + || executable_changed + || rent_epoch_changed + || self.changed + { + timings.changed_account_count += 1; + timings.data_size_changed += post.data().len(); + } + } + + Ok(()) + } + + pub fn update(&mut self, account: &AccountSharedData) { + let mut pre = self.account.borrow_mut(); + let rent_epoch = pre.rent_epoch(); + *pre = account.clone(); + pre.set_rent_epoch(rent_epoch); + + self.changed = true; + } + + pub fn key(&self) -> &Pubkey { + &self.key + } + + pub fn data(&self) -> Ref<[u8]> { + Ref::map(self.account.borrow(), |account| account.data()) + } + + pub fn lamports(&self) -> u64 { + self.account.borrow().lamports() + } + + pub fn executable(&self) -> bool { + self.account.borrow().executable() + } + + pub fn is_zeroed(buf: &[u8]) -> bool { + const ZEROS_LEN: usize = 1024; + static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN]; + let mut chunks = buf.chunks_exact(ZEROS_LEN); + + chunks.all(|chunk| chunk == &ZEROS[..]) + && chunks.remainder() == &ZEROS[..chunks.remainder().len()] + } +} + +#[derive(Deserialize, Serialize)] +pub struct InstructionProcessor { + #[serde(skip)] + programs: Vec<(Pubkey, ProcessInstructionWithContext)>, + #[serde(skip)] + native_loader: NativeLoader, +} + +impl std::fmt::Debug for InstructionProcessor { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + #[derive(Debug)] + struct MessageProcessor<'a> { + programs: Vec, + native_loader: &'a NativeLoader, + } + + // These are just type aliases for work around of Debug-ing above pointers + type ErasedProcessInstructionWithContext = fn( + &'static Pubkey, + &'static [u8], + &'static mut dyn InvokeContext, + ) -> Result<(), InstructionError>; + + // rustc doesn't compile due to bug without this work around + // https://github.com/rust-lang/rust/issues/50280 + // https://users.rust-lang.org/t/display-function-pointer/17073/2 + let processor = MessageProcessor { + programs: self + .programs + .iter() + .map(|(pubkey, instruction)| { + let erased_instruction: ErasedProcessInstructionWithContext = *instruction; + format!("{}: {:p}", pubkey, erased_instruction) + }) + .collect::>(), + native_loader: &self.native_loader, + }; + + write!(f, "{:?}", processor) + } +} + +impl Default for InstructionProcessor { + fn default() -> Self { + Self { + programs: vec![], + native_loader: NativeLoader::default(), + } + } +} +impl Clone for InstructionProcessor { + fn clone(&self) -> Self { + InstructionProcessor { + programs: self.programs.clone(), + native_loader: NativeLoader::default(), + } + } +} + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::AbiExample for InstructionProcessor { + fn example() -> Self { + // MessageProcessor's fields are #[serde(skip)]-ed and not Serialize + // so, just rely on Default anyway. + InstructionProcessor::default() + } +} + +impl InstructionProcessor { + pub fn programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] { + &self.programs + } + + /// Add a static entrypoint to intercept instructions before the dynamic loader. + pub fn add_program( + &mut self, + program_id: Pubkey, + process_instruction: ProcessInstructionWithContext, + ) { + match self.programs.iter_mut().find(|(key, _)| program_id == *key) { + Some((_, processor)) => *processor = process_instruction, + None => self.programs.push((program_id, process_instruction)), + } + } + + /// Create the KeyedAccounts that will be passed to the program + pub fn create_keyed_accounts<'a>( + message: &'a Message, + instruction: &'a CompiledInstruction, + executable_accounts: &'a [(Pubkey, Rc>)], + accounts: &'a [(Pubkey, Rc>)], + ) -> Vec<(bool, bool, &'a Pubkey, &'a RefCell)> { + executable_accounts + .iter() + .map(|(key, account)| (false, false, key, account as &RefCell)) + .chain(instruction.accounts.iter().map(|index| { + let index = *index as usize; + ( + message.is_signer(index), + message.is_writable(index), + &accounts[index].0, + &accounts[index].1 as &RefCell, + ) + })) + .collect::>() + } + + /// Process an instruction + /// This method calls the instruction's program entrypoint method + pub fn process_instruction( + &self, + program_id: &Pubkey, + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + if let Some(root_account) = invoke_context.get_keyed_accounts()?.iter().next() { + let root_id = root_account.unsigned_key(); + if solana_sdk::native_loader::check_id(&root_account.owner()?) { + for (id, process_instruction) in &self.programs { + if id == root_id { + invoke_context.remove_first_keyed_account()?; + // Call the builtin program + return process_instruction(program_id, instruction_data, invoke_context); + } + } + // Call the program via the native loader + return self.native_loader.process_instruction( + &solana_sdk::native_loader::id(), + instruction_data, + invoke_context, + ); + } else { + let owner_id = &root_account.owner()?; + for (id, process_instruction) in &self.programs { + if id == owner_id { + // Call the program via a builtin loader + return process_instruction(program_id, instruction_data, invoke_context); + } + } + } + } + Err(InstructionError::UnsupportedProgramId) + } + + pub fn create_message( + instruction: &Instruction, + keyed_accounts: &[&KeyedAccount], + signers: &[Pubkey], + invoke_context: &Ref<&mut dyn InvokeContext>, + ) -> Result<(Message, Pubkey, usize), InstructionError> { + // Check for privilege escalation + for account in instruction.accounts.iter() { + let keyed_account = keyed_accounts + .iter() + .find_map(|keyed_account| { + if &account.pubkey == keyed_account.unsigned_key() { + Some(keyed_account) + } else { + None + } + }) + .ok_or_else(|| { + ic_msg!( + invoke_context, + "Instruction references an unknown account {}", + account.pubkey + ); + InstructionError::MissingAccount + })?; + // Readonly account cannot become writable + if account.is_writable && !keyed_account.is_writable() { + ic_msg!( + invoke_context, + "{}'s writable privilege escalated", + account.pubkey + ); + return Err(InstructionError::PrivilegeEscalation); + } + + if account.is_signer && // If message indicates account is signed + !( // one of the following needs to be true: + keyed_account.signer_key().is_some() // Signed in the parent instruction + || signers.contains(&account.pubkey) // Signed by the program + ) { + ic_msg!( + invoke_context, + "{}'s signer privilege escalated", + account.pubkey + ); + return Err(InstructionError::PrivilegeEscalation); + } + } + + // validate the caller has access to the program account and that it is executable + let program_id = instruction.program_id; + match keyed_accounts + .iter() + .find(|keyed_account| &program_id == keyed_account.unsigned_key()) + { + Some(keyed_account) => { + if !keyed_account.executable()? { + ic_msg!( + invoke_context, + "Account {} is not executable", + keyed_account.unsigned_key() + ); + return Err(InstructionError::AccountNotExecutable); + } + } + None => { + ic_msg!(invoke_context, "Unknown program {}", program_id); + return Err(InstructionError::MissingAccount); + } + } + + let message = Message::new(&[instruction.clone()], None); + let program_id_index = message.instructions[0].program_id_index as usize; + + Ok((message, program_id, program_id_index)) + } + + /// Entrypoint for a cross-program invocation from a native program + pub fn native_invoke( + invoke_context: &mut dyn InvokeContext, + instruction: Instruction, + keyed_account_indices: &[usize], + signers: &[Pubkey], + ) -> Result<(), InstructionError> { + let invoke_context = RefCell::new(invoke_context); + + let ( + message, + executable_accounts, + accounts, + keyed_account_indices_reordered, + caller_write_privileges, + ) = { + let invoke_context = invoke_context.borrow(); + + // Translate and verify caller's data + let keyed_accounts = invoke_context.get_keyed_accounts()?; + let keyed_accounts = keyed_account_indices + .iter() + .map(|index| keyed_account_at_index(keyed_accounts, *index)) + .collect::, InstructionError>>()?; + let (message, callee_program_id, _) = + Self::create_message(&instruction, &keyed_accounts, signers, &invoke_context)?; + let keyed_accounts = invoke_context.get_keyed_accounts()?; + let mut caller_write_privileges = keyed_account_indices + .iter() + .map(|index| keyed_accounts[*index].is_writable()) + .collect::>(); + caller_write_privileges.insert(0, false); + let mut accounts = vec![]; + let mut keyed_account_indices_reordered = vec![]; + let keyed_accounts = invoke_context.get_keyed_accounts()?; + 'root: for account_key in message.account_keys.iter() { + for keyed_account_index in keyed_account_indices { + let keyed_account = &keyed_accounts[*keyed_account_index]; + if account_key == keyed_account.unsigned_key() { + accounts.push((*account_key, Rc::new(keyed_account.account.clone()))); + keyed_account_indices_reordered.push(*keyed_account_index); + continue 'root; + } + } + ic_msg!( + invoke_context, + "Instruction references an unknown account {}", + account_key + ); + return Err(InstructionError::MissingAccount); + } + + // Process instruction + + invoke_context.record_instruction(&instruction); + + let program_account = + invoke_context + .get_account(&callee_program_id) + .ok_or_else(|| { + ic_msg!(invoke_context, "Unknown program {}", callee_program_id); + InstructionError::MissingAccount + })?; + if !program_account.borrow().executable() { + ic_msg!( + invoke_context, + "Account {} is not executable", + callee_program_id + ); + return Err(InstructionError::AccountNotExecutable); + } + let programdata = if program_account.borrow().owner() == &bpf_loader_upgradeable::id() { + if let UpgradeableLoaderState::Program { + programdata_address, + } = program_account.borrow().state()? + { + if let Some(account) = invoke_context.get_account(&programdata_address) { + Some((programdata_address, account)) + } else { + ic_msg!( + invoke_context, + "Unknown upgradeable programdata account {}", + programdata_address, + ); + return Err(InstructionError::MissingAccount); + } + } else { + ic_msg!( + invoke_context, + "Upgradeable program account state not valid {}", + callee_program_id, + ); + return Err(InstructionError::MissingAccount); + } + } else { + None + }; + let mut executable_accounts = vec![(callee_program_id, program_account)]; + if let Some(programdata) = programdata { + executable_accounts.push(programdata); + } + ( + message, + executable_accounts, + accounts, + keyed_account_indices_reordered, + caller_write_privileges, + ) + }; + + #[allow(clippy::deref_addrof)] + InstructionProcessor::process_cross_program_instruction( + &message, + &executable_accounts, + &accounts, + &caller_write_privileges, + *(&mut *(invoke_context.borrow_mut())), + )?; + + // Copy results back to caller + + { + let invoke_context = invoke_context.borrow(); + let keyed_accounts = invoke_context.get_keyed_accounts()?; + for (src_keyed_account_index, ((_key, account), dst_keyed_account_index)) in accounts + .iter() + .zip(keyed_account_indices_reordered) + .enumerate() + { + let dst_keyed_account = &keyed_accounts[dst_keyed_account_index]; + let src_keyed_account = account.borrow(); + if message.is_writable(src_keyed_account_index) && !src_keyed_account.executable() { + if dst_keyed_account.data_len()? != src_keyed_account.data().len() + && dst_keyed_account.data_len()? != 0 + { + // Only support for `CreateAccount` at this time. + // Need a way to limit total realloc size across multiple CPI calls + ic_msg!( + invoke_context, + "Inner instructions do not support realloc, only SystemProgram::CreateAccount", + ); + return Err(InstructionError::InvalidRealloc); + } + dst_keyed_account + .try_account_ref_mut()? + .set_lamports(src_keyed_account.lamports()); + dst_keyed_account + .try_account_ref_mut()? + .set_owner(*src_keyed_account.owner()); + dst_keyed_account + .try_account_ref_mut()? + .set_data(src_keyed_account.data().to_vec()); + } + } + } + + Ok(()) + } + + /// Process a cross-program instruction + /// This method calls the instruction's program entrypoint function + pub fn process_cross_program_instruction( + message: &Message, + executable_accounts: &[(Pubkey, Rc>)], + accounts: &[(Pubkey, Rc>)], + caller_write_privileges: &[bool], + invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + if let Some(instruction) = message.instructions.get(0) { + let program_id = instruction.program_id(&message.account_keys); + + // Verify the calling program hasn't misbehaved + invoke_context.verify_and_update(instruction, accounts, caller_write_privileges)?; + + // Construct keyed accounts + let keyed_accounts = + Self::create_keyed_accounts(message, instruction, executable_accounts, accounts); + + // Invoke callee + invoke_context.push(program_id, &keyed_accounts)?; + + let mut instruction_processor = InstructionProcessor::default(); + for (program_id, process_instruction) in invoke_context.get_programs().iter() { + instruction_processor.add_program(*program_id, *process_instruction); + } + + let mut result = instruction_processor.process_instruction( + program_id, + &instruction.data, + invoke_context, + ); + if result.is_ok() { + // Verify the called program has not misbehaved + let write_privileges: Vec = (0..message.account_keys.len()) + .map(|i| message.is_writable(i)) + .collect(); + result = invoke_context.verify_and_update(instruction, accounts, &write_privileges); + } + + // Restore previous state + invoke_context.pop(); + result + } else { + // This function is always called with a valid instruction, if that changes return an error + Err(InstructionError::GenericError) + } + } + + /// Record the initial state of the accounts so that they can be compared + /// after the instruction is processed + pub fn create_pre_accounts( + message: &Message, + instruction: &CompiledInstruction, + accounts: &[(Pubkey, Rc>)], + ) -> Vec { + let mut pre_accounts = Vec::with_capacity(instruction.accounts.len()); + { + let mut work = |_unique_index: usize, account_index: usize| { + if account_index < message.account_keys.len() && account_index < accounts.len() { + let account = accounts[account_index].1.borrow(); + pre_accounts.push(PreAccount::new(&accounts[account_index].0, &account)); + return Ok(()); + } + Err(InstructionError::MissingAccount) + }; + let _ = instruction.visit_each_account(&mut work); + } + pre_accounts + } + + /// Verify the results of a cross-program instruction + #[allow(clippy::too_many_arguments)] + pub fn verify_and_update( + instruction: &CompiledInstruction, + pre_accounts: &mut [PreAccount], + accounts: &[(Pubkey, Rc>)], + program_id: &Pubkey, + rent: &Rent, + write_privileges: &[bool], + timings: &mut ExecuteDetailsTimings, + logger: Rc>, + updated_verify_policy: bool, + ) -> Result<(), InstructionError> { + // Verify the per-account instruction results + let (mut pre_sum, mut post_sum) = (0_u128, 0_u128); + let mut work = |_unique_index: usize, account_index: usize| { + if account_index < write_privileges.len() && account_index < accounts.len() { + let (key, account) = &accounts[account_index]; + let is_writable = write_privileges[account_index]; + // Find the matching PreAccount + for pre_account in pre_accounts.iter_mut() { + if key == pre_account.key() { + { + // Verify account has no outstanding references + let _ = account + .try_borrow_mut() + .map_err(|_| InstructionError::AccountBorrowOutstanding)?; + } + let account = account.borrow(); + pre_account + .verify( + program_id, + is_writable, + rent, + &account, + timings, + false, + updated_verify_policy, + ) + .map_err(|err| { + ic_logger_msg!(logger, "failed to verify account {}: {}", key, err); + err + })?; + pre_sum += u128::from(pre_account.lamports()); + post_sum += u128::from(account.lamports()); + if is_writable && !pre_account.executable() { + pre_account.update(&account); + } + return Ok(()); + } + } + } + Err(InstructionError::MissingAccount) + }; + instruction.visit_each_account(&mut work)?; + + // Verify that the total sum of all the lamports did not change + if pre_sum != post_sum { + return Err(InstructionError::UnbalancedInstruction); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::{account::Account, instruction::InstructionError}; + + #[test] + fn test_is_zeroed() { + const ZEROS_LEN: usize = 1024; + let mut buf = [0; ZEROS_LEN]; + assert!(PreAccount::is_zeroed(&buf)); + buf[0] = 1; + assert!(!PreAccount::is_zeroed(&buf)); + + let mut buf = [0; ZEROS_LEN - 1]; + assert!(PreAccount::is_zeroed(&buf)); + buf[0] = 1; + assert!(!PreAccount::is_zeroed(&buf)); + + let mut buf = [0; ZEROS_LEN + 1]; + assert!(PreAccount::is_zeroed(&buf)); + buf[0] = 1; + assert!(!PreAccount::is_zeroed(&buf)); + + let buf = vec![]; + assert!(PreAccount::is_zeroed(&buf)); + } + + struct Change { + program_id: Pubkey, + is_writable: bool, + rent: Rent, + pre: PreAccount, + post: AccountSharedData, + } + impl Change { + pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self { + Self { + program_id: *program_id, + rent: Rent::default(), + is_writable: true, + pre: PreAccount::new( + &solana_sdk::pubkey::new_rand(), + &AccountSharedData::from(Account { + owner: *owner, + lamports: std::u64::MAX, + data: vec![], + ..Account::default() + }), + ), + post: AccountSharedData::from(Account { + owner: *owner, + lamports: std::u64::MAX, + ..Account::default() + }), + } + } + pub fn read_only(mut self) -> Self { + self.is_writable = false; + self + } + pub fn executable(mut self, pre: bool, post: bool) -> Self { + self.pre.account.borrow_mut().set_executable(pre); + self.post.set_executable(post); + self + } + pub fn lamports(mut self, pre: u64, post: u64) -> Self { + self.pre.account.borrow_mut().set_lamports(pre); + self.post.set_lamports(post); + self + } + pub fn owner(mut self, post: &Pubkey) -> Self { + self.post.set_owner(*post); + self + } + pub fn data(mut self, pre: Vec, post: Vec) -> Self { + self.pre.account.borrow_mut().set_data(pre); + self.post.set_data(post); + self + } + pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self { + self.pre.account.borrow_mut().set_rent_epoch(pre); + self.post.set_rent_epoch(post); + self + } + pub fn verify(&self) -> Result<(), InstructionError> { + self.pre.verify( + &self.program_id, + self.is_writable, + &self.rent, + &self.post, + &mut ExecuteDetailsTimings::default(), + false, + true, + ) + } + } + + #[test] + fn test_verify_account_changes_owner() { + let system_program_id = system_program::id(); + let alice_program_id = solana_sdk::pubkey::new_rand(); + let mallory_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&system_program_id, &system_program_id) + .owner(&alice_program_id) + .verify(), + Ok(()), + "system program should be able to change the account owner" + ); + assert_eq!( + Change::new(&system_program_id, &system_program_id) + .owner(&alice_program_id) + .read_only() + .verify(), + Err(InstructionError::ModifiedProgramId), + "system program should not be able to change the account owner of a read-only account" + ); + assert_eq!( + Change::new(&mallory_program_id, &system_program_id) + .owner(&alice_program_id) + .verify(), + Err(InstructionError::ModifiedProgramId), + "system program should not be able to change the account owner of a non-system account" + ); + assert_eq!( + Change::new(&mallory_program_id, &mallory_program_id) + .owner(&alice_program_id) + .verify(), + Ok(()), + "mallory should be able to change the account owner, if she leaves clear data" + ); + assert_eq!( + Change::new(&mallory_program_id, &mallory_program_id) + .owner(&alice_program_id) + .data(vec![42], vec![0]) + .verify(), + Ok(()), + "mallory should be able to change the account owner, if she leaves clear data" + ); + assert_eq!( + Change::new(&mallory_program_id, &mallory_program_id) + .owner(&alice_program_id) + .executable(true, true) + .data(vec![42], vec![0]) + .verify(), + Err(InstructionError::ModifiedProgramId), + "mallory should not be able to change the account owner, if the account executable" + ); + assert_eq!( + Change::new(&mallory_program_id, &mallory_program_id) + .owner(&alice_program_id) + .data(vec![42], vec![42]) + .verify(), + Err(InstructionError::ModifiedProgramId), + "mallory should not be able to inject data into the alice program" + ); + } + + #[test] + fn test_verify_account_changes_executable() { + let owner = solana_sdk::pubkey::new_rand(); + let mallory_program_id = solana_sdk::pubkey::new_rand(); + let system_program_id = system_program::id(); + + assert_eq!( + Change::new(&owner, &system_program_id) + .executable(false, true) + .verify(), + Err(InstructionError::ExecutableModified), + "system program can't change executable if system doesn't own the account" + ); + assert_eq!( + Change::new(&owner, &system_program_id) + .executable(true, true) + .data(vec![1], vec![2]) + .verify(), + Err(InstructionError::ExecutableDataModified), + "system program can't change executable data if system doesn't own the account" + ); + assert_eq!( + Change::new(&owner, &owner).executable(false, true).verify(), + Ok(()), + "owner should be able to change executable" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .read_only() + .verify(), + Err(InstructionError::ExecutableModified), + "owner can't modify executable of read-only accounts" + ); + assert_eq!( + Change::new(&owner, &owner).executable(true, false).verify(), + Err(InstructionError::ExecutableModified), + "owner program can't reverse executable" + ); + assert_eq!( + Change::new(&owner, &mallory_program_id) + .executable(false, true) + .verify(), + Err(InstructionError::ExecutableModified), + "malicious Mallory should not be able to change the account executable" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .data(vec![1], vec![2]) + .verify(), + Ok(()), + "account data can change in the same instruction that sets the bit" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(true, true) + .data(vec![1], vec![2]) + .verify(), + Err(InstructionError::ExecutableDataModified), + "owner should not be able to change an account's data once its marked executable" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(true, true) + .lamports(1, 2) + .verify(), + Err(InstructionError::ExecutableLamportChange), + "owner should not be able to add lamports once marked executable" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(true, true) + .lamports(1, 2) + .verify(), + Err(InstructionError::ExecutableLamportChange), + "owner should not be able to add lamports once marked executable" + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(true, true) + .lamports(2, 1) + .verify(), + Err(InstructionError::ExecutableLamportChange), + "owner should not be able to subtract lamports once marked executable" + ); + let data = vec![1; 100]; + let min_lamports = Rent::default().minimum_balance(data.len()); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .lamports(0, min_lamports) + .data(data.clone(), data.clone()) + .verify(), + Ok(()), + ); + assert_eq!( + Change::new(&owner, &owner) + .executable(false, true) + .lamports(0, min_lamports - 1) + .data(data.clone(), data) + .verify(), + Err(InstructionError::ExecutableAccountNotRentExempt), + "owner should not be able to change an account's data once its marked executable" + ); + } + + #[test] + fn test_verify_account_changes_data_len() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&system_program::id(), &system_program::id()) + .data(vec![0], vec![0, 0]) + .verify(), + Ok(()), + "system program should be able to change the data len" + ); + assert_eq!( + Change::new(&alice_program_id, &system_program::id()) + .data(vec![0], vec![0,0]) + .verify(), + Err(InstructionError::AccountDataSizeChanged), + "system program should not be able to change the data length of accounts it does not own" + ); + } + + #[test] + fn test_verify_account_changes_data() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + let mallory_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .data(vec![0], vec![42]) + .verify(), + Ok(()), + "alice program should be able to change the data" + ); + assert_eq!( + Change::new(&mallory_program_id, &alice_program_id) + .data(vec![0], vec![42]) + .verify(), + Err(InstructionError::ExternalAccountDataModified), + "non-owner mallory should not be able to change the account data" + ); + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .data(vec![0], vec![42]) + .read_only() + .verify(), + Err(InstructionError::ReadonlyDataModified), + "alice isn't allowed to touch a CO account" + ); + } + + #[test] + fn test_verify_account_changes_rent_epoch() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&alice_program_id, &system_program::id()).verify(), + Ok(()), + "nothing changed!" + ); + assert_eq!( + Change::new(&alice_program_id, &system_program::id()) + .rent_epoch(0, 1) + .verify(), + Err(InstructionError::RentEpochModified), + "no one touches rent_epoch" + ); + } + + #[test] + fn test_verify_account_changes_deduct_lamports_and_reassign_account() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + let bob_program_id = solana_sdk::pubkey::new_rand(); + + // positive test of this capability + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .owner(&bob_program_id) + .lamports(42, 1) + .data(vec![42], vec![0]) + .verify(), + Ok(()), + "alice should be able to deduct lamports and give the account to bob if the data is zeroed", + ); + } + + #[test] + fn test_verify_account_changes_lamports() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&alice_program_id, &system_program::id()) + .lamports(42, 0) + .read_only() + .verify(), + Err(InstructionError::ExternalAccountLamportSpend), + "debit should fail, even if system program" + ); + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .lamports(42, 0) + .read_only() + .verify(), + Err(InstructionError::ReadonlyLamportChange), + "debit should fail, even if owning program" + ); + assert_eq!( + Change::new(&alice_program_id, &system_program::id()) + .lamports(42, 0) + .owner(&system_program::id()) + .verify(), + Err(InstructionError::ModifiedProgramId), + "system program can't debit the account unless it was the pre.owner" + ); + assert_eq!( + Change::new(&system_program::id(), &system_program::id()) + .lamports(42, 0) + .owner(&alice_program_id) + .verify(), + Ok(()), + "system can spend (and change owner)" + ); + } + + #[test] + fn test_verify_account_changes_data_size_changed() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&alice_program_id, &system_program::id()) + .data(vec![0], vec![0, 0]) + .verify(), + Err(InstructionError::AccountDataSizeChanged), + "system program should not be able to change another program's account data size" + ); + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .data(vec![0], vec![0, 0]) + .verify(), + Err(InstructionError::AccountDataSizeChanged), + "non-system programs cannot change their data size" + ); + assert_eq!( + Change::new(&system_program::id(), &system_program::id()) + .data(vec![0], vec![0, 0]) + .verify(), + Ok(()), + "system program should be able to change account data size" + ); + } + + #[test] + fn test_verify_account_changes_owner_executable() { + let alice_program_id = solana_sdk::pubkey::new_rand(); + let bob_program_id = solana_sdk::pubkey::new_rand(); + + assert_eq!( + Change::new(&alice_program_id, &alice_program_id) + .owner(&bob_program_id) + .executable(false, true) + .verify(), + Err(InstructionError::ExecutableModified), + "Program should not be able to change owner and executable at the same time" + ); + } + + #[test] + fn test_debug() { + let mut instruction_processor = InstructionProcessor::default(); + #[allow(clippy::unnecessary_wraps)] + fn mock_process_instruction( + _program_id: &Pubkey, + _data: &[u8], + _invoke_context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + Ok(()) + } + #[allow(clippy::unnecessary_wraps)] + fn mock_ix_processor( + _pubkey: &Pubkey, + _data: &[u8], + _context: &mut dyn InvokeContext, + ) -> Result<(), InstructionError> { + Ok(()) + } + let program_id = solana_sdk::pubkey::new_rand(); + instruction_processor.add_program(program_id, mock_process_instruction); + instruction_processor.add_program(program_id, mock_ix_processor); + + assert!(!format!("{:?}", instruction_processor).is_empty()); + } +} diff --git a/program-runtime/src/lib.rs b/program-runtime/src/lib.rs new file mode 100644 index 0000000000..f2f993cd8d --- /dev/null +++ b/program-runtime/src/lib.rs @@ -0,0 +1,8 @@ +#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] +#![allow(clippy::integer_arithmetic)] + +mod instruction_processor; +mod native_loader; + +pub use instruction_processor::*; +pub use native_loader::*; diff --git a/runtime/src/native_loader.rs b/program-runtime/src/native_loader.rs similarity index 99% rename from runtime/src/native_loader.rs rename to program-runtime/src/native_loader.rs index 4fec5ab6f8..ec3b6dfaeb 100644 --- a/runtime/src/native_loader.rs +++ b/program-runtime/src/native_loader.rs @@ -5,6 +5,7 @@ use libloading::os::unix::*; use libloading::os::windows::*; use log::*; use num_derive::{FromPrimitive, ToPrimitive}; +use serde::Serialize; use solana_sdk::{ account::ReadableAccount, decode_error::DecodeError, diff --git a/program-test/Cargo.toml b/program-test/Cargo.toml index 0db5521efc..6aa210ef01 100644 --- a/program-test/Cargo.toml +++ b/program-test/Cargo.toml @@ -21,6 +21,7 @@ solana-banks-client = { path = "../banks-client", version = "=1.8.0" } solana-banks-server = { path = "../banks-server", version = "=1.8.0" } solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.8.0" } solana-logger = { path = "../logger", version = "=1.8.0" } +solana-program-runtime = { path = "../program-runtime", version = "=1.8.0" } solana-runtime = { path = "../runtime", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 77087ef5bb..41f3e14d3c 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -9,6 +9,7 @@ use { log::*, solana_banks_client::start_client, solana_banks_server::banks_server::start_local_server, + solana_program_runtime::InstructionProcessor, solana_runtime::{ bank::{Bank, Builtin, ExecuteTimings}, bank_forks::BankForks, @@ -324,7 +325,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { invoke_context.record_instruction(instruction); - solana_runtime::message_processor::MessageProcessor::process_cross_program_instruction( + InstructionProcessor::process_cross_program_instruction( &message, &executables, &accounts, diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 3bfab310b2..1a98aa873a 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2452,7 +2452,7 @@ dependencies = [ "regex", "sha3", "solana-measure", - "solana-runtime", + "solana-program-runtime", "solana-sdk", "solana_rbpf", "thiserror", @@ -3099,6 +3099,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-program-runtime" +version = "1.8.0" +dependencies = [ + "libc", + "libloading", + "log", + "num-derive", + "num-traits", + "rustc_version 0.4.0", + "serde", + "solana-frozen-abi 1.8.0", + "solana-frozen-abi-macro 1.8.0", + "solana-logger 1.8.0", + "solana-sdk", + "thiserror", +] + [[package]] name = "solana-program-test" version = "1.8.0" @@ -3116,6 +3134,7 @@ dependencies = [ "solana-banks-server", "solana-bpf-loader-program", "solana-logger 1.8.0", + "solana-program-runtime", "solana-runtime", "solana-sdk", "solana-vote-program", @@ -3167,8 +3186,6 @@ dependencies = [ "fnv", "itertools 0.10.1", "lazy_static", - "libc", - "libloading", "log", "memmap2 0.3.1", "num-derive", @@ -3188,6 +3205,7 @@ dependencies = [ "solana-logger 1.8.0", "solana-measure", "solana-metrics", + "solana-program-runtime", "solana-rayon-threadlimit", "solana-sdk", "solana-secp256k1-program", diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index a113e94fe1..4c84bf1286 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -22,7 +22,7 @@ rand_core = "0.6.3" libsecp256k1 = "0.6.0" sha3 = "0.9.1" solana-measure = { path = "../../measure", version = "=1.8.0" } -solana-runtime = { path = "../../runtime", version = "=1.8.0" } +solana-program-runtime = { path = "../../program-runtime", version = "=1.8.0" } solana-sdk = { path = "../../sdk", version = "=1.8.0" } solana_rbpf = "=0.2.14" thiserror = "1.0" @@ -30,6 +30,7 @@ thiserror = "1.0" [dev-dependencies] rand = "0.7.3" rustversion = "1.0.5" +solana-runtime = { path = "../../runtime", version = "=1.8.0" } [lib] crate-type = ["lib"] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index da23270d01..c85e379dca 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -14,6 +14,7 @@ use crate::{ }; use log::{log_enabled, trace, Level::Trace}; use solana_measure::measure::Measure; +use solana_program_runtime::InstructionProcessor; use solana_rbpf::{ aligned_memory::AlignedMemory, ebpf::HOST_ALIGN, @@ -22,7 +23,6 @@ use solana_rbpf::{ verifier::{self, VerifierError}, vm::{Config, EbpfVm, Executable, InstructionMeter}, }; -use solana_runtime::message_processor::MessageProcessor; use solana_sdk::{ account::{ReadableAccount, WritableAccount}, account_utils::State, @@ -400,7 +400,7 @@ fn process_loader_upgradeable_instruction( .iter() .map(|seeds| Pubkey::create_program_address(*seeds, caller_program_id)) .collect::, solana_sdk::pubkey::PubkeyError>>()?; - MessageProcessor::native_invoke( + InstructionProcessor::native_invoke( invoke_context, instruction, &[0, 1, 6], diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index de951300c2..8799fdec73 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -1,5 +1,6 @@ use crate::{alloc, BpfError}; use alloc::Alloc; +use solana_program_runtime::InstructionProcessor; use solana_rbpf::{ aligned_memory::AlignedMemory, ebpf, @@ -8,7 +9,6 @@ use solana_rbpf::{ question_mark, vm::{EbpfVm, SyscallObject, SyscallRegistry}, }; -use solana_runtime::message_processor::MessageProcessor; #[allow(deprecated)] use solana_sdk::sysvar::fees::Fees; use solana_sdk::{ @@ -2332,7 +2332,7 @@ fn call<'a>( .iter() .collect::>(); let (message, callee_program_id, callee_program_id_index) = - MessageProcessor::create_message( + InstructionProcessor::create_message( &instruction, &keyed_account_refs, &signers, @@ -2399,7 +2399,7 @@ fn call<'a>( // Process instruction #[allow(clippy::deref_addrof)] - match MessageProcessor::process_cross_program_instruction( + match InstructionProcessor::process_cross_program_instruction( &message, &executables, &accounts, diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ac38688662..be46f78d6c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -23,8 +23,6 @@ flate2 = "1.0.20" fnv = "1.0.7" itertools = "0.10.1" lazy_static = "1.4.0" -libc = "0.2.101" -libloading = "0.7.0" log = "0.4.14" memmap2 = "0.3.1" num-derive = { version = "0.3" } @@ -43,6 +41,7 @@ solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.8.0" } solana-logger = { path = "../logger", version = "=1.8.0" } solana-measure = { path = "../measure", version = "=1.8.0" } solana-metrics = { path = "../metrics", version = "=1.8.0" } +solana-program-runtime = { path = "../program-runtime", version = "=1.8.0" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } solana-secp256k1-program = { path = "../programs/secp256k1", version = "=1.8.0" } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d21018878e..68cb6ae05e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -50,7 +50,7 @@ use crate::{ inline_spl_token_v2_0, instruction_recorder::InstructionRecorder, log_collector::LogCollector, - message_processor::{ExecuteDetailsTimings, Executors, MessageProcessor}, + message_processor::MessageProcessor, rent_collector::RentCollector, stake_weighted_timestamp::{ calculate_stake_weighted_timestamp, MaxAllowableDrift, MAX_ALLOWABLE_DRIFT_PERCENTAGE, @@ -68,6 +68,7 @@ use log::*; use rayon::ThreadPool; use solana_measure::measure::Measure; use solana_metrics::{datapoint_debug, inc_new_counter_debug, inc_new_counter_info}; +use solana_program_runtime::{ExecuteDetailsTimings, Executors}; #[allow(deprecated)] use solana_sdk::recent_blockhashes_account; use solana_sdk::{ @@ -5766,10 +5767,10 @@ pub(crate) mod tests { create_genesis_config_with_leader, create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs, }, - native_loader::NativeLoaderError, status_cache::MAX_CACHE_ENTRIES, }; use crossbeam_channel::{bounded, unbounded}; + use solana_program_runtime::NativeLoaderError; #[allow(deprecated)] use solana_sdk::sysvar::fees::Fees; use solana_sdk::{ diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e689683762..6f6f3d2726 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -25,7 +25,6 @@ pub mod instruction_recorder; pub mod loader_utils; pub mod log_collector; pub mod message_processor; -mod native_loader; pub mod neon_evm_program; pub mod non_circulating_supply; mod pubkey_bins; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 32acb5c5ee..dcb7257ed0 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -1,14 +1,13 @@ use crate::{ accounts::Accounts, ancestors::Ancestors, instruction_recorder::InstructionRecorder, - log_collector::LogCollector, native_loader::NativeLoader, rent_collector::RentCollector, + log_collector::LogCollector, rent_collector::RentCollector, }; use log::*; use serde::{Deserialize, Serialize}; use solana_measure::measure::Measure; +use solana_program_runtime::{ExecuteDetailsTimings, Executors, InstructionProcessor, PreAccount}; use solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, - account_utils::StateMut, - bpf_loader_upgradeable::{self, UpgradeableLoaderState}, compute_budget::ComputeBudget, feature_set::{ instructions_sysvar_enabled, neon_evm_compute_budget, tx_wide_compute_cap, @@ -16,259 +15,20 @@ use solana_sdk::{ }, fee_calculator::FeeCalculator, hash::Hash, - ic_logger_msg, ic_msg, + ic_logger_msg, instruction::{CompiledInstruction, Instruction, InstructionError}, - keyed_account::{create_keyed_accounts_unified, keyed_account_at_index, KeyedAccount}, + keyed_account::{create_keyed_accounts_unified, KeyedAccount}, message::Message, - native_loader, process_instruction::{ ComputeMeter, Executor, InvokeContext, InvokeContextStackFrame, Logger, ProcessInstructionWithContext, }, pubkey::Pubkey, rent::Rent, - system_program, sysvar::instructions, transaction::TransactionError, }; -use std::{ - cell::{Ref, RefCell}, - collections::HashMap, - rc::Rc, - sync::Arc, -}; - -pub struct Executors { - pub executors: HashMap>, - pub is_dirty: bool, -} -impl Default for Executors { - fn default() -> Self { - Self { - executors: HashMap::default(), - is_dirty: false, - } - } -} -impl Executors { - pub fn insert(&mut self, key: Pubkey, executor: Arc) { - let _ = self.executors.insert(key, executor); - self.is_dirty = true; - } - pub fn get(&self, key: &Pubkey) -> Option> { - self.executors.get(key).cloned() - } -} - -#[derive(Default, Debug)] -pub struct ProgramTiming { - pub accumulated_us: u64, - pub accumulated_units: u64, - pub count: u32, -} - -#[derive(Default, Debug)] -pub struct ExecuteDetailsTimings { - pub serialize_us: u64, - pub create_vm_us: u64, - pub execute_us: u64, - pub deserialize_us: u64, - pub changed_account_count: u64, - pub total_account_count: u64, - pub total_data_size: usize, - pub data_size_changed: usize, - pub per_program_timings: HashMap, -} -impl ExecuteDetailsTimings { - pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) { - self.serialize_us += other.serialize_us; - self.create_vm_us += other.create_vm_us; - self.execute_us += other.execute_us; - self.deserialize_us += other.deserialize_us; - self.changed_account_count += other.changed_account_count; - self.total_account_count += other.total_account_count; - self.total_data_size += other.total_data_size; - self.data_size_changed += other.data_size_changed; - for (id, other) in &other.per_program_timings { - let program_timing = self.per_program_timings.entry(*id).or_default(); - program_timing.accumulated_us = program_timing - .accumulated_us - .saturating_add(other.accumulated_us); - program_timing.accumulated_units = program_timing - .accumulated_units - .saturating_add(other.accumulated_units); - program_timing.count = program_timing.count.saturating_add(other.count); - } - } - pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) { - let program_timing = self.per_program_timings.entry(*program_id).or_default(); - program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us); - program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units); - program_timing.count = program_timing.count.saturating_add(1); - } -} - -// The relevant state of an account before an Instruction executes, used -// to verify account integrity after the Instruction completes -#[derive(Clone, Debug, Default)] -pub struct PreAccount { - key: Pubkey, - account: Rc>, - changed: bool, -} -impl PreAccount { - pub fn new(key: &Pubkey, account: &AccountSharedData) -> Self { - Self { - key: *key, - account: Rc::new(RefCell::new(account.clone())), - changed: false, - } - } - - pub fn verify( - &self, - program_id: &Pubkey, - is_writable: bool, - rent: &Rent, - post: &AccountSharedData, - timings: &mut ExecuteDetailsTimings, - outermost_call: bool, - updated_verify_policy: bool, - ) -> Result<(), InstructionError> { - let pre = self.account.borrow(); - - // Only the owner of the account may change owner and - // only if the account is writable and - // only if the account is not executable and - // only if the data is zero-initialized or empty - let owner_changed = pre.owner() != post.owner(); - if owner_changed - && (!is_writable // line coverage used to get branch coverage - || pre.executable() - || program_id != pre.owner() - || !Self::is_zeroed(post.data())) - { - return Err(InstructionError::ModifiedProgramId); - } - - // An account not assigned to the program cannot have its balance decrease. - if program_id != pre.owner() // line coverage used to get branch coverage - && pre.lamports() > post.lamports() - { - return Err(InstructionError::ExternalAccountLamportSpend); - } - - // The balance of read-only and executable accounts may not change - let lamports_changed = pre.lamports() != post.lamports(); - if lamports_changed { - if !is_writable { - return Err(InstructionError::ReadonlyLamportChange); - } - if pre.executable() { - return Err(InstructionError::ExecutableLamportChange); - } - } - - // Only the system program can change the size of the data - // and only if the system program owns the account - let data_len_changed = pre.data().len() != post.data().len(); - if data_len_changed - && (!system_program::check_id(program_id) // line coverage used to get branch coverage - || !system_program::check_id(pre.owner())) - { - return Err(InstructionError::AccountDataSizeChanged); - } - - // Only the owner may change account data - // and if the account is writable - // and if the account is not executable - if !(program_id == pre.owner() - && is_writable // line coverage used to get branch coverage - && !pre.executable()) - && pre.data() != post.data() - { - if pre.executable() { - return Err(InstructionError::ExecutableDataModified); - } else if is_writable { - return Err(InstructionError::ExternalAccountDataModified); - } else { - return Err(InstructionError::ReadonlyDataModified); - } - } - - // executable is one-way (false->true) and only the account owner may set it. - let executable_changed = pre.executable() != post.executable(); - if executable_changed { - if !rent.is_exempt(post.lamports(), post.data().len()) { - return Err(InstructionError::ExecutableAccountNotRentExempt); - } - let owner = if updated_verify_policy { - post.owner() - } else { - pre.owner() - }; - if !is_writable // line coverage used to get branch coverage - || pre.executable() - || program_id != owner - { - return Err(InstructionError::ExecutableModified); - } - } - - // No one modifies rent_epoch (yet). - let rent_epoch_changed = pre.rent_epoch() != post.rent_epoch(); - if rent_epoch_changed { - return Err(InstructionError::RentEpochModified); - } - - if outermost_call { - timings.total_account_count += 1; - timings.total_data_size += post.data().len(); - if owner_changed - || lamports_changed - || data_len_changed - || executable_changed - || rent_epoch_changed - || self.changed - { - timings.changed_account_count += 1; - timings.data_size_changed += post.data().len(); - } - } - - Ok(()) - } - - pub fn update(&mut self, account: &AccountSharedData) { - let mut pre = self.account.borrow_mut(); - let rent_epoch = pre.rent_epoch(); - *pre = account.clone(); - pre.set_rent_epoch(rent_epoch); - - self.changed = true; - } - - pub fn key(&self) -> &Pubkey { - &self.key - } - - pub fn lamports(&self) -> u64 { - self.account.borrow().lamports() - } - - pub fn executable(&self) -> bool { - self.account.borrow().executable() - } - - pub fn is_zeroed(buf: &[u8]) -> bool { - const ZEROS_LEN: usize = 1024; - static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN]; - let mut chunks = buf.chunks_exact(ZEROS_LEN); - - chunks.all(|chunk| chunk == &ZEROS[..]) - && chunks.remainder() == &ZEROS[..chunks.remainder().len()] - } -} +use std::{cell::RefCell, rc::Rc, sync::Arc}; pub struct ThisComputeMeter { remaining: u64, @@ -330,7 +90,7 @@ impl<'a> ThisInvokeContext<'a> { fee_calculator: &'a FeeCalculator, ) -> Self { let pre_accounts = MessageProcessor::create_pre_accounts(message, instruction, accounts); - let keyed_accounts = MessageProcessor::create_keyed_accounts( + let keyed_accounts = InstructionProcessor::create_keyed_accounts( message, instruction, executable_accounts, @@ -443,7 +203,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { .last() .ok_or(InstructionError::CallDepth)?; let logger = self.get_logger(); - MessageProcessor::verify_and_update( + InstructionProcessor::verify_and_update( instruction, &mut self.pre_accounts, accounts, @@ -569,63 +329,10 @@ impl Logger for ThisLogger { } } -#[derive(Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct MessageProcessor { #[serde(skip)] - programs: Vec<(Pubkey, ProcessInstructionWithContext)>, - #[serde(skip)] - native_loader: NativeLoader, -} - -impl std::fmt::Debug for MessageProcessor { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - #[derive(Debug)] - struct MessageProcessor<'a> { - programs: Vec, - native_loader: &'a NativeLoader, - } - - // These are just type aliases for work around of Debug-ing above pointers - type ErasedProcessInstructionWithContext = fn( - &'static Pubkey, - &'static [u8], - &'static mut dyn InvokeContext, - ) -> Result<(), InstructionError>; - - // rustc doesn't compile due to bug without this work around - // https://github.com/rust-lang/rust/issues/50280 - // https://users.rust-lang.org/t/display-function-pointer/17073/2 - let processor = MessageProcessor { - programs: self - .programs - .iter() - .map(|(pubkey, instruction)| { - let erased_instruction: ErasedProcessInstructionWithContext = *instruction; - format!("{}: {:p}", pubkey, erased_instruction) - }) - .collect::>(), - native_loader: &self.native_loader, - }; - - write!(f, "{:?}", processor) - } -} - -impl Default for MessageProcessor { - fn default() -> Self { - Self { - programs: vec![], - native_loader: NativeLoader::default(), - } - } -} -impl Clone for MessageProcessor { - fn clone(&self) -> Self { - MessageProcessor { - programs: self.programs.clone(), - native_loader: NativeLoader::default(), - } - } + instruction_processor: InstructionProcessor, } #[cfg(RUSTC_WITH_SPECIALIZATION)] @@ -644,362 +351,8 @@ impl MessageProcessor { program_id: Pubkey, process_instruction: ProcessInstructionWithContext, ) { - match self.programs.iter_mut().find(|(key, _)| program_id == *key) { - Some((_, processor)) => *processor = process_instruction, - None => self.programs.push((program_id, process_instruction)), - } - } - - pub fn add_loader( - &mut self, - program_id: Pubkey, - process_instruction: ProcessInstructionWithContext, - ) { - self.add_program(program_id, process_instruction); - } - - /// Create the KeyedAccounts that will be passed to the program - fn create_keyed_accounts<'a>( - message: &'a Message, - instruction: &'a CompiledInstruction, - executable_accounts: &'a [(Pubkey, Rc>)], - accounts: &'a [(Pubkey, Rc>)], - ) -> Vec<(bool, bool, &'a Pubkey, &'a RefCell)> { - executable_accounts - .iter() - .map(|(key, account)| (false, false, key, account as &RefCell)) - .chain(instruction.accounts.iter().map(|index| { - let index = *index as usize; - ( - message.is_signer(index), - message.is_writable(index), - &accounts[index].0, - &accounts[index].1 as &RefCell, - ) - })) - .collect::>() - } - - /// Process an instruction - /// This method calls the instruction's program entrypoint method - fn process_instruction( - &self, - program_id: &Pubkey, - instruction_data: &[u8], - invoke_context: &mut dyn InvokeContext, - ) -> Result<(), InstructionError> { - if let Some(root_account) = invoke_context.get_keyed_accounts()?.iter().next() { - let root_id = root_account.unsigned_key(); - if native_loader::check_id(&root_account.owner()?) { - for (id, process_instruction) in &self.programs { - if id == root_id { - invoke_context.remove_first_keyed_account()?; - // Call the builtin program - return process_instruction(program_id, instruction_data, invoke_context); - } - } - // Call the program via the native loader - return self.native_loader.process_instruction( - &native_loader::id(), - instruction_data, - invoke_context, - ); - } else { - let owner_id = &root_account.owner()?; - for (id, process_instruction) in &self.programs { - if id == owner_id { - // Call the program via a builtin loader - return process_instruction(program_id, instruction_data, invoke_context); - } - } - } - } - Err(InstructionError::UnsupportedProgramId) - } - - pub fn create_message( - instruction: &Instruction, - keyed_accounts: &[&KeyedAccount], - signers: &[Pubkey], - invoke_context: &Ref<&mut dyn InvokeContext>, - ) -> Result<(Message, Pubkey, usize), InstructionError> { - // Check for privilege escalation - for account in instruction.accounts.iter() { - let keyed_account = keyed_accounts - .iter() - .find_map(|keyed_account| { - if &account.pubkey == keyed_account.unsigned_key() { - Some(keyed_account) - } else { - None - } - }) - .ok_or_else(|| { - ic_msg!( - invoke_context, - "Instruction references an unknown account {}", - account.pubkey - ); - InstructionError::MissingAccount - })?; - // Readonly account cannot become writable - if account.is_writable && !keyed_account.is_writable() { - ic_msg!( - invoke_context, - "{}'s writable privilege escalated", - account.pubkey - ); - return Err(InstructionError::PrivilegeEscalation); - } - - if account.is_signer && // If message indicates account is signed - !( // one of the following needs to be true: - keyed_account.signer_key().is_some() // Signed in the parent instruction - || signers.contains(&account.pubkey) // Signed by the program - ) { - ic_msg!( - invoke_context, - "{}'s signer privilege escalated", - account.pubkey - ); - return Err(InstructionError::PrivilegeEscalation); - } - } - - // validate the caller has access to the program account and that it is executable - let program_id = instruction.program_id; - match keyed_accounts - .iter() - .find(|keyed_account| &program_id == keyed_account.unsigned_key()) - { - Some(keyed_account) => { - if !keyed_account.executable()? { - ic_msg!( - invoke_context, - "Account {} is not executable", - keyed_account.unsigned_key() - ); - return Err(InstructionError::AccountNotExecutable); - } - } - None => { - ic_msg!(invoke_context, "Unknown program {}", program_id); - return Err(InstructionError::MissingAccount); - } - } - - let message = Message::new(&[instruction.clone()], None); - let program_id_index = message.instructions[0].program_id_index as usize; - - Ok((message, program_id, program_id_index)) - } - - /// Entrypoint for a cross-program invocation from a native program - pub fn native_invoke( - invoke_context: &mut dyn InvokeContext, - instruction: Instruction, - keyed_account_indices: &[usize], - signers: &[Pubkey], - ) -> Result<(), InstructionError> { - let invoke_context = RefCell::new(invoke_context); - - let ( - message, - executable_accounts, - accounts, - keyed_account_indices_reordered, - caller_write_privileges, - ) = { - let invoke_context = invoke_context.borrow(); - - // Translate and verify caller's data - let keyed_accounts = invoke_context.get_keyed_accounts()?; - let keyed_accounts = keyed_account_indices - .iter() - .map(|index| keyed_account_at_index(keyed_accounts, *index)) - .collect::, InstructionError>>()?; - let (message, callee_program_id, _) = - Self::create_message(&instruction, &keyed_accounts, signers, &invoke_context)?; - let keyed_accounts = invoke_context.get_keyed_accounts()?; - let mut caller_write_privileges = keyed_account_indices - .iter() - .map(|index| keyed_accounts[*index].is_writable()) - .collect::>(); - caller_write_privileges.insert(0, false); - let mut accounts = vec![]; - let mut keyed_account_indices_reordered = vec![]; - let keyed_accounts = invoke_context.get_keyed_accounts()?; - 'root: for account_key in message.account_keys.iter() { - for keyed_account_index in keyed_account_indices { - let keyed_account = &keyed_accounts[*keyed_account_index]; - if account_key == keyed_account.unsigned_key() { - accounts.push((*account_key, Rc::new(keyed_account.account.clone()))); - keyed_account_indices_reordered.push(*keyed_account_index); - continue 'root; - } - } - ic_msg!( - invoke_context, - "Instruction references an unknown account {}", - account_key - ); - return Err(InstructionError::MissingAccount); - } - - // Process instruction - - invoke_context.record_instruction(&instruction); - - let program_account = - invoke_context - .get_account(&callee_program_id) - .ok_or_else(|| { - ic_msg!(invoke_context, "Unknown program {}", callee_program_id); - InstructionError::MissingAccount - })?; - if !program_account.borrow().executable() { - ic_msg!( - invoke_context, - "Account {} is not executable", - callee_program_id - ); - return Err(InstructionError::AccountNotExecutable); - } - let programdata = if program_account.borrow().owner() == &bpf_loader_upgradeable::id() { - if let UpgradeableLoaderState::Program { - programdata_address, - } = program_account.borrow().state()? - { - if let Some(account) = invoke_context.get_account(&programdata_address) { - Some((programdata_address, account)) - } else { - ic_msg!( - invoke_context, - "Unknown upgradeable programdata account {}", - programdata_address, - ); - return Err(InstructionError::MissingAccount); - } - } else { - ic_msg!( - invoke_context, - "Upgradeable program account state not valid {}", - callee_program_id, - ); - return Err(InstructionError::MissingAccount); - } - } else { - None - }; - let mut executable_accounts = vec![(callee_program_id, program_account)]; - if let Some(programdata) = programdata { - executable_accounts.push(programdata); - } - ( - message, - executable_accounts, - accounts, - keyed_account_indices_reordered, - caller_write_privileges, - ) - }; - - #[allow(clippy::deref_addrof)] - MessageProcessor::process_cross_program_instruction( - &message, - &executable_accounts, - &accounts, - &caller_write_privileges, - *(&mut *(invoke_context.borrow_mut())), - )?; - - // Copy results back to caller - - { - let invoke_context = invoke_context.borrow(); - let keyed_accounts = invoke_context.get_keyed_accounts()?; - for (src_keyed_account_index, ((_key, account), dst_keyed_account_index)) in accounts - .iter() - .zip(keyed_account_indices_reordered) - .enumerate() - { - let dst_keyed_account = &keyed_accounts[dst_keyed_account_index]; - let src_keyed_account = account.borrow(); - if message.is_writable(src_keyed_account_index) && !src_keyed_account.executable() { - if dst_keyed_account.data_len()? != src_keyed_account.data().len() - && dst_keyed_account.data_len()? != 0 - { - // Only support for `CreateAccount` at this time. - // Need a way to limit total realloc size across multiple CPI calls - ic_msg!( - invoke_context, - "Inner instructions do not support realloc, only SystemProgram::CreateAccount", - ); - return Err(InstructionError::InvalidRealloc); - } - dst_keyed_account - .try_account_ref_mut()? - .set_lamports(src_keyed_account.lamports()); - dst_keyed_account - .try_account_ref_mut()? - .set_owner(*src_keyed_account.owner()); - dst_keyed_account - .try_account_ref_mut()? - .set_data(src_keyed_account.data().to_vec()); - } - } - } - - Ok(()) - } - - /// Process a cross-program instruction - /// This method calls the instruction's program entrypoint function - pub fn process_cross_program_instruction( - message: &Message, - executable_accounts: &[(Pubkey, Rc>)], - accounts: &[(Pubkey, Rc>)], - caller_write_privileges: &[bool], - invoke_context: &mut dyn InvokeContext, - ) -> Result<(), InstructionError> { - if let Some(instruction) = message.instructions.get(0) { - let program_id = instruction.program_id(&message.account_keys); - - // Verify the calling program hasn't misbehaved - invoke_context.verify_and_update(instruction, accounts, caller_write_privileges)?; - - // Construct keyed accounts - let keyed_accounts = - Self::create_keyed_accounts(message, instruction, executable_accounts, accounts); - - // Invoke callee - invoke_context.push(program_id, &keyed_accounts)?; - - let mut message_processor = MessageProcessor::default(); - for (program_id, process_instruction) in invoke_context.get_programs().iter() { - message_processor.add_program(*program_id, *process_instruction); - } - - let mut result = message_processor.process_instruction( - program_id, - &instruction.data, - invoke_context, - ); - if result.is_ok() { - // Verify the called program has not misbehaved - let write_privileges: Vec = (0..message.account_keys.len()) - .map(|i| message.is_writable(i)) - .collect(); - result = invoke_context.verify_and_update(instruction, accounts, &write_privileges); - } - - // Restore previous state - invoke_context.pop(); - result - } else { - // This function is always called with a valid instruction, if that changes return an error - Err(InstructionError::GenericError) - } + self.instruction_processor + .add_program(program_id, process_instruction); } /// Record the initial state of the accounts so that they can be compared @@ -1078,7 +431,7 @@ impl MessageProcessor { ic_logger_msg!( logger, "failed to verify account {}: {}", - pre_accounts[unique_index].key, + pre_accounts[unique_index].key(), err ); err @@ -1097,69 +450,6 @@ impl MessageProcessor { Ok(()) } - /// Verify the results of a cross-program instruction - #[allow(clippy::too_many_arguments)] - fn verify_and_update( - instruction: &CompiledInstruction, - pre_accounts: &mut [PreAccount], - accounts: &[(Pubkey, Rc>)], - program_id: &Pubkey, - rent: &Rent, - write_privileges: &[bool], - timings: &mut ExecuteDetailsTimings, - logger: Rc>, - updated_verify_policy: bool, - ) -> Result<(), InstructionError> { - // Verify the per-account instruction results - let (mut pre_sum, mut post_sum) = (0_u128, 0_u128); - let mut work = |_unique_index: usize, account_index: usize| { - if account_index < write_privileges.len() && account_index < accounts.len() { - let (key, account) = &accounts[account_index]; - let is_writable = write_privileges[account_index]; - // Find the matching PreAccount - for pre_account in pre_accounts.iter_mut() { - if key == pre_account.key() { - { - // Verify account has no outstanding references - let _ = account - .try_borrow_mut() - .map_err(|_| InstructionError::AccountBorrowOutstanding)?; - } - let account = account.borrow(); - pre_account - .verify( - program_id, - is_writable, - rent, - &account, - timings, - false, - updated_verify_policy, - ) - .map_err(|err| { - ic_logger_msg!(logger, "failed to verify account {}: {}", key, err); - err - })?; - pre_sum += u128::from(pre_account.lamports()); - post_sum += u128::from(account.lamports()); - if is_writable && !pre_account.executable() { - pre_account.update(&account); - } - return Ok(()); - } - } - } - Err(InstructionError::MissingAccount) - }; - instruction.visit_each_account(&mut work)?; - - // Verify that the total sum of all the lamports did not change - if pre_sum != post_sum { - return Err(InstructionError::UnbalancedInstruction); - } - Ok(()) - } - /// Execute an instruction /// This method calls the instruction's program entrypoint method and verifies that the result of /// the call does not violate the bank's accounting rules. @@ -1211,6 +501,7 @@ impl MessageProcessor { compute_budget.heap_size = Some(256 * 1024); } + let programs = self.instruction_processor.programs(); let mut invoke_context = ThisInvokeContext::new( program_id, rent_collector.rent, @@ -1218,7 +509,7 @@ impl MessageProcessor { instruction, executable_accounts, accounts, - &self.programs, + programs, log_collector, compute_budget, compute_meter, @@ -1230,7 +521,12 @@ impl MessageProcessor { blockhash, fee_calculator, ); - self.process_instruction(program_id, &instruction.data, &mut invoke_context)?; + + self.instruction_processor.process_instruction( + program_id, + &instruction.data, + &mut invoke_context, + )?; Self::verify( message, instruction, @@ -1317,10 +613,9 @@ impl MessageProcessor { mod tests { use super::*; use solana_sdk::{ - account::Account, instruction::{AccountMeta, Instruction, InstructionError}, message::Message, - native_loader::create_loadable_account_for_test, + native_loader::{self, create_loadable_account_for_test}, process_instruction::MockComputeMeter, }; @@ -1427,10 +722,7 @@ mod tests { .verify_and_update(&message.instructions[0], &these_accounts, &write_privileges) .unwrap(); assert_eq!( - invoke_context.pre_accounts[owned_index] - .account - .borrow() - .data()[0], + invoke_context.pre_accounts[owned_index].data()[0], (MAX_DEPTH + owned_index) as u8 ); @@ -1446,41 +738,13 @@ mod tests { ), Err(InstructionError::ExternalAccountDataModified) ); - assert_eq!( - invoke_context.pre_accounts[not_owned_index] - .account - .borrow() - .data()[0], - data - ); + assert_eq!(invoke_context.pre_accounts[not_owned_index].data()[0], data); accounts[not_owned_index].1.borrow_mut().data_as_mut_slice()[0] = data; invoke_context.pop(); } } - #[test] - fn test_is_zeroed() { - const ZEROS_LEN: usize = 1024; - let mut buf = [0; ZEROS_LEN]; - assert!(PreAccount::is_zeroed(&buf)); - buf[0] = 1; - assert!(!PreAccount::is_zeroed(&buf)); - - let mut buf = [0; ZEROS_LEN - 1]; - assert!(PreAccount::is_zeroed(&buf)); - buf[0] = 1; - assert!(!PreAccount::is_zeroed(&buf)); - - let mut buf = [0; ZEROS_LEN + 1]; - assert!(PreAccount::is_zeroed(&buf)); - buf[0] = 1; - assert!(!PreAccount::is_zeroed(&buf)); - - let buf = vec![]; - assert!(PreAccount::is_zeroed(&buf)); - } - #[test] fn test_verify_account_references() { let accounts = vec![( @@ -1497,409 +761,6 @@ mod tests { ); } - struct Change { - program_id: Pubkey, - is_writable: bool, - rent: Rent, - pre: PreAccount, - post: AccountSharedData, - } - impl Change { - pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self { - Self { - program_id: *program_id, - rent: Rent::default(), - is_writable: true, - pre: PreAccount::new( - &solana_sdk::pubkey::new_rand(), - &AccountSharedData::from(Account { - owner: *owner, - lamports: std::u64::MAX, - data: vec![], - ..Account::default() - }), - ), - post: AccountSharedData::from(Account { - owner: *owner, - lamports: std::u64::MAX, - ..Account::default() - }), - } - } - pub fn read_only(mut self) -> Self { - self.is_writable = false; - self - } - pub fn executable(mut self, pre: bool, post: bool) -> Self { - self.pre.account.borrow_mut().set_executable(pre); - self.post.set_executable(post); - self - } - pub fn lamports(mut self, pre: u64, post: u64) -> Self { - self.pre.account.borrow_mut().set_lamports(pre); - self.post.set_lamports(post); - self - } - pub fn owner(mut self, post: &Pubkey) -> Self { - self.post.set_owner(*post); - self - } - pub fn data(mut self, pre: Vec, post: Vec) -> Self { - self.pre.account.borrow_mut().set_data(pre); - self.post.set_data(post); - self - } - pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self { - self.pre.account.borrow_mut().set_rent_epoch(pre); - self.post.set_rent_epoch(post); - self - } - pub fn verify(&self) -> Result<(), InstructionError> { - self.pre.verify( - &self.program_id, - self.is_writable, - &self.rent, - &self.post, - &mut ExecuteDetailsTimings::default(), - false, - true, - ) - } - } - - #[test] - fn test_verify_account_changes_owner() { - let system_program_id = system_program::id(); - let alice_program_id = solana_sdk::pubkey::new_rand(); - let mallory_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&system_program_id, &system_program_id) - .owner(&alice_program_id) - .verify(), - Ok(()), - "system program should be able to change the account owner" - ); - assert_eq!( - Change::new(&system_program_id, &system_program_id) - .owner(&alice_program_id) - .read_only() - .verify(), - Err(InstructionError::ModifiedProgramId), - "system program should not be able to change the account owner of a read-only account" - ); - assert_eq!( - Change::new(&mallory_program_id, &system_program_id) - .owner(&alice_program_id) - .verify(), - Err(InstructionError::ModifiedProgramId), - "system program should not be able to change the account owner of a non-system account" - ); - assert_eq!( - Change::new(&mallory_program_id, &mallory_program_id) - .owner(&alice_program_id) - .verify(), - Ok(()), - "mallory should be able to change the account owner, if she leaves clear data" - ); - assert_eq!( - Change::new(&mallory_program_id, &mallory_program_id) - .owner(&alice_program_id) - .data(vec![42], vec![0]) - .verify(), - Ok(()), - "mallory should be able to change the account owner, if she leaves clear data" - ); - assert_eq!( - Change::new(&mallory_program_id, &mallory_program_id) - .owner(&alice_program_id) - .executable(true, true) - .data(vec![42], vec![0]) - .verify(), - Err(InstructionError::ModifiedProgramId), - "mallory should not be able to change the account owner, if the account executable" - ); - assert_eq!( - Change::new(&mallory_program_id, &mallory_program_id) - .owner(&alice_program_id) - .data(vec![42], vec![42]) - .verify(), - Err(InstructionError::ModifiedProgramId), - "mallory should not be able to inject data into the alice program" - ); - } - - #[test] - fn test_verify_account_changes_executable() { - let owner = solana_sdk::pubkey::new_rand(); - let mallory_program_id = solana_sdk::pubkey::new_rand(); - let system_program_id = system_program::id(); - - assert_eq!( - Change::new(&owner, &system_program_id) - .executable(false, true) - .verify(), - Err(InstructionError::ExecutableModified), - "system program can't change executable if system doesn't own the account" - ); - assert_eq!( - Change::new(&owner, &system_program_id) - .executable(true, true) - .data(vec![1], vec![2]) - .verify(), - Err(InstructionError::ExecutableDataModified), - "system program can't change executable data if system doesn't own the account" - ); - assert_eq!( - Change::new(&owner, &owner).executable(false, true).verify(), - Ok(()), - "owner should be able to change executable" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(false, true) - .read_only() - .verify(), - Err(InstructionError::ExecutableModified), - "owner can't modify executable of read-only accounts" - ); - assert_eq!( - Change::new(&owner, &owner).executable(true, false).verify(), - Err(InstructionError::ExecutableModified), - "owner program can't reverse executable" - ); - assert_eq!( - Change::new(&owner, &mallory_program_id) - .executable(false, true) - .verify(), - Err(InstructionError::ExecutableModified), - "malicious Mallory should not be able to change the account executable" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(false, true) - .data(vec![1], vec![2]) - .verify(), - Ok(()), - "account data can change in the same instruction that sets the bit" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(true, true) - .data(vec![1], vec![2]) - .verify(), - Err(InstructionError::ExecutableDataModified), - "owner should not be able to change an account's data once its marked executable" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(true, true) - .lamports(1, 2) - .verify(), - Err(InstructionError::ExecutableLamportChange), - "owner should not be able to add lamports once marked executable" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(true, true) - .lamports(1, 2) - .verify(), - Err(InstructionError::ExecutableLamportChange), - "owner should not be able to add lamports once marked executable" - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(true, true) - .lamports(2, 1) - .verify(), - Err(InstructionError::ExecutableLamportChange), - "owner should not be able to subtract lamports once marked executable" - ); - let data = vec![1; 100]; - let min_lamports = Rent::default().minimum_balance(data.len()); - assert_eq!( - Change::new(&owner, &owner) - .executable(false, true) - .lamports(0, min_lamports) - .data(data.clone(), data.clone()) - .verify(), - Ok(()), - ); - assert_eq!( - Change::new(&owner, &owner) - .executable(false, true) - .lamports(0, min_lamports - 1) - .data(data.clone(), data) - .verify(), - Err(InstructionError::ExecutableAccountNotRentExempt), - "owner should not be able to change an account's data once its marked executable" - ); - } - - #[test] - fn test_verify_account_changes_data_len() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&system_program::id(), &system_program::id()) - .data(vec![0], vec![0, 0]) - .verify(), - Ok(()), - "system program should be able to change the data len" - ); - assert_eq!( - Change::new(&alice_program_id, &system_program::id()) - .data(vec![0], vec![0,0]) - .verify(), - Err(InstructionError::AccountDataSizeChanged), - "system program should not be able to change the data length of accounts it does not own" - ); - } - - #[test] - fn test_verify_account_changes_data() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - let mallory_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .data(vec![0], vec![42]) - .verify(), - Ok(()), - "alice program should be able to change the data" - ); - assert_eq!( - Change::new(&mallory_program_id, &alice_program_id) - .data(vec![0], vec![42]) - .verify(), - Err(InstructionError::ExternalAccountDataModified), - "non-owner mallory should not be able to change the account data" - ); - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .data(vec![0], vec![42]) - .read_only() - .verify(), - Err(InstructionError::ReadonlyDataModified), - "alice isn't allowed to touch a CO account" - ); - } - - #[test] - fn test_verify_account_changes_rent_epoch() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&alice_program_id, &system_program::id()).verify(), - Ok(()), - "nothing changed!" - ); - assert_eq!( - Change::new(&alice_program_id, &system_program::id()) - .rent_epoch(0, 1) - .verify(), - Err(InstructionError::RentEpochModified), - "no one touches rent_epoch" - ); - } - - #[test] - fn test_verify_account_changes_deduct_lamports_and_reassign_account() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - let bob_program_id = solana_sdk::pubkey::new_rand(); - - // positive test of this capability - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .owner(&bob_program_id) - .lamports(42, 1) - .data(vec![42], vec![0]) - .verify(), - Ok(()), - "alice should be able to deduct lamports and give the account to bob if the data is zeroed", - ); - } - - #[test] - fn test_verify_account_changes_lamports() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&alice_program_id, &system_program::id()) - .lamports(42, 0) - .read_only() - .verify(), - Err(InstructionError::ExternalAccountLamportSpend), - "debit should fail, even if system program" - ); - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .lamports(42, 0) - .read_only() - .verify(), - Err(InstructionError::ReadonlyLamportChange), - "debit should fail, even if owning program" - ); - assert_eq!( - Change::new(&alice_program_id, &system_program::id()) - .lamports(42, 0) - .owner(&system_program::id()) - .verify(), - Err(InstructionError::ModifiedProgramId), - "system program can't debit the account unless it was the pre.owner" - ); - assert_eq!( - Change::new(&system_program::id(), &system_program::id()) - .lamports(42, 0) - .owner(&alice_program_id) - .verify(), - Ok(()), - "system can spend (and change owner)" - ); - } - - #[test] - fn test_verify_account_changes_data_size_changed() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&alice_program_id, &system_program::id()) - .data(vec![0], vec![0, 0]) - .verify(), - Err(InstructionError::AccountDataSizeChanged), - "system program should not be able to change another program's account data size" - ); - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .data(vec![0], vec![0, 0]) - .verify(), - Err(InstructionError::AccountDataSizeChanged), - "non-system programs cannot change their data size" - ); - assert_eq!( - Change::new(&system_program::id(), &system_program::id()) - .data(vec![0], vec![0, 0]) - .verify(), - Ok(()), - "system program should be able to change account data size" - ); - } - - #[test] - fn test_verify_account_changes_owner_executable() { - let alice_program_id = solana_sdk::pubkey::new_rand(); - let bob_program_id = solana_sdk::pubkey::new_rand(); - - assert_eq!( - Change::new(&alice_program_id, &alice_program_id) - .owner(&bob_program_id) - .executable(false, true) - .verify(), - Err(InstructionError::ExecutableModified), - "Program should not be able to change owner and executable at the same time" - ); - } - #[test] fn test_process_message_readonly_handling() { #[derive(Serialize, Deserialize)] @@ -2368,7 +1229,7 @@ mod tests { .collect::>(); accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 1; assert_eq!( - MessageProcessor::process_cross_program_instruction( + InstructionProcessor::process_cross_program_instruction( &message, &executable_accounts, &accounts, @@ -2427,7 +1288,7 @@ mod tests { .map(|(i, _)| message.is_writable(i)) .collect::>(); assert_eq!( - MessageProcessor::process_cross_program_instruction( + InstructionProcessor::process_cross_program_instruction( &message, &executable_accounts, &accounts, @@ -2438,30 +1299,4 @@ mod tests { ); } } - - #[test] - fn test_debug() { - let mut message_processor = MessageProcessor::default(); - #[allow(clippy::unnecessary_wraps)] - fn mock_process_instruction( - _program_id: &Pubkey, - _data: &[u8], - _invoke_context: &mut dyn InvokeContext, - ) -> Result<(), InstructionError> { - Ok(()) - } - #[allow(clippy::unnecessary_wraps)] - fn mock_ix_processor( - _pubkey: &Pubkey, - _data: &[u8], - _context: &mut dyn InvokeContext, - ) -> Result<(), InstructionError> { - Ok(()) - } - let program_id = solana_sdk::pubkey::new_rand(); - message_processor.add_program(program_id, mock_process_instruction); - message_processor.add_loader(program_id, mock_ix_processor); - - assert!(!format!("{:?}", message_processor).is_empty()); - } } diff --git a/runtime/src/serde_snapshot.rs b/runtime/src/serde_snapshot.rs index 1761bf6939..5069eb194f 100644 --- a/runtime/src/serde_snapshot.rs +++ b/runtime/src/serde_snapshot.rs @@ -11,7 +11,6 @@ use { blockhash_queue::BlockhashQueue, epoch_stakes::EpochStakes, hardened_unpack::UnpackedAppendVecMap, - message_processor::MessageProcessor, rent_collector::RentCollector, serde_snapshot::future::SerializableStorage, stakes::Stakes, @@ -21,6 +20,7 @@ use { log::*, rayon::prelude::*, serde::{de::DeserializeOwned, Deserialize, Serialize}, + solana_program_runtime::InstructionProcessor, solana_sdk::{ clock::{Epoch, Slot, UnixTimestamp}, epoch_schedule::EpochSchedule, diff --git a/runtime/src/serde_snapshot/future.rs b/runtime/src/serde_snapshot/future.rs index 1002c1cddb..6f4f973278 100644 --- a/runtime/src/serde_snapshot/future.rs +++ b/runtime/src/serde_snapshot/future.rs @@ -78,7 +78,7 @@ pub(crate) struct DeserializableVersionedBank { pub(crate) unused_accounts: UnusedAccounts, pub(crate) epoch_stakes: HashMap, pub(crate) is_delta: bool, - pub(crate) message_processor: MessageProcessor, + pub(crate) message_processor: InstructionProcessor, } impl From for BankFieldsToDeserialize { @@ -155,7 +155,7 @@ pub(crate) struct SerializableVersionedBank<'a> { pub(crate) unused_accounts: UnusedAccounts, pub(crate) epoch_stakes: &'a HashMap, pub(crate) is_delta: bool, - pub(crate) message_processor: MessageProcessor, + pub(crate) message_processor: InstructionProcessor, } impl<'a> From> for SerializableVersionedBank<'a> { diff --git a/runtime/src/serde_snapshot/tests.rs b/runtime/src/serde_snapshot/tests.rs index bb3d9c710e..c64d5a24e6 100644 --- a/runtime/src/serde_snapshot/tests.rs +++ b/runtime/src/serde_snapshot/tests.rs @@ -308,7 +308,7 @@ mod test_bank_serialize { // This some what long test harness is required to freeze the ABI of // Bank's serialization due to versioned nature - #[frozen_abi(digest = "7XCv7DU27QC6iNJ1WYkXY3X4bKu8j6CxAn6morP2u4hu")] + #[frozen_abi(digest = "A9KFf8kLJczP3AMbFXRrqzmruoqMjooTPzdvEwZZ4EP7")] #[derive(Serialize, AbiExample)] pub struct BankAbiTestWrapperFuture { #[serde(serialize_with = "wrapper_future")]