From 9290e561e1babdfb0f48abf43fb88dd397788ea0 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 11 Aug 2020 16:11:52 -0700 Subject: [PATCH] Align host addresses (#11384) * Align host addresses * support new program abi * update epoch rollout * Enforce aligned pointers in cross-program invocations --- core/src/validator.rs | 3 + genesis-programs/src/lib.rs | 38 +- local-cluster/tests/local_cluster.rs | 8 +- programs/bpf/c/src/invoked/invoked.c | 5 + programs/bpf_loader/src/deprecated.rs | 9 + programs/bpf_loader/src/lib.rs | 184 ++++---- programs/bpf_loader/src/serialization.rs | 410 ++++++++++++++++++ programs/bpf_loader/src/syscalls.rs | 68 +-- programs/bpf_loader/test_elfs/noop_aligned.so | Bin 0 -> 58088 bytes .../test_elfs/{noop.so => noop_unaligned.so} | Bin sdk/bpf/c/inc/solana_sdk.h | 37 +- sdk/src/bpf_loader.rs | 2 +- sdk/src/bpf_loader_deprecated.rs | 1 + sdk/src/entrypoint.rs | 18 +- sdk/src/entrypoint_native.rs | 34 +- sdk/src/lib.rs | 1 + 16 files changed, 655 insertions(+), 163 deletions(-) create mode 100644 programs/bpf_loader/src/deprecated.rs create mode 100644 programs/bpf_loader/src/serialization.rs create mode 100755 programs/bpf_loader/test_elfs/noop_aligned.so rename programs/bpf_loader/test_elfs/{noop.so => noop_unaligned.so} (100%) create mode 100644 sdk/src/bpf_loader_deprecated.rs diff --git a/core/src/validator.rs b/core/src/validator.rs index 32983d80c9..e458ba00fa 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -871,6 +871,9 @@ impl TestValidator { genesis_config .native_instruction_processors .push(solana_bpf_loader_program!()); + genesis_config + .native_instruction_processors + .push(solana_bpf_loader_deprecated_program!()); genesis_config.rent.lamports_per_byte_year = 1; genesis_config.rent.exemption_threshold = 1.0; diff --git a/genesis-programs/src/lib.rs b/genesis-programs/src/lib.rs index ca951cee03..86d2935d38 100644 --- a/genesis-programs/src/lib.rs +++ b/genesis-programs/src/lib.rs @@ -36,9 +36,8 @@ pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option match epoch { // No inflation at epoch 0 0 => Some(Inflation::new_disabled()), - // Inflation starts - // The epoch of Epoch::MAX is a placeholder and is expected to be reduced in - // a future hard fork. + // Inflation starts The epoch of Epoch::MAX is a placeholder and is + // expected to be reduced in a future hard fork. Epoch::MAX => Some(Inflation::default()), _ => None, }, @@ -49,38 +48,36 @@ pub fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option { if epoch == 0 { + // Programs used for testing Some(vec![ - // Enable all Stable programs solana_bpf_loader_program!(), + solana_bpf_loader_deprecated_program!(), solana_vest_program!(), - // Programs that are only available in Development mode solana_budget_program!(), solana_exchange_program!(), ]) + } else if epoch == std::u64::MAX { + // The epoch of std::u64::MAX is a placeholder and is expected + // to be reduced in a future network update. + Some(vec![solana_bpf_loader_program!()]) } else { None } } OperatingMode::Stable => { - if epoch == std::u64::MAX - 1 { - // The epoch of std::u64::MAX - 1 is a placeholder and is expected to be reduced in - // a future hard fork. - Some(vec![solana_bpf_loader_program!()]) - } else if epoch == std::u64::MAX { - // The epoch of std::u64::MAX is a placeholder and is expected to be reduced in a - // future hard fork. - Some(vec![solana_vest_program!()]) + if epoch == std::u64::MAX { + // The epoch of std::u64::MAX is a placeholder and is expected + // to be reduced in a future network update. + Some(vec![solana_bpf_loader_program!(), solana_vest_program!()]) } else { None } } OperatingMode::Preview => { - if epoch == 0 { - Some(vec![solana_bpf_loader_program!()]) - } else if epoch == std::u64::MAX { - // The epoch of std::u64::MAX is a placeholder and is expected to be reduced in a - // future hard fork. - Some(vec![solana_vest_program!()]) + if epoch == std::u64::MAX { + // The epoch of std::u64::MAX is a placeholder and is expected + // to be reduced in a future network update. + Some(vec![solana_bpf_loader_program!(), solana_vest_program!()]) } else { None } @@ -138,7 +135,7 @@ mod tests { fn test_development_programs() { assert_eq!( get_programs(OperatingMode::Development, 0).unwrap().len(), - 4 + 5 ); assert_eq!(get_programs(OperatingMode::Development, 1), None); } @@ -159,7 +156,6 @@ mod tests { #[test] fn test_softlaunch_programs() { assert_eq!(get_programs(OperatingMode::Stable, 1), None); - assert!(get_programs(OperatingMode::Stable, std::u64::MAX - 1).is_some()); assert!(get_programs(OperatingMode::Stable, std::u64::MAX).is_some()); } } diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index 1f5c69ddb6..9e5c57b969 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -705,7 +705,13 @@ fn test_stable_operating_mode() { } // Programs that are not available at epoch 0 - for program_id in [&solana_sdk::bpf_loader::id(), &solana_vest_program::id()].iter() { + for program_id in [ + &solana_sdk::bpf_loader::id(), + &solana_sdk::bpf_loader_deprecated::id(), + &solana_vest_program::id(), + ] + .iter() + { assert_eq!( ( program_id, diff --git a/programs/bpf/c/src/invoked/invoked.c b/programs/bpf/c/src/invoked/invoked.c index ece9591a5e..bdd5482244 100644 --- a/programs/bpf/c/src/invoked/invoked.c +++ b/programs/bpf/c/src/invoked/invoked.c @@ -23,6 +23,11 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(sol_deserialize(input, ¶ms, 4)); SolPubkey bpf_loader_id = + (SolPubkey){.x = {2, 168, 246, 145, 78, 136, 161, 110, 57, 90, 225, + 40, 148, 143, 250, 105, 86, 147, 55, 104, 24, 221, + 71, 67, 82, 33, 243, 198, 0, 0, 0, 0}}; + + SolPubkey bpf_loader_deprecated_id = (SolPubkey){.x = {2, 168, 246, 145, 78, 136, 161, 107, 189, 35, 149, 133, 95, 100, 4, 217, 180, 244, 86, 183, 130, 27, 176, 20, 87, 73, 66, 140, 0, 0, 0, 0}}; diff --git a/programs/bpf_loader/src/deprecated.rs b/programs/bpf_loader/src/deprecated.rs new file mode 100644 index 0000000000..b2564c9ad1 --- /dev/null +++ b/programs/bpf_loader/src/deprecated.rs @@ -0,0 +1,9 @@ +use crate::process_instruction; + +solana_sdk::declare_loader!( + solana_sdk::bpf_loader_deprecated::ID, + solana_bpf_loader_deprecated_program, + process_instruction, + solana_bpf_loader_program, + deprecated::id +); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index c48874e11c..c9cefa1401 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1,10 +1,15 @@ pub mod alloc; pub mod allocator_bump; pub mod bpf_verifier; +pub mod deprecated; +pub mod serialization; pub mod syscalls; -use crate::{bpf_verifier::VerifierError, syscalls::SyscallError}; -use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; +use crate::{ + bpf_verifier::VerifierError, + serialization::{deserialize_parameters, serialize_parameters}, + syscalls::SyscallError, +}; use num_derive::{FromPrimitive, ToPrimitive}; use solana_rbpf::{ ebpf::{EbpfError, UserDefinedError}, @@ -13,7 +18,7 @@ use solana_rbpf::{ }; use solana_sdk::{ account::{is_executable, next_keyed_account, KeyedAccount}, - bpf_loader, + bpf_loader, bpf_loader_deprecated, decode_error::DecodeError, entrypoint::SUCCESS, entrypoint_native::InvokeContext, @@ -22,13 +27,13 @@ use solana_sdk::{ program_utils::limited_deserialize, pubkey::Pubkey, }; -use std::{io::prelude::*, mem}; use thiserror::Error; solana_sdk::declare_loader!( solana_sdk::bpf_loader::ID, solana_bpf_loader_program, - process_instruction + process_instruction, + solana_bpf_loader_program ); #[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] @@ -86,75 +91,6 @@ pub fn is_dup(accounts: &[KeyedAccount], keyed_account: &KeyedAccount) -> (bool, (false, 0) } -pub fn serialize_parameters( - program_id: &Pubkey, - keyed_accounts: &[KeyedAccount], - data: &[u8], -) -> Result, InstructionError> { - assert_eq!(32, mem::size_of::()); - - let mut v: Vec = Vec::new(); - v.write_u64::(keyed_accounts.len() as u64) - .unwrap(); - for (i, keyed_account) in keyed_accounts.iter().enumerate() { - let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account); - if is_dup { - v.write_u8(position as u8).unwrap(); - } else { - v.write_u8(std::u8::MAX).unwrap(); - v.write_u8(keyed_account.signer_key().is_some() as u8) - .unwrap(); - v.write_u8(keyed_account.is_writable() as u8).unwrap(); - v.write_all(keyed_account.unsigned_key().as_ref()).unwrap(); - v.write_u64::(keyed_account.lamports()?) - .unwrap(); - v.write_u64::(keyed_account.data_len()? as u64) - .unwrap(); - v.write_all(&keyed_account.try_account_ref()?.data).unwrap(); - v.write_all(keyed_account.owner()?.as_ref()).unwrap(); - v.write_u8(keyed_account.executable()? as u8).unwrap(); - v.write_u64::(keyed_account.rent_epoch()? as u64) - .unwrap(); - } - } - v.write_u64::(data.len() as u64).unwrap(); - v.write_all(data).unwrap(); - v.write_all(program_id.as_ref()).unwrap(); - Ok(v) -} - -pub fn deserialize_parameters( - keyed_accounts: &[KeyedAccount], - buffer: &[u8], -) -> Result<(), InstructionError> { - assert_eq!(32, mem::size_of::()); - - let mut start = mem::size_of::(); // number of accounts - for (i, keyed_account) in keyed_accounts.iter().enumerate() { - let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account); - start += 1; // is_dup - if !is_dup { - start += mem::size_of::(); // is_signer - start += mem::size_of::(); // is_writable - start += mem::size_of::(); // pubkey - keyed_account.try_account_ref_mut()?.lamports = - LittleEndian::read_u64(&buffer[start..]); - start += mem::size_of::() // lamports - + mem::size_of::(); // data length - let end = start + keyed_account.data_len()?; - keyed_account - .try_account_ref_mut()? - .data - .clone_from_slice(&buffer[start..end]); - start += keyed_account.data_len()? // data - + mem::size_of::() // owner - + mem::size_of::() // executable - + mem::size_of::(); // rent_epoch - } - } - Ok(()) -} - macro_rules! log{ ($logger:ident, $message:expr) => { if let Ok(mut logger) = $logger.try_borrow_mut() { @@ -176,7 +112,7 @@ pub fn process_instruction( instruction_data: &[u8], invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { - debug_assert!(bpf_loader::check_id(program_id)); + debug_assert!(bpf_loader::check_id(program_id) || bpf_loader_deprecated::check_id(program_id)); let logger = invoke_context.get_logger(); @@ -191,6 +127,7 @@ pub fn process_instruction( let parameter_accounts = keyed_accounts_iter.as_slice(); let parameter_bytes = serialize_parameters( + program_id, program.unsigned_key(), parameter_accounts, &instruction_data, @@ -236,7 +173,7 @@ pub fn process_instruction( } } } - deserialize_parameters(parameter_accounts, ¶meter_bytes)?; + deserialize_parameters(program_id, parameter_accounts, ¶meter_bytes)?; log!(logger, "BPF program {} success", program.unsigned_key()); } else if !keyed_accounts.is_empty() { match limited_deserialize(instruction_data)? { @@ -349,6 +286,7 @@ mod tests { // Ensure that we can invoke this macro from the same crate // where it is defined. solana_bpf_loader_program!(); + solana_bpf_loader_deprecated_program!(); } #[test] @@ -440,7 +378,7 @@ mod tests { fn test_bpf_loader_finalize() { let program_id = Pubkey::new_rand(); let program_key = Pubkey::new_rand(); - let mut file = File::open("test_elfs/noop.so").expect("file open failed"); + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); let mut elf = Vec::new(); let rent = Rent::default(); file.read_to_end(&mut elf).unwrap(); @@ -506,7 +444,7 @@ mod tests { let program_key = Pubkey::new_rand(); // Create program account - let mut file = File::open("test_elfs/noop.so").expect("file open failed"); + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); let program_account = Account::new_ref(1, 0, &program_id); @@ -580,6 +518,94 @@ mod tests { ); } + #[test] + fn test_bpf_loader_serialize_unaligned() { + let program_id = Pubkey::new_rand(); + let program_key = Pubkey::new_rand(); + + // Create program account + let mut file = File::open("test_elfs/noop_unaligned.so").expect("file open failed"); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let program_account = Account::new_ref(1, 0, &program_id); + program_account.borrow_mut().data = elf; + program_account.borrow_mut().executable = true; + let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + + // Case: With program and parameter account + let parameter_account = Account::new_ref(1, 0, &program_id); + keyed_accounts.push(KeyedAccount::new(&program_key, false, ¶meter_account)); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_deprecated::id(), + &keyed_accounts, + &[], + &mut MockInvokeContext::default() + ) + ); + + // Case: With duplicate accounts + let duplicate_key = Pubkey::new_rand(); + let parameter_account = Account::new_ref(1, 0, &program_id); + let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, ¶meter_account)); + keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, ¶meter_account)); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_deprecated::id(), + &keyed_accounts, + &[], + &mut MockInvokeContext::default() + ) + ); + } + + #[test] + fn test_bpf_loader_serialize_aligned() { + let program_id = Pubkey::new_rand(); + let program_key = Pubkey::new_rand(); + + // Create program account + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let program_account = Account::new_ref(1, 0, &program_id); + program_account.borrow_mut().data = elf; + program_account.borrow_mut().executable = true; + let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + + // Case: With program and parameter account + let parameter_account = Account::new_ref(1, 0, &program_id); + keyed_accounts.push(KeyedAccount::new(&program_key, false, ¶meter_account)); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader::id(), + &keyed_accounts, + &[], + &mut MockInvokeContext::default() + ) + ); + + // Case: With duplicate accounts + let duplicate_key = Pubkey::new_rand(); + let parameter_account = Account::new_ref(1, 0, &program_id); + let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; + keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, ¶meter_account)); + keyed_accounts.push(KeyedAccount::new(&duplicate_key, false, ¶meter_account)); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader::id(), + &keyed_accounts, + &[], + &mut MockInvokeContext::default() + ) + ); + } + /// fuzzing utility function fn fuzz( bytes: &[u8], @@ -610,7 +636,7 @@ mod tests { let program_key = Pubkey::new_rand(); // Create program account - let mut file = File::open("test_elfs/noop.so").expect("file open failed"); + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); diff --git a/programs/bpf_loader/src/serialization.rs b/programs/bpf_loader/src/serialization.rs new file mode 100644 index 0000000000..87137efa1a --- /dev/null +++ b/programs/bpf_loader/src/serialization.rs @@ -0,0 +1,410 @@ +use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; +use solana_sdk::{ + account::KeyedAccount, bpf_loader_deprecated, instruction::InstructionError, pubkey::Pubkey, +}; +use std::{ + io::prelude::*, + mem::{self, align_of}, +}; + +/// Look for a duplicate account and return its position if found +pub fn is_dup(accounts: &[KeyedAccount], keyed_account: &KeyedAccount) -> (bool, usize) { + for (i, account) in accounts.iter().enumerate() { + if account == keyed_account { + return (true, i); + } + } + (false, 0) +} + +pub fn serialize_parameters( + loader_id: &Pubkey, + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + data: &[u8], +) -> Result, InstructionError> { + if *loader_id == bpf_loader_deprecated::id() { + serialize_parameters_unaligned(program_id, keyed_accounts, data) + } else { + serialize_parameters_aligned(program_id, keyed_accounts, data) + } +} + +pub fn deserialize_parameters( + loader_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + buffer: &[u8], +) -> Result<(), InstructionError> { + if *loader_id == bpf_loader_deprecated::id() { + deserialize_parameters_unaligned(keyed_accounts, buffer) + } else { + deserialize_parameters_aligned(keyed_accounts, buffer) + } +} + +pub fn serialize_parameters_unaligned( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], +) -> Result, InstructionError> { + assert_eq!(32, mem::size_of::()); + + let mut v: Vec = Vec::new(); + v.write_u64::(keyed_accounts.len() as u64) + .unwrap(); + for (i, keyed_account) in keyed_accounts.iter().enumerate() { + let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account); + if is_dup { + v.write_u8(position as u8).unwrap(); + } else { + v.write_u8(std::u8::MAX).unwrap(); + v.write_u8(keyed_account.signer_key().is_some() as u8) + .unwrap(); + v.write_u8(keyed_account.is_writable() as u8).unwrap(); + v.write_all(keyed_account.unsigned_key().as_ref()).unwrap(); + v.write_u64::(keyed_account.lamports()?) + .unwrap(); + v.write_u64::(keyed_account.data_len()? as u64) + .unwrap(); + v.write_all(&keyed_account.try_account_ref()?.data).unwrap(); + v.write_all(keyed_account.owner()?.as_ref()).unwrap(); + v.write_u8(keyed_account.executable()? as u8).unwrap(); + v.write_u64::(keyed_account.rent_epoch()? as u64) + .unwrap(); + } + } + v.write_u64::(instruction_data.len() as u64) + .unwrap(); + v.write_all(instruction_data).unwrap(); + v.write_all(program_id.as_ref()).unwrap(); + Ok(v) +} + +pub fn deserialize_parameters_unaligned( + keyed_accounts: &[KeyedAccount], + buffer: &[u8], +) -> Result<(), InstructionError> { + assert_eq!(32, mem::size_of::()); + + let mut start = mem::size_of::(); // number of accounts + for (i, keyed_account) in keyed_accounts.iter().enumerate() { + let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account); + start += 1; // is_dup + if !is_dup { + start += mem::size_of::(); // is_signer + start += mem::size_of::(); // is_writable + start += mem::size_of::(); // pubkey + keyed_account.try_account_ref_mut()?.lamports = + LittleEndian::read_u64(&buffer[start..]); + start += mem::size_of::() // lamports + + mem::size_of::(); // data length + let end = start + keyed_account.data_len()?; + keyed_account + .try_account_ref_mut()? + .data + .clone_from_slice(&buffer[start..end]); + start += keyed_account.data_len()? // data + + mem::size_of::() // owner + + mem::size_of::() // executable + + mem::size_of::(); // rent_epoch + } + } + Ok(()) +} + +pub fn serialize_parameters_aligned( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], +) -> Result, InstructionError> { + assert_eq!(32, mem::size_of::()); + + // TODO use with capacity would be nice, but don't know account data sizes... + let mut v: Vec = Vec::new(); + v.write_u64::(keyed_accounts.len() as u64) + .unwrap(); + + // TODO panic? + if v.as_ptr().align_offset(align_of::()) != 0 { + panic!(); + } + for (i, keyed_account) in keyed_accounts.iter().enumerate() { + let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account); + if is_dup { + v.write_u8(position as u8).unwrap(); + v.write_all(&[0u8, 0, 0, 0, 0, 0, 0]).unwrap(); // 7 bytes of padding to make 64-bit aligned + } else { + v.write_u8(std::u8::MAX).unwrap(); + v.write_u8(keyed_account.signer_key().is_some() as u8) + .unwrap(); + v.write_u8(keyed_account.is_writable() as u8).unwrap(); + v.write_u8(keyed_account.executable()? as u8).unwrap(); + v.write_all(&[0u8, 0, 0, 0]).unwrap(); // 4 bytes of padding to make 128-bit aligned + v.write_all(keyed_account.unsigned_key().as_ref()).unwrap(); + v.write_all(keyed_account.owner()?.as_ref()).unwrap(); + v.write_u64::(keyed_account.lamports()?) + .unwrap(); + v.write_u64::(keyed_account.data_len()? as u64) + .unwrap(); + v.write_all(&keyed_account.try_account_ref()?.data).unwrap(); + for _ in 0..16 - (v.len() % 16) { + v.write_u8(0).unwrap(); // 128 bit aligned again + } + v.write_u64::(keyed_account.rent_epoch()? as u64) + .unwrap(); + } + } + v.write_u64::(instruction_data.len() as u64) + .unwrap(); + v.write_all(instruction_data).unwrap(); + v.write_all(program_id.as_ref()).unwrap(); + Ok(v) +} + +pub fn deserialize_parameters_aligned( + keyed_accounts: &[KeyedAccount], + buffer: &[u8], +) -> Result<(), InstructionError> { + assert_eq!(32, mem::size_of::()); + + let mut start = mem::size_of::(); // number of accounts + for (i, keyed_account) in keyed_accounts.iter().enumerate() { + let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account); + start += 1; // is_dup + if !is_dup { + start += mem::size_of::() // is_signer + + mem::size_of::() // is_writable + + mem::size_of::() // executable + + 4 // padding + + mem::size_of::() // pubkey + + mem::size_of::(); // owner + keyed_account.try_account_ref_mut()?.lamports = + LittleEndian::read_u64(&buffer[start..]); + start += mem::size_of::() // lamports + + mem::size_of::(); // data length + let end = start + keyed_account.data_len()?; + keyed_account + .try_account_ref_mut()? + .data + .clone_from_slice(&buffer[start..end]); + start += keyed_account.data_len()?; // data + start += 16 - (start % 16); // padding + start += mem::size_of::(); // rent_epoch + } else { + start += 7; // padding + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::{ + account::Account, account_info::AccountInfo, bpf_loader, entrypoint::deserialize, + }; + use std::{ + cell::RefCell, + mem::size_of, + rc::Rc, + // Hide Result from bindgen gets confused about generics in non-generic type declarations + slice::{from_raw_parts, from_raw_parts_mut}, + }; + + #[test] + fn test_serialize_parameters() { + let program_id = Pubkey::new_rand(); + let dup_key = Pubkey::new_rand(); + let keys = vec![dup_key, dup_key, Pubkey::new_rand(), Pubkey::new_rand()]; + let accounts = [ + RefCell::new(Account { + lamports: 1, + data: vec![1u8, 2, 3, 4, 5], + owner: bpf_loader::id(), + executable: false, + rent_epoch: 100, + }), + // dup of first + RefCell::new(Account { + lamports: 1, + data: vec![1u8, 2, 3, 4, 5], + owner: bpf_loader::id(), + executable: false, + rent_epoch: 100, + }), + RefCell::new(Account { + lamports: 2, + data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19], + owner: bpf_loader::id(), + executable: true, + rent_epoch: 200, + }), + RefCell::new(Account { + lamports: 3, + data: vec![], + owner: bpf_loader::id(), + executable: false, + rent_epoch: 3100, + }), + ]; + + let keyed_accounts: Vec<_> = keys + .iter() + .zip(&accounts) + .map(|(key, account)| KeyedAccount::new(&key, false, &account)) + .collect(); + let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + + // check serialize_parameters_aligned + + let mut serialized = serialize_parameters( + &bpf_loader::id(), + &program_id, + &keyed_accounts, + &instruction_data, + ) + .unwrap(); + let (de_program_id, de_accounts, de_instruction_data) = + unsafe { deserialize(&mut serialized[0] as *mut u8) }; + + assert_eq!(&program_id, de_program_id); + assert_eq!(instruction_data, de_instruction_data); + assert_eq!( + (&de_instruction_data[0] as *const u8).align_offset(align_of::()), + 0 + ); + for ((account, account_info), key) in accounts.iter().zip(de_accounts).zip(keys.clone()) { + assert_eq!(key, *account_info.key); + let account = account.borrow(); + assert_eq!(account.lamports, account_info.lamports()); + assert_eq!(&account.data[..], &account_info.data.borrow()[..]); + assert_eq!(&account.owner, account_info.owner); + assert_eq!(account.executable, account_info.executable); + assert_eq!(account.rent_epoch, account_info.rent_epoch); + + assert_eq!( + (*account_info.lamports.borrow() as *const u64).align_offset(align_of::()), + 0 + ); + assert_eq!( + account_info + .data + .borrow() + .as_ptr() + .align_offset(align_of::()), + 0 + ); + } + + // check serialize_parameters_unaligned + + let mut serialized = serialize_parameters( + &bpf_loader_deprecated::id(), + &program_id, + &keyed_accounts, + &instruction_data, + ) + .unwrap(); + let (de_program_id, de_accounts, de_instruction_data) = + unsafe { deserialize_unaligned(&mut serialized[0] as *mut u8) }; + + assert_eq!(&program_id, de_program_id); + assert_eq!(instruction_data, de_instruction_data); + for ((account, account_info), key) in accounts.iter().zip(de_accounts).zip(keys) { + assert_eq!(key, *account_info.key); + let account = account.borrow(); + assert_eq!(account.lamports, account_info.lamports()); + assert_eq!(&account.data[..], &account_info.data.borrow()[..]); + assert_eq!(&account.owner, account_info.owner); + assert_eq!(account.executable, account_info.executable); + assert_eq!(account.rent_epoch, account_info.rent_epoch); + } + } + + // the old bpf_loader in-program deserializer bpf_loader::id() + #[allow(clippy::type_complexity)] + pub unsafe fn deserialize_unaligned<'a>( + input: *mut u8, + ) -> (&'a Pubkey, Vec>, &'a [u8]) { + let mut offset: usize = 0; + + // number of accounts present + + #[allow(clippy::cast_ptr_alignment)] + let num_accounts = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + // account Infos + + let mut accounts = Vec::with_capacity(num_accounts); + for _ in 0..num_accounts { + let dup_info = *(input.add(offset) as *const u8); + offset += size_of::(); + if dup_info == std::u8::MAX { + #[allow(clippy::cast_ptr_alignment)] + let is_signer = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let is_writable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + let key: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64))); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let data = Rc::new(RefCell::new({ + from_raw_parts_mut(input.add(offset), data_len) + })); + offset += data_len; + + let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let executable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let rent_epoch = *(input.add(offset) as *const u64); + offset += size_of::(); + + accounts.push(AccountInfo { + is_signer, + is_writable, + key, + lamports, + data, + owner, + executable, + rent_epoch, + }); + } else { + // duplicate account, clone the original + accounts.push(accounts[dup_info as usize].clone()); + } + } + + // instruction data + + #[allow(clippy::cast_ptr_alignment)] + let instruction_data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) }; + offset += instruction_data_len; + + // program Id + + let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey); + + (program_id, accounts, instruction_data) + } +} diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 954b712892..4f23d1890a 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -50,6 +50,8 @@ pub enum SyscallError { InstructionError(InstructionError), #[error("Cross-program invocation with unauthorized signer or writable account")] PrivilegeEscalation, + #[error("Unaligned pointer")] + UnalignedPointer, } impl From for EbpfError { fn from(error: SyscallError) -> Self { @@ -141,20 +143,24 @@ macro_rules! translate { #[macro_export] macro_rules! translate_type_mut { - ($t:ty, $vm_addr:expr, $regions:expr) => { - unsafe { - match translate_addr::( - $vm_addr as u64, - size_of::<$t>(), - file!(), - line!() as usize - ELF_INSN_DUMP_OFFSET + 1, - $regions, - ) { - Ok(value) => Ok(&mut *(value as *mut $t)), - Err(e) => Err(e), + ($t:ty, $vm_addr:expr, $regions:expr) => {{ + if ($vm_addr as u64 as *mut $t).align_offset(align_of::<$t>()) != 0 { + Err(SyscallError::UnalignedPointer.into()) + } else { + unsafe { + match translate_addr::( + $vm_addr as u64, + size_of::<$t>(), + file!(), + line!() as usize - ELF_INSN_DUMP_OFFSET + 1, + $regions, + ) { + Ok(value) => Ok(&mut *(value as *mut $t)), + Err(e) => Err(e), + } } } - }; + }}; } #[macro_export] macro_rules! translate_type { @@ -168,18 +174,22 @@ macro_rules! translate_type { #[macro_export] macro_rules! translate_slice_mut { - ($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => { - match translate_addr::( - $vm_addr as u64, - $len as usize * size_of::<$t>(), - file!(), - line!() as usize - ELF_INSN_DUMP_OFFSET + 1, - $regions, - ) { - Ok(value) => Ok(unsafe { from_raw_parts_mut(value as *mut $t, $len as usize) }), - Err(e) => Err(e), + ($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {{ + if ($vm_addr as u64 as *mut $t).align_offset(align_of::<$t>()) != 0 { + Err(SyscallError::UnalignedPointer.into()) + } else { + match translate_addr::( + $vm_addr as u64, + $len as usize * size_of::<$t>(), + file!(), + line!() as usize - ELF_INSN_DUMP_OFFSET + 1, + $regions, + ) { + Ok(value) => Ok(unsafe { from_raw_parts_mut(value as *mut $t, $len as usize) }), + Err(e) => Err(e), + } } - }; + }}; } #[macro_export] macro_rules! translate_slice { @@ -456,7 +466,7 @@ impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> { let lamports_ref = { // Double translate lamports out of RefCell let ptr = translate_type!(u64, account_info.lamports.as_ptr(), ro_regions)?; - translate_type_mut!(u64, *(ptr as *const u64), rw_regions)? + translate_type_mut!(u64, *ptr, rw_regions)? }; let data = { // Double translate data out of RefCell @@ -918,13 +928,17 @@ mod tests { vec![AccountMeta::new(Pubkey::new_rand(), false)], ); let addr = &instruction as *const _ as u64; - let regions = vec![MemoryRegion { + let mut regions = vec![MemoryRegion { addr_host: addr, - addr_vm: 100, + addr_vm: 96, len: std::mem::size_of::() as u64, }]; - let translated_instruction = translate_type!(Instruction, 100, ®ions).unwrap(); + let translated_instruction = translate_type!(Instruction, 96, ®ions).unwrap(); assert_eq!(instruction, *translated_instruction); + regions[0].len = 1; + assert!(translate_type!(Instruction, 100, ®ions).is_err()); + regions[0].len = std::mem::size_of::() as u64 * 2; + assert!(translate_type!(Instruction, 100, ®ions).is_err()); } #[test] diff --git a/programs/bpf_loader/test_elfs/noop_aligned.so b/programs/bpf_loader/test_elfs/noop_aligned.so new file mode 100755 index 0000000000000000000000000000000000000000..28bbdc0980a7023ccb04591967638007c3d82d1e GIT binary patch literal 58088 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

i3Iscm&~-rKfB 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 upUAUKl6U;~a1^Keo8%f{&_5Xivok+w0 literal 0 HcmV?d00001 diff --git a/programs/bpf_loader/test_elfs/noop.so b/programs/bpf_loader/test_elfs/noop_unaligned.so similarity index 100% rename from programs/bpf_loader/test_elfs/noop.so rename to programs/bpf_loader/test_elfs/noop_unaligned.so diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 83f26f6ced..7bae5304e7 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -289,12 +289,15 @@ static bool sol_deserialize( if (dup_info == UINT8_MAX) { input += sizeof(uint8_t); input += sizeof(uint8_t); - input += sizeof(SolPubkey); - input += sizeof(uint64_t); - input += *(uint64_t *) input; - input += sizeof(uint64_t); - input += sizeof(SolPubkey); input += sizeof(uint8_t); + input += 4; // padding + input += sizeof(SolPubkey); + input += sizeof(SolPubkey); + input += sizeof(uint64_t); + uint64_t data_len = *(uint64_t *) input; + input += sizeof(uint64_t); + input += data_len; + input += 16 - (data_len % 16); // padding input += sizeof(uint64_t); } continue; @@ -308,10 +311,20 @@ static bool sol_deserialize( params->ka[i].is_writable = *(uint8_t *) input != 0; input += sizeof(uint8_t); + // executable? + params->ka[i].executable = *(uint8_t *) input; + input += sizeof(uint8_t); + + input += 4; // padding + // key params->ka[i].key = (SolPubkey *) input; input += sizeof(SolPubkey); + // owner + params->ka[i].owner = (SolPubkey *) input; + input += sizeof(SolPubkey); + // lamports params->ka[i].lamports = (uint64_t *) input; input += sizeof(uint64_t); @@ -322,26 +335,22 @@ static bool sol_deserialize( params->ka[i].data = (uint8_t *) input; input += params->ka[i].data_len; - // owner - params->ka[i].owner = (SolPubkey *) input; - input += sizeof(SolPubkey); - - // executable? - params->ka[i].executable = *(uint8_t *) input; - input += sizeof(uint8_t); + input += 16 - (params->ka[i].data_len % 16); // padding // rent epoch params->ka[i].rent_epoch = *(uint64_t *) input; input += sizeof(uint64_t); } else { params->ka[i].is_signer = params->ka[dup_info].is_signer; + params->ka[i].is_writable = params->ka[dup_info].is_writable; + params->ka[i].executable = params->ka[dup_info].executable; params->ka[i].key = params->ka[dup_info].key; + params->ka[i].owner = params->ka[dup_info].owner; params->ka[i].lamports = params->ka[dup_info].lamports; params->ka[i].data_len = params->ka[dup_info].data_len; params->ka[i].data = params->ka[dup_info].data; - params->ka[i].owner = params->ka[dup_info].owner; - params->ka[i].executable = params->ka[dup_info].executable; params->ka[i].rent_epoch = params->ka[dup_info].rent_epoch; + input += 7; // padding } } diff --git a/sdk/src/bpf_loader.rs b/sdk/src/bpf_loader.rs index 33461caa0a..cf6fa767b9 100644 --- a/sdk/src/bpf_loader.rs +++ b/sdk/src/bpf_loader.rs @@ -1 +1 @@ -crate::declare_id!("BPFLoader1111111111111111111111111111111111"); +crate::declare_id!("BPFLoader2111111111111111111111111111111111"); diff --git a/sdk/src/bpf_loader_deprecated.rs b/sdk/src/bpf_loader_deprecated.rs new file mode 100644 index 0000000000..33461caa0a --- /dev/null +++ b/sdk/src/bpf_loader_deprecated.rs @@ -0,0 +1 @@ +crate::declare_id!("BPFLoader1111111111111111111111111111111111"); diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 5bc7da2ab3..6be53a323d 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -76,9 +76,18 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec(); + #[allow(clippy::cast_ptr_alignment)] + let executable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + offset += 4; // padding + let key: &Pubkey = &*(input.add(offset) as *const Pubkey); offset += size_of::(); + let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + #[allow(clippy::cast_ptr_alignment)] let lamports = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64))); offset += size_of::(); @@ -92,12 +101,7 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec(); - - #[allow(clippy::cast_ptr_alignment)] - let executable = *(input.add(offset) as *const u8) != 0; - offset += size_of::(); + offset += 16 - (offset % 16); // padding #[allow(clippy::cast_ptr_alignment)] let rent_epoch = *(input.add(offset) as *const u64); @@ -114,6 +118,8 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec { + ($name:ident, $filename:ident, $id:path) => { #[macro_export] macro_rules! $name { () => { @@ -66,8 +66,8 @@ macro_rules! declare_name { // `respan!` respans the path `$crate::id`, which we then call (hence the extra // parens) ( - stringify!($name).to_string(), - ::solana_sdk::respan!($crate::id, $name)(), + stringify!($filename).to_string(), + ::solana_sdk::respan!($crate::$id, $name)(), ) }; } @@ -77,11 +77,11 @@ macro_rules! declare_name { #[rustversion::not(since(1.46.0))] #[macro_export] macro_rules! declare_name { - ($name:ident) => { + ($name:ident, $filename:ident, $id:path) => { #[macro_export] macro_rules! $name { () => { - (stringify!($name).to_string(), $crate::id()) + (stringify!($filename).to_string(), $crate::$id()) }; } }; @@ -90,8 +90,10 @@ macro_rules! declare_name { /// Convenience macro to declare a native program /// /// bs58_string: bs58 string representation the program's id -/// name: Name of the program, must match the library name in Cargo.toml +/// name: Name of the program +/// filename: must match the library name in Cargo.toml /// entrypoint: Program's entrypoint, must be of `type Entrypoint` +/// id: Path to the program id access function, used if not called in `src/lib` /// /// # Examples /// @@ -159,7 +161,7 @@ macro_rules! declare_name { macro_rules! declare_program( ($bs58_string:expr, $name:ident, $entrypoint:expr) => ( $crate::declare_id!($bs58_string); - $crate::declare_name!($name); + $crate::declare_name!($name, $name, id); #[no_mangle] pub extern "C" fn $name( @@ -174,12 +176,16 @@ macro_rules! declare_program( /// Same as declare_program but for native loaders #[macro_export] -macro_rules! declare_loader( - ($bs58_string:expr, $name:ident, $entrypoint:expr) => ( +macro_rules! declare_loader { + ($bs58_string:expr, $name:ident, $entrypoint:expr) => { + $crate::declare_loader!($bs58_string, $name, $entrypoint, $name, id); + }; + ($bs58_string:expr, $name:ident, $entrypoint:expr, $filename:ident) => { + $crate::declare_loader!($bs58_string, $name, $entrypoint, $filename, id); + }; + ($bs58_string:expr, $name:ident, $entrypoint:expr, $filename:ident, $id:path) => { $crate::declare_id!($bs58_string); - $crate::declare_name!($name); - - + $crate::declare_name!($name, $filename, $id); #[no_mangle] pub extern "C" fn $name( @@ -190,8 +196,8 @@ macro_rules! declare_loader( ) -> Result<(), $crate::instruction::InstructionError> { $entrypoint(program_id, keyed_accounts, instruction_data, invoke_context) } - ) -); + }; +} pub type ProcessInstruction = fn(&Pubkey, &[KeyedAccount], &[u8]) -> Result<(), InstructionError>; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 0738ea7f9d..e8e69e92b9 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -12,6 +12,7 @@ pub mod abi_example; pub mod account; pub mod account_utils; pub mod bpf_loader; +pub mod bpf_loader_deprecated; pub mod clock; pub mod commitment_config; pub mod decode_error;