diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index d154439e0e..0c99a29fb2 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2067,6 +2067,13 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-bpf-rust-ro-modify" +version = "1.5.0" +dependencies = [ + "solana-program", +] + [[package]] name = "solana-bpf-rust-sanity" version = "1.5.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 8a561b7cf0..961a7b6337 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -58,6 +58,7 @@ members = [ "rust/param_passing_dep", "rust/rand", "rust/ristretto", + "rust/ro_modify", "rust/sanity", "rust/sha256", "rust/spoof1", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index ea2f4743d9..a7cfae5a99 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -79,6 +79,7 @@ fn main() { "param_passing", "rand", "ristretto", + "ro_modify", "sanity", "sha256", "spoof1", diff --git a/programs/bpf/rust/ro_modify/Cargo.toml b/programs/bpf/rust/ro_modify/Cargo.toml new file mode 100644 index 0000000000..a7e4f7690f --- /dev/null +++ b/programs/bpf/rust/ro_modify/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "solana-bpf-rust-ro-modify" +version = "1.5.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "1.5.0" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/ro_modify/Xargo.toml b/programs/bpf/rust/ro_modify/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/ro_modify/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/ro_modify/src/lib.rs b/programs/bpf/rust/ro_modify/src/lib.rs new file mode 100644 index 0000000000..d55179a729 --- /dev/null +++ b/programs/bpf/rust/ro_modify/src/lib.rs @@ -0,0 +1,222 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, program::invoke, + program_error::ProgramError, pubkey::Pubkey, system_instruction, +}; + +#[derive(Debug)] +#[repr(C)] +struct SolInstruction { + program_id_addr: u64, + accounts_addr: u64, + accounts_len: usize, + data_addr: u64, + data_len: usize, +} + +/// Rust representation of C's SolAccountMeta +#[derive(Debug)] +#[repr(C)] +struct SolAccountMeta { + pubkey_addr: u64, + is_writable: bool, + is_signer: bool, +} + +/// Rust representation of C's SolAccountInfo +#[derive(Debug, Clone)] +#[repr(C)] +struct SolAccountInfo { + key_addr: u64, + lamports_addr: u64, + data_len: u64, + data_addr: u64, + owner_addr: u64, + rent_epoch: u64, + is_signer: bool, + is_writable: bool, + executable: bool, +} + +/// Rust representation of C's SolSignerSeed +#[derive(Debug)] +#[repr(C)] +struct SolSignerSeedC { + addr: u64, + len: u64, +} + +/// Rust representation of C's SolSignerSeeds +#[derive(Debug)] +#[repr(C)] +struct SolSignerSeedsC { + addr: u64, + len: u64, +} + +extern "C" { + fn sol_invoke_signed_c( + instruction_addr: *const SolInstruction, + account_infos_addr: *const SolAccountInfo, + account_infos_len: u64, + signers_seeds_addr: *const SolSignerSeedsC, + signers_seeds_len: u64, + ) -> u64; +} + +const READONLY_ACCOUNTS: &[SolAccountInfo] = &[ + SolAccountInfo { + is_signer: false, + is_writable: false, + executable: true, + key_addr: 0x400000010, + owner_addr: 0x400000030, + lamports_addr: 0x400000050, + rent_epoch: 0, + data_addr: 0x400000060, + data_len: 14, + }, + SolAccountInfo { + is_signer: true, + is_writable: true, + executable: false, + key_addr: 0x400002880, + owner_addr: 0x4000028A0, + lamports_addr: 0x4000028c0, + rent_epoch: 0, + data_addr: 0x4000028d0, + data_len: 0, + }, +]; + +const PUBKEY: Pubkey = Pubkey::new_from_array([ + 0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]); + +fn check_preconditions( + in_infos: &[AccountInfo], + static_infos: &[SolAccountInfo], +) -> Result<(), ProgramError> { + for (in_info, static_info) in in_infos.iter().zip(static_infos) { + check!(in_info.key.as_ref().as_ptr() as u64, static_info.key_addr); + check!( + in_info.owner.as_ref().as_ptr() as u64, + static_info.owner_addr + ); + check!( + unsafe { *in_info.lamports.as_ptr() as *const u64 as u64 }, + static_info.lamports_addr + ); + check!( + in_info.try_borrow_data()?.as_ptr() as u64, + static_info.data_addr + ); + check!(in_info.data_len() as u64, static_info.data_len); + } + Ok(()) +} + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + check_preconditions(accounts, READONLY_ACCOUNTS)?; + + match instruction_data[0] { + 1 => { + let system_instruction = system_instruction::allocate(accounts[1].key, 42); + let metas = &[SolAccountMeta { + is_signer: true, + is_writable: true, + pubkey_addr: accounts[1].key as *const _ as u64, + }]; + let instruction = SolInstruction { + accounts_addr: metas.as_ptr() as u64, + accounts_len: metas.len(), + data_addr: system_instruction.data.as_ptr() as u64, + data_len: system_instruction.data.len(), + program_id_addr: accounts[0].key as *const Pubkey as u64, + }; + unsafe { + check!( + 0, + sol_invoke_signed_c( + &instruction as *const _, + READONLY_ACCOUNTS.as_ptr(), + READONLY_ACCOUNTS.len() as u64, + std::ptr::null::(), + 0, + ) + ); + } + let ptr = &READONLY_ACCOUNTS[1].data_len as *const _ as u64 as *mut u64; + check!(42, unsafe { read_val(ptr) }); + } + 2 => { + // Not sure how to get a const data length in an Rc> + } + 3 => { + let mut new_accounts = + &mut [READONLY_ACCOUNTS[0].clone(), READONLY_ACCOUNTS[1].clone()]; + new_accounts[1].owner_addr = &PUBKEY as *const _ as u64; + let system_instruction = system_instruction::assign(accounts[1].key, program_id); + let metas = &[SolAccountMeta { + is_signer: true, + is_writable: true, + pubkey_addr: accounts[1].key as *const _ as u64, + }]; + let instruction = SolInstruction { + accounts_addr: metas.as_ptr() as u64, + accounts_len: metas.len(), + data_addr: system_instruction.data.as_ptr() as u64, + data_len: system_instruction.data.len(), + program_id_addr: accounts[0].key as *const Pubkey as u64, + }; + unsafe { + check!( + 0, + sol_invoke_signed_c( + &instruction as *const _, + new_accounts.as_ptr(), + new_accounts.len() as u64, + std::ptr::null::(), + 0, + ) + ); + } + } + 4 => { + let mut new_account = accounts[1].clone(); + new_account.owner = &PUBKEY; + let instruction = system_instruction::assign(accounts[1].key, program_id); + invoke(&instruction, &[accounts[0].clone(), new_account])?; + } + _ => check!(0, 1), + } + + Ok(()) +} + +#[macro_export] +macro_rules! check { + ($left:expr, $right:expr) => { + if $left != $right { + msg!( + "Condition failure: {:?} != {:?} at line {:?}", + $left, + $right, + line!() + ); + return Err(ProgramError::Custom(0)); + } + }; +} + +/// Skirt the compiler and force a read from a const value +/// # Safety +#[inline(never)] +pub unsafe fn read_val(ptr: *mut T) -> T { + *ptr +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index f5e25e7c48..4e95d92f83 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -835,6 +835,65 @@ fn test_program_bpf_invoke() { } } +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_ro_modify() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin(&name, id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let program_pubkey = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_ro_modify", + ); + + let test_keypair = Keypair::new(); + let account = Account::new(10, 0, &solana_sdk::system_program::id()); + bank.store_account(&test_keypair.pubkey(), &account); + + let account_metas = vec![ + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new(test_keypair.pubkey(), true), + ]; + + let instruction = Instruction::new(program_pubkey, &[1_u8], account_metas.clone()); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); + println!("result {:?}", result); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(0xb9f0002)) + ); + + let instruction = Instruction::new(program_pubkey, &[3_u8], account_metas.clone()); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); + println!("result {:?}", result); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(0xb9f0002)) + ); + + let instruction = Instruction::new(program_pubkey, &[4_u8], account_metas.clone()); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(0xb9f0002)) + ); +} + #[cfg(feature = "bpf_rust")] #[test] fn test_program_bpf_call_depth() { diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index b07b8bd965..ccdccc8754 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -905,7 +905,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { }; let owner = translate_type_mut::( memory_mapping, - AccessType::Load, + AccessType::Store, account_info.owner as *const _ as u64, self.loader_id, )?; @@ -920,11 +920,11 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> { let translated = translate( memory_mapping, AccessType::Load, - account_info.data.as_ptr() as *const _ as u64, + unsafe { (account_info.data.as_ptr() as *const u64).offset(1) as u64 }, 8, self.loader_id, )? as *mut u64; - let ref_to_len_in_vm = unsafe { &mut *translated.offset(1) }; + let ref_to_len_in_vm = unsafe { &mut *translated }; let ref_of_len_in_input_buffer = unsafe { data.as_ptr().offset(-8) }; let serialized_len_ptr = translate_type_mut::( memory_mapping, @@ -1168,6 +1168,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { account_infos_len, self.loader_id, )?; + let first_info_addr = &account_infos[0] as *const _ as u64; let mut accounts = Vec::with_capacity(message.account_keys.len()); let mut refs = Vec::with_capacity(message.account_keys.len()); 'root: for account_key in message.account_keys.iter() { @@ -1187,7 +1188,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { )?; let owner = translate_type_mut::( memory_mapping, - AccessType::Load, + AccessType::Store, account_info.owner_addr, self.loader_id, )?; @@ -1198,8 +1199,18 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> { account_info.data_len, self.loader_id, )?; - let ref_to_len_in_vm = - unsafe { &mut *(&account_info.data_len as *const u64 as u64 as *mut u64) }; + + let addr = &account_info.data_len as *const u64 as u64; + let vm_addr = account_infos_addr + (addr - first_info_addr); + let _ = translate( + memory_mapping, + AccessType::Store, + vm_addr, + size_of::() as u64, + self.loader_id, + )?; + let ref_to_len_in_vm = unsafe { &mut *(addr as *mut u64) }; + let ref_of_len_in_input_buffer = unsafe { (account_info.data_addr as *mut u8).offset(-8) }; let serialized_len_ptr = translate_type_mut::(