diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 7816cc473b905b..b9d0651db76dfa 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -511,7 +511,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust { ) -> Result, Error> { let mut signers = Vec::new(); if signers_seeds_len > 0 { - let signers_seeds = translate_slice::<&[&[u8]]>( + let signers_seeds = translate_slice_of_slices::>( memory_mapping, signers_seeds_addr, signers_seeds_len, @@ -521,10 +521,10 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust { return Err(Box::new(SyscallError::TooManySigners)); } for signer_seeds in signers_seeds.iter() { - let untranslated_seeds = translate_slice::<&[u8]>( + let untranslated_seeds = translate_slice_of_slices::( memory_mapping, - signer_seeds.as_ptr() as *const _ as u64, - signer_seeds.len() as u64, + signer_seeds.ptr(), + signer_seeds.len(), invoke_context.get_check_aligned(), )?; if untranslated_seeds.len() > MAX_SEEDS { @@ -533,12 +533,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust { let seeds = untranslated_seeds .iter() .map(|untranslated_seed| { - translate_slice::( - memory_mapping, - untranslated_seed.as_ptr() as *const _ as u64, - untranslated_seed.len() as u64, - invoke_context.get_check_aligned(), - ) + untranslated_seed + .translate(memory_mapping, invoke_context.get_check_aligned()) }) .collect::, Error>>()?; let signer = Pubkey::create_program_address(&seeds, program_id) diff --git a/programs/bpf_loader/src/syscalls/logging.rs b/programs/bpf_loader/src/syscalls/logging.rs index 522145b1d71408..a80649d7cc8216 100644 --- a/programs/bpf_loader/src/syscalls/logging.rs +++ b/programs/bpf_loader/src/syscalls/logging.rs @@ -120,7 +120,7 @@ declare_builtin_function!( consume_compute_meter(invoke_context, budget.syscall_base_cost)?; - let untranslated_fields = translate_slice::<&[u8]>( + let untranslated_fields = translate_slice_of_slices::( memory_mapping, addr, len, @@ -137,18 +137,13 @@ declare_builtin_function!( invoke_context, untranslated_fields .iter() - .fold(0, |total, e| total.saturating_add(e.len() as u64)), + .fold(0, |total, e| total.saturating_add(e.len())), )?; let mut fields = Vec::with_capacity(untranslated_fields.len()); for untranslated_field in untranslated_fields { - fields.push(translate_slice::( - memory_mapping, - untranslated_field.as_ptr() as *const _ as u64, - untranslated_field.len() as u64, - invoke_context.get_check_aligned(), - )?); + fields.push(untranslated_field.translate(memory_mapping, invoke_context.get_check_aligned())?); } let log_collector = invoke_context.get_log_collector(); diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index 334272896ad177..618bd0273ba952 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -10,6 +10,7 @@ pub use self::{ SyscallGetSysvar, }, }; + #[allow(deprecated)] use { solana_bn254::prelude::{ @@ -59,6 +60,7 @@ use { solana_type_overrides::sync::Arc, std::{ alloc::Layout, + marker::PhantomData, mem::{align_of, size_of}, slice::from_raw_parts_mut, str::{from_utf8, Utf8Error}, @@ -226,6 +228,66 @@ impl HasherImpl for Keccak256Hasher { } } +// The VmSlice class is used for cases when you need a slice that is stored in the BPF +// interpreter's virtual address space. Because this source code can be compiled with +// addresses of different bit depths, we cannot assume that the 64-bit BPF interpreter's +// pointer sizes can be mapped to physical pointer sizes. In particular, if you need a +// slice-of-slices in the virtual space, the inner slices will be different sizes in a +// 32-bit app build than in the 64-bit virtual space. Therefore instead of a slice-of-slices, +// you should implement a slice-of-VmSlices, which can then use VmSlice::translate() to +// map to the physical address. +// This class must consist only of 16 bytes: a u64 ptr and a u64 len, to match the 64-bit +// implementation of a slice in Rust. The PhantomData entry takes up 0 bytes. +pub struct VmSlice { + ptr: u64, + len: u64, + resource_type: PhantomData, +} + +impl VmSlice { + pub fn new(ptr: u64, len: u64) -> Self { + VmSlice { + ptr, + len, + resource_type: PhantomData, + } + } + + pub fn ptr(&self) -> u64 { + self.ptr + } + pub fn len(&self) -> u64 { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Adjust the length of the vector. This is unchecked, and it assumes that the pointer + /// points to valid memory of the correct length after vm-translation. + pub fn resize(&mut self, len: u64) { + self.len = len; + } + + /// Returns a slice using a mapped physical address + pub fn translate( + &self, + memory_mapping: &MemoryMapping, + check_aligned: bool, + ) -> Result<&[T], Error> { + translate_slice::(memory_mapping, self.ptr, self.len, check_aligned) + } + + pub fn translate_mut( + &mut self, + memory_mapping: &MemoryMapping, + check_aligned: bool, + ) -> Result<&mut [T], Error> { + translate_slice_mut::(memory_mapping, self.ptr, self.len, check_aligned) + } +} + fn consume_compute_meter(invoke_context: &InvokeContext, amount: u64) -> Result<(), Error> { invoke_context.consume_checked(amount)?; Ok(()) @@ -618,6 +680,62 @@ fn translate_slice<'a, T>( .map(|value| &*value) } +fn translate_slice_of_slices_inner<'a, T>( + memory_mapping: &MemoryMapping, + access_type: AccessType, + vm_addr: u64, + len: u64, + check_aligned: bool, +) -> Result<&'a mut [VmSlice], Error> { + if len == 0 { + return Ok(&mut []); + } + + let total_size = len.saturating_mul(size_of::>() as u64); + if isize::try_from(total_size).is_err() { + return Err(SyscallError::InvalidLength.into()); + } + + let host_addr = translate(memory_mapping, access_type, vm_addr, total_size)?; + + if check_aligned && !address_is_aligned::>(host_addr) { + return Err(SyscallError::UnalignedPointer.into()); + } + Ok(unsafe { from_raw_parts_mut(host_addr as *mut VmSlice, len as usize) }) +} + +#[allow(dead_code)] +fn translate_slice_of_slices_mut<'a, T>( + memory_mapping: &MemoryMapping, + vm_addr: u64, + len: u64, + check_aligned: bool, +) -> Result<&'a mut [VmSlice], Error> { + translate_slice_of_slices_inner::( + memory_mapping, + AccessType::Store, + vm_addr, + len, + check_aligned, + ) +} + +fn translate_slice_of_slices<'a, T>( + memory_mapping: &MemoryMapping, + vm_addr: u64, + len: u64, + check_aligned: bool, +) -> Result<&'a [VmSlice], Error> { + translate_slice_of_slices_inner::( + memory_mapping, + AccessType::Load, + vm_addr, + len, + check_aligned, + ) + .map(|value| &*value) +} + /// Take a virtual pointer to a string (points to SBF VM memory space), translate it /// pass it to a user-defined work function fn translate_string_and_do( @@ -724,22 +842,17 @@ fn translate_and_check_program_address_inputs<'a>( check_aligned: bool, ) -> Result<(Vec<&'a [u8]>, &'a Pubkey), Error> { let untranslated_seeds = - translate_slice::<&[u8]>(memory_mapping, seeds_addr, seeds_len, check_aligned)?; + translate_slice_of_slices::(memory_mapping, seeds_addr, seeds_len, check_aligned)?; if untranslated_seeds.len() > MAX_SEEDS { return Err(SyscallError::BadSeeds(PubkeyError::MaxSeedLengthExceeded).into()); } let seeds = untranslated_seeds .iter() .map(|untranslated_seed| { - if untranslated_seed.len() > MAX_SEED_LEN { + if untranslated_seed.len() > MAX_SEED_LEN as u64 { return Err(SyscallError::BadSeeds(PubkeyError::MaxSeedLengthExceeded).into()); } - translate_slice::( - memory_mapping, - untranslated_seed.as_ptr() as *const _ as u64, - untranslated_seed.len() as u64, - check_aligned, - ) + untranslated_seed.translate(memory_mapping, check_aligned) }) .collect::, Error>>()?; let program_id = translate_type::(memory_mapping, program_id_addr, check_aligned)?; @@ -1795,7 +1908,7 @@ declare_builtin_function!( poseidon::HASH_BYTES as u64, invoke_context.get_check_aligned(), )?; - let inputs = translate_slice::<&[u8]>( + let inputs = translate_slice_of_slices::( memory_mapping, vals_addr, vals_len, @@ -1803,14 +1916,7 @@ declare_builtin_function!( )?; let inputs = inputs .iter() - .map(|input| { - translate_slice::( - memory_mapping, - input.as_ptr() as *const _ as u64, - input.len() as u64, - invoke_context.get_check_aligned(), - ) - }) + .map(|input| input.translate(memory_mapping, invoke_context.get_check_aligned())) .collect::, Error>>()?; let simplify_alt_bn128_syscall_error_codes = invoke_context @@ -2011,22 +2117,18 @@ declare_builtin_function!( )?; let mut hasher = H::create_hasher(); if vals_len > 0 { - let vals = translate_slice::<&[u8]>( + let vals = translate_slice_of_slices::( memory_mapping, vals_addr, vals_len, invoke_context.get_check_aligned(), )?; + for val in vals.iter() { - let bytes = translate_slice::( - memory_mapping, - val.as_ptr() as u64, - val.len() as u64, - invoke_context.get_check_aligned(), - )?; + let bytes = val.translate(memory_mapping, invoke_context.get_check_aligned())?; let cost = compute_budget.mem_op_base_cost.max( hash_byte_cost.saturating_mul( - (val.len() as u64) + val.len() .checked_div(2) .expect("div by non-zero literal"), ),