Skip to content

Commit

Permalink
bpf_loader: fix non-portable cross-VM transmutation of Instruction
Browse files Browse the repository at this point in the history
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<AccountMeta>`.
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<T>` type that describes `Vec<T>` 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.
  • Loading branch information
riptl committed Jul 10, 2022
1 parent 9547b00 commit 8ccc837
Showing 1 changed file with 66 additions and 21 deletions.
87 changes: 66 additions & 21 deletions programs/bpf_loader/src/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -2027,6 +2028,62 @@ struct CallerAccount<'a> {
}
type TranslatedAccounts<'a> = Vec<(usize, Option<CallerAccount<'a>>)>;

/// Stable representation of a `Vec<T>` in the SBFv2 Rust runtime.
/// Only supports the global allocator.
#[repr(C)]
struct RustVMVec<T: Sized> {
ptr: u64,
raw_cap: u64,
raw_len: u64,
_phantom: PhantomData<T>,
}

impl<T: Sized> RustVMVec<T> {
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<BpfError>> {
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<AccountMeta>,
data: RustVMVec<u8>,
}

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<BpfError>> {
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<RefMut<&'a mut InvokeContext<'b>>, EbpfError<BpfError>>;
Expand Down Expand Up @@ -2093,30 +2150,18 @@ impl<'a, 'b> SyscallInvokeSigned<'a, 'b> for SyscallInvokeSignedRust<'a, 'b> {
memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError<BpfError>> {
let ix = translate_type::<Instruction>(
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::<AccountMeta>(
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::<u8>(
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,
Expand Down

0 comments on commit 8ccc837

Please sign in to comment.