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 0000000000..28bbdc0980 Binary files /dev/null and b/programs/bpf_loader/test_elfs/noop_aligned.so differ 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;