solana/programs/bpf_loader/src/helpers.rs

977 lines
31 KiB
Rust
Raw Normal View History

use crate::{alloc, BPFError};
2019-08-23 11:03:53 -07:00
use alloc::Alloc;
use log::*;
2019-08-23 11:03:53 -07:00
use solana_rbpf::{
ebpf::{EbpfError, HelperObject, ELF_INSN_DUMP_OFFSET, MM_HEAP_START},
2019-08-23 11:03:53 -07:00
memory_region::{translate_addr, MemoryRegion},
EbpfVm,
};
2020-04-28 14:33:56 -07:00
use solana_runtime::message_processor::MessageProcessor;
use solana_sdk::{
account::Account,
account_info::AccountInfo,
bpf_loader,
entrypoint::SUCCESS,
entrypoint_native::InvokeContext,
hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
program_error::ProgramError,
pubkey::{Pubkey, PubkeyError},
};
2020-01-09 23:58:13 -08:00
use std::{
alloc::Layout,
2020-04-28 14:33:56 -07:00
cell::{RefCell, RefMut},
convert::TryFrom,
mem::{align_of, size_of},
2020-04-28 14:33:56 -07:00
rc::Rc,
slice::from_raw_parts_mut,
str::{from_utf8, Utf8Error},
2020-01-09 23:58:13 -08:00
};
use thiserror::Error as ThisError;
/// Error definitions
#[derive(Debug, ThisError)]
pub enum HelperError {
#[error("{0}: {1:?}")]
InvalidString(Utf8Error, Vec<u8>),
#[error("BPF program called abort()!")]
Abort,
#[error("BPF program Panicked at {0}, {1}:{2}")]
Panic(String, u64, u64),
2020-04-28 14:33:56 -07:00
#[error("cannot borrow invoke context")]
InvokeContextBorrowFailed,
#[error("malformed signer seed: {0}: {1:?}")]
MalformedSignerSeed(Utf8Error, Vec<u8>),
#[error("Could not create program address with signer seeds: {0}")]
BadSeeds(PubkeyError),
#[error("Program id is not supported by cross-program invocations")]
ProgramNotSupported,
#[error("{0}")]
InstructionError(InstructionError),
}
impl From<HelperError> for EbpfError<BPFError> {
fn from(error: HelperError) -> Self {
EbpfError::UserError(error.into())
}
}
/// Program heap allocators are intended to allocate/free from a given
/// chunk of memory. The specific allocator implementation is
/// selectable at build-time.
2019-08-23 11:03:53 -07:00
/// Only one allocator is currently supported
/// Simple bump allocator, never frees
use crate::allocator_bump::BPFAllocator;
/// Default program heap size, allocators
/// are expected to enforce this
const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
pub fn register_helpers<'a>(
vm: &mut EbpfVm<'a, BPFError>,
2020-04-28 14:33:56 -07:00
invoke_context: &'a mut dyn InvokeContext,
) -> Result<MemoryRegion, EbpfError<BPFError>> {
vm.register_helper_ex("abort", helper_abort)?;
vm.register_helper_ex("sol_panic", helper_sol_panic)?;
vm.register_helper_ex("sol_panic_", helper_sol_panic)?;
vm.register_helper_ex("sol_log", helper_sol_log)?;
vm.register_helper_ex("sol_log_", helper_sol_log)?;
vm.register_helper_ex("sol_log_64", helper_sol_log_u64)?;
vm.register_helper_ex("sol_log_64_", helper_sol_log_u64)?;
2020-04-28 14:33:56 -07:00
let invoke_context = Rc::new(RefCell::new(invoke_context));
vm.register_helper_with_context_ex(
"sol_invoke_signed_rust",
Box::new(HelperProcessInstructionRust {
invoke_context: invoke_context.clone(),
}),
)?;
vm.register_helper_with_context_ex(
"sol_invoke_signed_c",
Box::new(HelperProcessSolInstructionC {
invoke_context: invoke_context.clone(),
}),
)?;
let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
2019-08-23 11:03:53 -07:00
let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
vm.register_helper_with_context_ex(
"sol_alloc_free_",
Box::new(HelperSolAllocFree {
allocator: BPFAllocator::new(heap, MM_HEAP_START),
}),
)?;
Ok(heap_region)
}
#[macro_export]
macro_rules! translate {
($vm_addr:expr, $len:expr, $regions:expr) => {
2020-04-18 17:04:13 -07:00
translate_addr::<BPFError>(
$vm_addr as u64,
$len as usize,
file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions,
2020-04-18 17:04:13 -07:00
)
};
}
#[macro_export]
macro_rules! translate_type_mut {
($t:ty, $vm_addr:expr, $regions:expr) => {
unsafe {
2020-04-18 17:04:13 -07:00
match translate_addr::<BPFError>(
$vm_addr as u64,
size_of::<$t>(),
file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions,
2020-04-18 17:04:13 -07:00
) {
Ok(value) => Ok(&mut *(value as *mut $t)),
Err(e) => Err(e),
}
}
};
}
#[macro_export]
macro_rules! translate_type {
($t:ty, $vm_addr:expr, $regions:expr) => {
2020-04-18 17:04:13 -07:00
match translate_type_mut!($t, $vm_addr, $regions) {
Ok(value) => Ok(&*value),
Err(e) => Err(e),
}
};
}
#[macro_export]
macro_rules! translate_slice_mut {
2020-04-18 17:04:13 -07:00
($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {
match translate_addr::<BPFError>(
$vm_addr as u64,
$len as usize * size_of::<$t>(),
file!(),
line!() as usize - ELF_INSN_DUMP_OFFSET + 1,
$regions,
2020-04-18 17:04:13 -07:00
) {
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 {
($t:ty, $vm_addr:expr, $len: expr, $regions:expr) => {
2020-04-18 17:04:13 -07:00
match translate_slice_mut!($t, $vm_addr, $len, $regions) {
Ok(value) => Ok(&*value),
Err(e) => Err(e),
}
};
}
/// Take a virtual pointer to a string (points to BPF VM memory space), translate it
/// pass it to a user-defined work function
fn translate_string_and_do(
addr: u64,
len: u64,
regions: &[MemoryRegion],
work: &dyn Fn(&str) -> Result<u64, EbpfError<BPFError>>,
) -> Result<u64, EbpfError<BPFError>> {
2020-04-18 17:04:13 -07:00
let buf = translate_slice!(u8, addr, len, regions)?;
let i = match buf.iter().position(|byte| *byte == 0) {
Some(i) => i,
None => len as usize,
};
match from_utf8(&buf[..i]) {
Ok(message) => work(message),
Err(err) => Err(HelperError::InvalidString(err, buf[..i].to_vec()).into()),
}
}
/// Abort helper functions, called when the BPF program calls `abort()`
/// The verify function returns an error which will cause the BPF program
/// to be halted immediately
2019-08-23 11:03:53 -07:00
pub fn helper_abort(
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_arg5: u64,
_ro_regions: &[MemoryRegion],
_rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
Err(HelperError::Abort.into())
}
/// Panic helper functions, called when the BPF program calls 'sol_panic_()`
/// The verify function returns an error which will cause the BPF program
/// to be halted immediately
2019-08-23 11:03:53 -07:00
pub fn helper_sol_panic(
file: u64,
2019-08-23 11:03:53 -07:00
len: u64,
line: u64,
column: u64,
_arg5: u64,
ro_regions: &[MemoryRegion],
_rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
translate_string_and_do(file, len, ro_regions, &|string: &str| {
Err(HelperError::Panic(string.to_string(), line, column).into())
})
}
/// Log a user's info message
pub fn helper_sol_log(
addr: u64,
len: u64,
_arg3: u64,
_arg4: u64,
_arg5: u64,
ro_regions: &[MemoryRegion],
_rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
2019-09-30 14:21:29 -07:00
if log_enabled!(log::Level::Info) {
translate_string_and_do(addr, len, ro_regions, &|string: &str| {
info!("info!: {}", string);
Ok(0)
})?;
}
Ok(0)
}
2019-08-23 11:03:53 -07:00
/// Log 5 u64 values
pub fn helper_sol_log_u64(
arg1: u64,
arg2: u64,
arg3: u64,
arg4: u64,
arg5: u64,
2019-08-23 11:03:53 -07:00
_ro_regions: &[MemoryRegion],
_rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
2019-09-30 14:21:29 -07:00
if log_enabled!(log::Level::Info) {
info!(
"info!: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}",
arg1, arg2, arg3, arg4, arg5
);
}
2019-08-23 11:03:53 -07:00
Ok(0)
}
/// Dynamic memory allocation helper called when the BPF program calls
/// `sol_alloc_free_()`. The allocator is expected to allocate/free
/// from/to a given chunk of memory and enforce size restrictions. The
/// memory chunk is given to the allocator during allocator creation and
/// information about that memory (start address and size) is passed
/// to the VM to use for enforcement.
pub struct HelperSolAllocFree {
allocator: BPFAllocator,
}
impl HelperObject<BPFError> for HelperSolAllocFree {
fn call(
&mut self,
size: u64,
free_addr: u64,
_arg3: u64,
_arg4: u64,
_arg5: u64,
_ro_regions: &[MemoryRegion],
_rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
let layout = Layout::from_size_align(size as usize, align_of::<u8>()).unwrap();
if free_addr == 0 {
match self.allocator.alloc(layout) {
Ok(addr) => Ok(addr as u64),
Err(_) => Ok(0),
}
} else {
self.allocator.dealloc(free_addr, layout);
Ok(0)
}
}
}
2020-04-18 17:04:13 -07:00
2020-04-28 14:33:56 -07:00
// Cross-program invocation helpers
pub type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<(&'a mut u64, &'a mut [u8])>);
/// Implemented by language specific data structure translators
trait HelperProcessInstruction<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>>;
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>>;
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>>;
}
/// Cross-program invocation called from Rust
pub struct HelperProcessInstructionRust<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessInstructionRust<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix = translate_type!(Instruction, addr, ro_regions)?;
let accounts = translate_slice!(
AccountMeta,
ix.accounts.as_ptr(),
ix.accounts.len(),
ro_regions
)?
.to_vec();
let data = translate_slice!(u8, ix.data.as_ptr(), ix.data.len(), ro_regions)?.to_vec();
Ok(Instruction {
program_id: ix.program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = if account_infos_len > 0 {
translate_slice!(
AccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?
} else {
&[]
};
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() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key as *const _, ro_regions)?;
if account_key == key {
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)?
};
let data = {
// Double translate data out of RefCell
let data = *translate_type!(&[u8], account_info.data.as_ptr(), ro_regions)?;
translate_slice_mut!(u8, data.as_ptr(), data.len(), rw_regions)?
};
let owner =
translate_type!(Pubkey, account_info.owner as *const _, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
let mut signers = Vec::new();
if signers_seeds_len > 0 {
let signers_seeds =
translate_slice!(&[&str], signers_seeds_addr, signers_seeds_len, ro_regions)?;
for signer_seeds in signers_seeds.iter() {
let untranslated_seeds =
translate_slice!(&str, signer_seeds.as_ptr(), signer_seeds.len(), ro_regions)?;
let seeds = untranslated_seeds
.iter()
.map(|untranslated_seed| {
let seed_bytes = translate_slice!(
u8,
untranslated_seed.as_ptr(),
untranslated_seed.len(),
ro_regions
)?;
from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
let signer = Pubkey::create_program_address(&seeds, program_id)
.map_err(HelperError::BadSeeds)?;
signers.push(signer);
}
Ok(signers)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessInstructionRust<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Rust representation of C's SolInstruction
#[derive(Debug)]
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)]
struct SolAccountMeta {
pubkey_addr: u64,
is_writable: bool,
is_signer: bool,
}
/// Rust representation of C's SolAccountInfo
#[derive(Debug)]
struct SolAccountInfo {
key_addr: u64,
lamports_addr: u64,
data_len: usize,
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)]
struct SolSignerSeedC {
addr: u64,
len: u64,
}
/// Rust representation of C's SolSignerSeeds
#[derive(Debug)]
struct SolSignerSeedsC {
addr: u64,
len: u64,
}
/// Cross-program invocation called from C
pub struct HelperProcessSolInstructionC<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> HelperProcessInstruction<'a> for HelperProcessSolInstructionC<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>> {
self.invoke_context
.try_borrow_mut()
.map_err(|_| HelperError::InvokeContextBorrowFailed.into())
}
fn translate_instruction(
&self,
addr: u64,
ro_regions: &[MemoryRegion],
) -> Result<Instruction, EbpfError<BPFError>> {
let ix_c = translate_type!(SolInstruction, addr, ro_regions)?;
let program_id = translate_type!(Pubkey, ix_c.program_id_addr, ro_regions)?;
let meta_cs = translate_slice!(
SolAccountMeta,
ix_c.accounts_addr,
ix_c.accounts_len,
ro_regions
)?;
let data = translate_slice!(u8, ix_c.data_addr, ix_c.data_len, ro_regions)?.to_vec();
let accounts = meta_cs
.iter()
.map(|meta_c| {
let pubkey = translate_type!(Pubkey, meta_c.pubkey_addr, ro_regions)?;
Ok(AccountMeta {
pubkey: *pubkey,
is_signer: meta_c.is_signer,
is_writable: meta_c.is_writable,
})
})
.collect::<Result<Vec<AccountMeta>, EbpfError<BPFError>>>()?;
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
fn translate_accounts(
&self,
message: &Message,
account_infos_addr: u64,
account_infos_len: usize,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<TranslatedAccounts<'a>, EbpfError<BPFError>> {
let account_infos = translate_slice!(
SolAccountInfo,
account_infos_addr,
account_infos_len,
ro_regions
)?;
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() {
for account_info in account_infos.iter() {
let key = translate_type!(Pubkey, account_info.key_addr, ro_regions)?;
if account_key == key {
let lamports_ref =
translate_type_mut!(u64, account_info.lamports_addr, rw_regions)?;
let data = translate_slice_mut!(
u8,
account_info.data_addr,
account_info.data_len,
rw_regions
)?;
let owner = translate_type!(Pubkey, account_info.owner_addr, ro_regions)?;
accounts.push(Rc::new(RefCell::new(Account {
lamports: *lamports_ref,
data: data.to_vec(),
executable: account_info.executable,
owner: *owner,
rent_epoch: account_info.rent_epoch,
hash: Hash::default(),
})));
refs.push((lamports_ref, data));
continue 'root;
}
}
return Err(HelperError::InstructionError(InstructionError::MissingAccount).into());
}
Ok((accounts, refs))
}
fn translate_signers(
&self,
program_id: &Pubkey,
signers_seeds_addr: u64,
signers_seeds_len: usize,
ro_regions: &[MemoryRegion],
) -> Result<Vec<Pubkey>, EbpfError<BPFError>> {
if signers_seeds_len > 0 {
let signers_seeds = translate_slice!(
SolSignerSeedC,
signers_seeds_addr,
signers_seeds_len,
ro_regions
)?;
Ok(signers_seeds
.iter()
.map(|signer_seeds| {
let seeds = translate_slice!(
SolSignerSeedC,
signer_seeds.addr,
signer_seeds.len,
ro_regions
)?;
let seed_strs = seeds
.iter()
.map(|seed| {
let seed_bytes = translate_slice!(u8, seed.addr, seed.len, ro_regions)?;
std::str::from_utf8(seed_bytes).map_err(|err| {
HelperError::MalformedSignerSeed(err, seed_bytes.to_vec()).into()
})
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?;
Pubkey::create_program_address(&seed_strs, program_id)
.map_err(|err| HelperError::BadSeeds(err).into())
})
.collect::<Result<Vec<_>, EbpfError<BPFError>>>()?)
} else {
Ok(vec![])
}
}
}
impl<'a> HelperObject<BPFError> for HelperProcessSolInstructionC<'a> {
fn call(
&mut self,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
call(
self,
instruction_addr,
account_infos_addr,
account_infos_len,
signers_seeds_addr,
signers_seeds_len,
ro_regions,
rw_regions,
)
}
}
/// Call process instruction, common to both Rust and C
fn call<'a>(
helper: &mut dyn HelperProcessInstruction<'a>,
instruction_addr: u64,
account_infos_addr: u64,
account_infos_len: u64,
signers_seeds_addr: u64,
signers_seeds_len: u64,
ro_regions: &[MemoryRegion],
rw_regions: &[MemoryRegion],
) -> Result<u64, EbpfError<BPFError>> {
let mut invoke_context = helper.get_context_mut()?;
// Translate data passed from the VM
let instruction = helper.translate_instruction(instruction_addr, ro_regions)?;
let message = Message::new(&[instruction]);
let program_id_index = message.instructions[0].program_id_index as usize;
let program_id = message.account_keys[program_id_index];
let (accounts, refs) = helper.translate_accounts(
&message,
account_infos_addr,
account_infos_len as usize,
ro_regions,
rw_regions,
)?;
let signers = helper.translate_signers(
&program_id,
signers_seeds_addr,
signers_seeds_len as usize,
ro_regions,
)?;
// Process instruction
let program_account = (*accounts[program_id_index]).clone();
if program_account.borrow().owner != bpf_loader::id() {
// Only BPF programs supported for now
return Err(HelperError::ProgramNotSupported.into());
}
let executable_accounts = vec![(program_id, program_account)];
#[allow(clippy::deref_addrof)]
match MessageProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&signers,
crate::process_instruction,
*(&mut *invoke_context),
) {
Ok(()) => (),
Err(err) => match ProgramError::try_from(err) {
Ok(err) => return Ok(err.into()),
Err(err) => return Err(HelperError::InstructionError(err).into()),
},
}
// Copy results back into caller's AccountInfos
for (i, (account, (lamport_ref, data))) in accounts.iter().zip(refs).enumerate() {
let account = account.borrow();
if message.is_writable(i) && !account.executable {
*lamport_ref = account.lamports;
data.clone_from_slice(&account.data);
}
}
Ok(SUCCESS)
}
2020-04-18 17:04:13 -07:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translate() {
const START: u64 = 100;
const LENGTH: u64 = 1000;
let data = vec![0u8; LENGTH as usize];
let addr = data.as_ptr() as u64;
let regions = vec![MemoryRegion::new_from_slice(&data, START)];
let cases = vec![
(true, START, 0, addr),
(true, START, 1, addr),
(true, START, LENGTH, addr),
(true, START + 1, LENGTH - 1, addr + 1),
(false, START + 1, LENGTH, 0),
(true, START + LENGTH - 1, 1, addr + LENGTH - 1),
(true, START + LENGTH, 0, addr + LENGTH),
(false, START + LENGTH, 1, 0),
(false, START, LENGTH + 1, 0),
(false, 0, 0, 0),
(false, 0, 1, 0),
(false, START - 1, 0, 0),
(false, START - 1, 1, 0),
(true, START + LENGTH / 2, LENGTH / 2, addr + LENGTH / 2),
];
for (ok, start, length, value) in cases {
match ok {
true => assert_eq!(translate!(start, length, &regions).unwrap(), value),
false => assert!(translate!(start, length, &regions).is_err()),
}
}
}
#[test]
fn test_translate_type() {
// Pubkey
let pubkey = Pubkey::new_rand();
let addr = &pubkey as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: std::mem::size_of::<Pubkey>() as u64,
}];
let translated_pubkey = translate_type!(Pubkey, 100, &regions).unwrap();
assert_eq!(pubkey, *translated_pubkey);
// Instruction
let instruction = Instruction::new(
Pubkey::new_rand(),
&"foobar",
vec![AccountMeta::new(Pubkey::new_rand(), false)],
);
let addr = &instruction as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: std::mem::size_of::<Instruction>() as u64,
}];
let translated_instruction = translate_type!(Instruction, 100, &regions).unwrap();
assert_eq!(instruction, *translated_instruction);
}
#[test]
fn test_translate_slice() {
// u8
let mut data = vec![1u8, 2, 3, 4, 5];
let addr = data.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: data.len() as u64,
}];
let translated_data = translate_slice!(u8, 100, data.len(), &regions).unwrap();
assert_eq!(data, translated_data);
data[0] = 10;
assert_eq!(data, translated_data);
// Pubkeys
let mut data = vec![Pubkey::new_rand(); 5];
let addr = data.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: (data.len() * std::mem::size_of::<Pubkey>()) as u64,
}];
let translated_data = translate_slice!(Pubkey, 100, data.len(), &regions).unwrap();
assert_eq!(data, translated_data);
data[0] = Pubkey::new_rand(); // Both should point to same place
assert_eq!(data, translated_data);
}
#[test]
fn test_translate_string_and_do() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let regions = vec![MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
}];
assert_eq!(
42,
translate_string_and_do(100, string.len() as u64, &regions, &|string: &str| {
assert_eq!(string, "Gaggablaghblagh!");
Ok(42)
})
.unwrap()
);
}
#[test]
#[should_panic(expected = "UserError(HelperError(Abort))")]
fn test_helper_abort() {
let ro_region = MemoryRegion::default();
let rw_region = MemoryRegion::default();
helper_abort(0, 0, 0, 0, 0, &[ro_region], &[rw_region]).unwrap();
}
#[test]
#[should_panic(expected = "UserError(HelperError(Panic(\"Gaggablaghblagh!\", 42, 84)))")]
fn test_helper_sol_panic() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let ro_region = MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
};
let rw_region = MemoryRegion::default();
helper_sol_panic(
100,
string.len() as u64,
42,
84,
0,
&[ro_region],
&[rw_region],
)
.unwrap();
}
// Ignore this test: solana_logger conflicts when running tests concurrently,
// this results in the bad string length being ignored and not returning an error
2020-04-18 17:04:13 -07:00
#[test]
#[ignore]
2020-04-18 17:04:13 -07:00
fn test_helper_sol_log() {
let string = "Gaggablaghblagh!";
let addr = string.as_ptr() as *const _ as u64;
let ro_regions = &[MemoryRegion {
addr_host: addr,
addr_vm: 100,
len: string.len() as u64,
}];
let rw_regions = &[MemoryRegion::default()];
solana_logger::setup_with_default("solana=info");
helper_sol_log(100, string.len() as u64, 0, 0, 0, ro_regions, rw_regions).unwrap();
solana_logger::setup_with_default("solana=info");
helper_sol_log(
100,
string.len() as u64 * 2,
0,
0,
0,
ro_regions,
rw_regions,
)
.unwrap_err();
}
// Ignore this test: solana_logger conflicts when running tests concurrently,
2020-04-22 14:53:06 -07:00
// this results in the bad string length being ignored and not returning an error
2020-04-18 17:04:13 -07:00
#[test]
#[ignore]
2020-04-18 17:04:13 -07:00
fn test_helper_sol_log_u64() {
solana_logger::setup_with_default("solana=info");
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::default()];
helper_sol_log_u64(1, 2, 3, 4, 5, ro_regions, rw_regions).unwrap();
}
#[test]
fn test_helper_sol_alloc_free() {
// large alloc
{
let heap = vec![0_u8; 100];
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::new_from_slice(&heap, MM_HEAP_START)];
let mut helper = HelperSolAllocFree {
allocator: BPFAllocator::new(heap, MM_HEAP_START),
};
assert_ne!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
assert_eq!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
}
// many small allocs
{
let heap = vec![0_u8; 100];
let ro_regions = &[MemoryRegion::default()];
let rw_regions = &[MemoryRegion::new_from_slice(&heap, MM_HEAP_START)];
let mut helper = HelperSolAllocFree {
allocator: BPFAllocator::new(heap, MM_HEAP_START),
};
for _ in 0..100 {
assert_ne!(
helper.call(1, 0, 0, 0, 0, ro_regions, rw_regions).unwrap(),
0
);
}
assert_eq!(
helper
.call(100, 0, 0, 0, 0, ro_regions, rw_regions)
.unwrap(),
0
);
}
}
}