From 8ccc837e185314ce37b71835a765f38a6c7e51de Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 10 Jul 2022 02:28:22 +0200 Subject: [PATCH] bpf_loader: fix non-portable cross-VM transmutation of Instruction The `sol_invoke_signed_rust` syscall handler initializes takes a reference to the `Instruction` type backed by VM memory in the `translate_instruction` function. The `Instruction` type contains an std `Vec`. Because the host and VM have different memory maps the resulting `&Vec` object is corrupted in a way that breaks Rust's safety semantics. For example, trying to access any Vec element (a safe operation) will result in a segfault, as the host would try to resolve a VM pointer without translating it. The transmutation of the `Vec<_>` type in an SBFv2 Rust environment to the host (x86_64 or arm64) Rust environment is also UB. Rust has no ABI stability guarantees, neither across different compiler versions, nor across different architectures. For this reason, the solana-bpf-loader-program would not be able to execute any Rust programs that use CPIs on 32-bit hosts. This code does not cause a security vulnerability but this construct should be improved to bring it more in line with the [Rust Unsafe Code Guidelines Reference](https://rust-lang.github.io/unsafe-code-guidelines/). - Introduces a new stable `RustVMVec` type that describes `Vec` on SBFv2. - Introduces a new stable `RustVMInstruction` type that describes `Instruction` on SBFv2. - Replaces non-portable `translate_instruction` code with a cross-arch/cross-rustc portable variant. --- programs/bpf_loader/src/syscalls.rs | 87 ++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 23ab57b7f498c7..d214d2f2aff4e7 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -46,6 +46,7 @@ use { std::{ alloc::Layout, cell::{Ref, RefCell, RefMut}, + marker::PhantomData, mem::{align_of, size_of}, rc::Rc, slice::from_raw_parts_mut, @@ -2027,6 +2028,62 @@ struct CallerAccount<'a> { } type TranslatedAccounts<'a> = Vec<(usize, Option>)>; +/// Stable representation of a `Vec` in the SBFv2 Rust runtime. +/// Only supports the global allocator. +#[repr(C)] +struct RustVMVec { + ptr: u64, + raw_cap: u64, + raw_len: u64, + _phantom: PhantomData, +} + +impl RustVMVec { + fn len(&self) -> usize { + // An u64 to usize conversion can only fail in the case of an overflow. + // Realistically, this only happens on 32-bit architectures. + // Assuming available memory in a VM will never exceed 4 GiB, + // "saturating" to UINT32_MAX safely reaches the "slice too large" error path + // that a comparable larger value would also trigger on a 64-bit architecture. + self.raw_len.try_into().unwrap_or(usize::MAX) + } + + /// Returns a slice to the elements pointed to by the Vec object. + fn try_as_slice( + &self, + memory_mapping: &MemoryMapping, + invoke_context: &InvokeContext, + ) -> Result<&[T], EbpfError> { + translate_slice( + memory_mapping, + self.ptr, + self.raw_len, + invoke_context.get_check_aligned(), + invoke_context.get_check_size(), + ) + } +} + +/// Stable representation of a `solana_program::instruction::Instruction` +/// in the SBFv2 Rust runtime. +#[repr(C)] +struct RustVMInstruction { + program_id: Pubkey, + accounts: RustVMVec, + data: RustVMVec, +} + +impl RustVMInstruction { + /// Returns a reference to an Instruction object at the given VM memory address. + fn from_vm_memory<'a>( + memory_mapping: &MemoryMapping<'a>, + vm_addr: u64, + invoke_context: &InvokeContext<'a>, + ) -> Result<&'a Self, EbpfError> { + translate_type(memory_mapping, vm_addr, invoke_context.get_check_aligned()) + } +} + /// Implemented by language specific data structure translators trait SyscallInvokeSigned<'a, 'b> { fn get_context_mut(&self) -> Result>, EbpfError>; @@ -2093,30 +2150,18 @@ impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> { memory_mapping: &mut MemoryMapping, invoke_context: &mut InvokeContext, ) -> Result> { - let ix = translate_type::( - memory_mapping, - addr, - invoke_context.get_check_aligned(), - )?; + let ix = RustVMInstruction::from_vm_memory(memory_mapping, addr, invoke_context)?; check_instruction_size(ix.accounts.len(), ix.data.len(), invoke_context)?; - let accounts = translate_slice::( - memory_mapping, - ix.accounts.as_ptr() as u64, - ix.accounts.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - .to_vec(); - let data = translate_slice::( - memory_mapping, - ix.data.as_ptr() as u64, - ix.data.len() as u64, - invoke_context.get_check_aligned(), - invoke_context.get_check_size(), - )? - .to_vec(); + let accounts = ix + .accounts + .try_as_slice(memory_mapping, invoke_context)? + .to_vec(); + let data = ix + .data + .try_as_slice(memory_mapping, invoke_context)? + .to_vec(); Ok(Instruction { program_id: ix.program_id, accounts,