Fix alignment check (#25351)

This commit is contained in:
Jack May 2022-05-19 15:14:28 -07:00 committed by GitHub
parent dcce90555b
commit 83c0dd3637
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 240 additions and 112 deletions

View File

@ -206,6 +206,13 @@ impl<'a> StackFrame<'a> {
} }
} }
struct SyscallContext {
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>,
allocator: Rc<RefCell<dyn Alloc>>,
}
pub struct InvokeContext<'a> { pub struct InvokeContext<'a> {
pub transaction_context: &'a mut TransactionContext, pub transaction_context: &'a mut TransactionContext,
#[allow(deprecated)] #[allow(deprecated)]
@ -224,10 +231,7 @@ pub struct InvokeContext<'a> {
pub timings: ExecuteDetailsTimings, pub timings: ExecuteDetailsTimings,
pub blockhash: Hash, pub blockhash: Hash,
pub lamports_per_signature: u64, pub lamports_per_signature: u64,
check_aligned: bool, syscall_context: Vec<Option<SyscallContext>>,
check_size: bool,
orig_account_lengths: Vec<Option<Vec<usize>>>,
allocators: Vec<Option<Rc<RefCell<dyn Alloc>>>>,
} }
impl<'a> InvokeContext<'a> { impl<'a> InvokeContext<'a> {
@ -262,10 +266,7 @@ impl<'a> InvokeContext<'a> {
timings: ExecuteDetailsTimings::default(), timings: ExecuteDetailsTimings::default(),
blockhash, blockhash,
lamports_per_signature, lamports_per_signature,
check_aligned: true, syscall_context: Vec::new(),
check_size: true,
orig_account_lengths: Vec::new(),
allocators: Vec::new(),
} }
} }
@ -454,8 +455,7 @@ impl<'a> InvokeContext<'a> {
std::mem::transmute(keyed_accounts.as_slice()) std::mem::transmute(keyed_accounts.as_slice())
}), }),
)); ));
self.orig_account_lengths.push(None); self.syscall_context.push(None);
self.allocators.push(None);
self.transaction_context.push( self.transaction_context.push(
program_indices, program_indices,
instruction_accounts, instruction_accounts,
@ -467,8 +467,7 @@ impl<'a> InvokeContext<'a> {
/// Pop a stack frame from the invocation stack /// Pop a stack frame from the invocation stack
pub fn pop(&mut self) -> Result<(), InstructionError> { pub fn pop(&mut self) -> Result<(), InstructionError> {
self.orig_account_lengths.pop(); self.syscall_context.pop();
self.allocators.pop();
self.invoke_stack.pop(); self.invoke_stack.pop();
self.transaction_context.pop() self.transaction_context.pop()
} }
@ -1077,66 +1076,61 @@ impl<'a> InvokeContext<'a> {
.get_key_of_account_at_index(index_in_transaction) .get_key_of_account_at_index(index_in_transaction)
} }
/// Set the original account lengths // Set this instruction syscall context
pub fn set_orig_account_lengths( pub fn set_syscall_context(
&mut self, &mut self,
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>, orig_account_lengths: Vec<usize>,
allocator: Rc<RefCell<dyn Alloc>>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
*self *self
.orig_account_lengths .syscall_context
.last_mut() .last_mut()
.ok_or(InstructionError::CallDepth)? = Some(orig_account_lengths); .ok_or(InstructionError::CallDepth)? = Some(SyscallContext {
check_aligned,
check_size,
orig_account_lengths,
allocator,
});
Ok(()) Ok(())
} }
/// Get the original account lengths
pub fn get_orig_account_lengths(&self) -> Result<&[usize], InstructionError> {
self.orig_account_lengths
.last()
.and_then(|orig_account_lengths| orig_account_lengths.as_ref())
.map(|orig_account_lengths| orig_account_lengths.as_slice())
.ok_or(InstructionError::CallDepth)
}
// Set should alignment be enforced during user pointer translation
pub fn set_check_aligned(&mut self, check_aligned: bool) {
self.check_aligned = check_aligned;
}
// Should alignment be enforced during user pointer translation // Should alignment be enforced during user pointer translation
pub fn get_check_aligned(&self) -> bool { pub fn get_check_aligned(&self) -> bool {
self.check_aligned self.syscall_context
} .last()
.and_then(|context| context.as_ref())
// Set should type size be checked during user pointer translation .map(|context| context.check_aligned)
pub fn set_check_size(&mut self, check_size: bool) { .unwrap_or(true)
self.check_size = check_size;
} }
// Set should type size be checked during user pointer translation // Set should type size be checked during user pointer translation
pub fn get_check_size(&self) -> bool { pub fn get_check_size(&self) -> bool {
self.check_size self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.check_size)
.unwrap_or(true)
}
/// Get the original account lengths
pub fn get_orig_account_lengths(&self) -> Result<&[usize], InstructionError> {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.orig_account_lengths.as_slice())
.ok_or(InstructionError::CallDepth)
} }
// Get this instruction's memory allocator // Get this instruction's memory allocator
pub fn get_allocator(&self) -> Result<Rc<RefCell<dyn Alloc>>, InstructionError> { pub fn get_allocator(&self) -> Result<Rc<RefCell<dyn Alloc>>, InstructionError> {
self.allocators self.syscall_context
.last() .last()
.and_then(|allocator| allocator.clone()) .and_then(|context| context.as_ref())
.map(|context| context.allocator.clone())
.ok_or(InstructionError::CallDepth) .ok_or(InstructionError::CallDepth)
} }
// Set this instruction's memory allocator
pub fn set_allocator(
&mut self,
allocator: Rc<RefCell<dyn Alloc>>,
) -> Result<(), InstructionError> {
*self
.allocators
.last_mut()
.ok_or(InstructionError::CallDepth)? = Some(allocator);
Ok(())
}
} }
pub struct MockInvokeContextPreparation { pub struct MockInvokeContextPreparation {

View File

@ -3983,6 +3983,13 @@ dependencies = [
"solana-program 1.11.0", "solana-program 1.11.0",
] ]
[[package]]
name = "solana-bpf-rust-inner_instruction_alignment_check"
version = "1.11.0"
dependencies = [
"solana-program 1.11.0",
]
[[package]] [[package]]
name = "solana-bpf-rust-instruction-introspection" name = "solana-bpf-rust-instruction-introspection"
version = "1.11.0" version = "1.11.0"

View File

@ -59,6 +59,7 @@ members = [
"rust/external_spend", "rust/external_spend",
"rust/get_minimum_delegation", "rust/get_minimum_delegation",
"rust/finalize", "rust/finalize",
"rust/inner_instruction_alignment_check",
"rust/instruction_introspection", "rust/instruction_introspection",
"rust/invoke", "rust/invoke",
"rust/invoke_and_error", "rust/invoke_and_error",

View File

@ -115,8 +115,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
Executable::<BpfError, ThisInstructionMeter>::jit_compile(&mut executable).unwrap(); Executable::<BpfError, ThisInstructionMeter>::jit_compile(&mut executable).unwrap();
let compute_meter = invoke_context.get_compute_meter(); let compute_meter = invoke_context.get_compute_meter();
let mut instruction_meter = ThisInstructionMeter { compute_meter }; let mut instruction_meter = ThisInstructionMeter { compute_meter };
invoke_context.set_orig_account_lengths(vec![]).unwrap(); let mut vm = create_vm(&executable, &mut inner_iter, vec![], invoke_context).unwrap();
let mut vm = create_vm(&executable, &mut inner_iter, invoke_context).unwrap();
println!("Interpreted:"); println!("Interpreted:");
assert_eq!( assert_eq!(
@ -220,9 +219,6 @@ fn bench_create_vm(bencher: &mut Bencher) {
.unwrap(), .unwrap(),
) )
.unwrap(); .unwrap();
invoke_context
.set_orig_account_lengths(account_lengths)
.unwrap();
let executable = Executable::<BpfError, ThisInstructionMeter>::from_elf( let executable = Executable::<BpfError, ThisInstructionMeter>::from_elf(
&elf, &elf,
@ -233,7 +229,13 @@ fn bench_create_vm(bencher: &mut Bencher) {
.unwrap(); .unwrap();
bencher.iter(|| { bencher.iter(|| {
let _ = create_vm(&executable, serialized.as_slice_mut(), invoke_context).unwrap(); let _ = create_vm(
&executable,
serialized.as_slice_mut(),
account_lengths.clone(),
invoke_context,
)
.unwrap();
}); });
}); });
} }
@ -268,10 +270,13 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
.unwrap(); .unwrap();
let compute_meter = invoke_context.get_compute_meter(); let compute_meter = invoke_context.get_compute_meter();
let mut instruction_meter = ThisInstructionMeter { compute_meter }; let mut instruction_meter = ThisInstructionMeter { compute_meter };
invoke_context let mut vm = create_vm(
.set_orig_account_lengths(account_lengths) &executable,
serialized.as_slice_mut(),
account_lengths,
invoke_context,
)
.unwrap(); .unwrap();
let mut vm = create_vm(&executable, serialized.as_slice_mut(), invoke_context).unwrap();
let mut measure = Measure::start("tune"); let mut measure = Measure::start("tune");
let _ = vm.execute_program_interpreted(&mut instruction_meter); let _ = vm.execute_program_interpreted(&mut instruction_meter);

View File

@ -71,6 +71,7 @@ fn main() {
"external_spend", "external_spend",
"finalize", "finalize",
"get_minimum_delegation", "get_minimum_delegation",
"inner_instruction_alignment_check",
"instruction_introspection", "instruction_introspection",
"invoke", "invoke",
"invoke_and_error", "invoke_and_error",

View File

@ -0,0 +1,19 @@
[package]
name = "solana-bpf-rust-inner_instruction_alignment_check"
version = "1.11.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-inner_instruction_alignment_check"
edition = "2021"
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.11.0" }
[lib]
crate-type = ["cdylib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,41 @@
//! Example Rust-based BPF noop program
use solana_program::{
account_info::AccountInfo,
entrypoint_deprecated::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
program::invoke,
pubkey::Pubkey,
};
#[no_mangle]
fn custom_panic(info: &core::panic::PanicInfo<'_>) {
// Full panic reporting
msg!(&format!("{}", info));
}
solana_program::entrypoint_deprecated!(process_instruction);
#[allow(clippy::unnecessary_wraps)]
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let to_call = accounts[0].key;
let infos = accounts;
let instruction = Instruction {
accounts: vec![AccountMeta {
pubkey: *accounts[1].key,
is_signer: accounts[1].is_signer,
is_writable: accounts[1].is_writable,
}],
data: instruction_data.to_owned(),
program_id: *to_call,
};
let _ = invoke(&instruction, infos);
let _ = invoke(&instruction, infos);
Ok(())
}

View File

@ -1,4 +1,4 @@
//! Invokes an instruction and returns an error, the instruction invoked //! Invokes an instruction and return ok no matter what, the instruction invoked
//! uses the instruction data provided and all the accounts //! uses the instruction data provided and all the accounts
use solana_program::{ use solana_program::{

View File

@ -1,4 +1,4 @@
//! Invokes an instruction and returns an error, the instruction invoked //! Invokes an instruction and returns the invoke result, the instruction invoked
//! uses the instruction data provided and all the accounts //! uses the instruction data provided and all the accounts
use solana_program::{ use solana_program::{

View File

@ -246,11 +246,13 @@ fn run_program(name: &str) -> u64 {
.unwrap(); .unwrap();
let mut parameter_bytes = parameter_bytes.clone(); let mut parameter_bytes = parameter_bytes.clone();
{ {
invoke_context let mut vm = create_vm(
.set_orig_account_lengths(account_lengths.clone()) &executable,
parameter_bytes.as_slice_mut(),
account_lengths.clone(),
invoke_context,
)
.unwrap(); .unwrap();
let mut vm =
create_vm(&executable, parameter_bytes.as_slice_mut(), invoke_context).unwrap();
let result = if i == 0 { let result = if i == 0 {
vm.execute_program_interpreted(&mut instruction_meter) vm.execute_program_interpreted(&mut instruction_meter)
} else { } else {
@ -3555,3 +3557,56 @@ fn test_get_minimum_delegation() {
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_inner_instruction_alignment_checks() {
solana_logger::setup();
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
genesis_config
.accounts
.remove(&solana_sdk::feature_set::disable_deprecated_loader::id())
.unwrap();
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, &id, entrypoint);
let (name, id, entrypoint) = solana_bpf_loader_deprecated_program!();
bank.add_builtin(&name, &id, entrypoint);
let bank_client = BankClient::new(bank);
// load aligned program
let noop = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_noop",
);
// Load unaligned program
let inner_instruction_alignment_check = load_bpf_program(
&bank_client,
&bpf_loader_deprecated::id(),
&mint_keypair,
"solana_bpf_rust_inner_instruction_alignment_check",
);
// invoke unaligned program, which will call aligned program twice,
// unaligned should be allowed once invoke completes
let mut instruction = Instruction::new_with_bytes(
inner_instruction_alignment_check,
&[0],
vec![
AccountMeta::new_readonly(noop, false),
AccountMeta::new_readonly(mint_keypair.pubkey(), false),
],
);
instruction.data[0] += 1;
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
assert!(result.is_ok());
}

View File

@ -249,6 +249,7 @@ fn check_loader_id(id: &Pubkey) -> bool {
pub fn create_vm<'a, 'b>( pub fn create_vm<'a, 'b>(
program: &'a Pin<Box<Executable<BpfError, ThisInstructionMeter>>>, program: &'a Pin<Box<Executable<BpfError, ThisInstructionMeter>>>,
parameter_bytes: &mut [u8], parameter_bytes: &mut [u8],
orig_account_lengths: Vec<usize>,
invoke_context: &'a mut InvokeContext<'b>, invoke_context: &'a mut InvokeContext<'b>,
) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> { ) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> {
let compute_budget = invoke_context.get_compute_budget(); let compute_budget = invoke_context.get_compute_budget();
@ -267,7 +268,7 @@ pub fn create_vm<'a, 'b>(
AlignedMemory::new_with_size(compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN); AlignedMemory::new_with_size(compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN);
let parameter_region = MemoryRegion::new_writable(parameter_bytes, MM_INPUT_START); let parameter_region = MemoryRegion::new_writable(parameter_bytes, MM_INPUT_START);
let mut vm = EbpfVm::new(program, heap.as_slice_mut(), vec![parameter_region])?; let mut vm = EbpfVm::new(program, heap.as_slice_mut(), vec![parameter_region])?;
syscalls::bind_syscall_context_objects(&mut vm, invoke_context, heap)?; syscalls::bind_syscall_context_objects(&mut vm, invoke_context, heap, orig_account_lengths)?;
Ok(vm) Ok(vm)
} }
@ -1165,13 +1166,14 @@ impl Executor for BpfExecutor {
let (mut parameter_bytes, account_lengths) = let (mut parameter_bytes, account_lengths) =
serialize_parameters(invoke_context.transaction_context, instruction_context)?; serialize_parameters(invoke_context.transaction_context, instruction_context)?;
serialize_time.stop(); serialize_time.stop();
invoke_context.set_orig_account_lengths(account_lengths)?;
let mut create_vm_time = Measure::start("create_vm"); let mut create_vm_time = Measure::start("create_vm");
let mut execute_time; let mut execute_time;
let execution_result = { let execution_result = {
let mut vm = match create_vm( let mut vm = match create_vm(
&self.executable, &self.executable,
parameter_bytes.as_slice_mut(), parameter_bytes.as_slice_mut(),
account_lengths,
invoke_context, invoke_context,
) { ) {
Ok(info) => info, Ok(info) => info,

View File

@ -384,30 +384,28 @@ pub fn bind_syscall_context_objects<'a, 'b>(
vm: &mut EbpfVm<'a, BpfError, crate::ThisInstructionMeter>, vm: &mut EbpfVm<'a, BpfError, crate::ThisInstructionMeter>,
invoke_context: &'a mut InvokeContext<'b>, invoke_context: &'a mut InvokeContext<'b>,
heap: AlignedMemory, heap: AlignedMemory,
orig_account_lengths: Vec<usize>,
) -> Result<(), EbpfError<BpfError>> { ) -> Result<(), EbpfError<BpfError>> {
invoke_context.set_check_aligned( let check_aligned = bpf_loader_deprecated::id()
bpf_loader_deprecated::id()
!= invoke_context != invoke_context
.transaction_context .transaction_context
.get_current_instruction_context() .get_current_instruction_context()
.and_then(|instruction_context| { .and_then(|instruction_context| {
instruction_context instruction_context.try_borrow_program_account(invoke_context.transaction_context)
.try_borrow_program_account(invoke_context.transaction_context)
}) })
.map(|program_account| *program_account.get_owner()) .map(|program_account| *program_account.get_owner())
.map_err(SyscallError::InstructionError)?, .map_err(SyscallError::InstructionError)?;
); let check_size = invoke_context
invoke_context.set_check_size(
invoke_context
.feature_set .feature_set
.is_active(&check_slice_translation_size::id()), .is_active(&check_slice_translation_size::id());
);
invoke_context invoke_context
.set_allocator(Rc::new(RefCell::new(BpfAllocator::new( .set_syscall_context(
heap, check_aligned,
ebpf::MM_HEAP_START, check_size,
)))) orig_account_lengths,
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.map_err(SyscallError::InstructionError)?; .map_err(SyscallError::InstructionError)?;
let invoke_context = Rc::new(RefCell::new(invoke_context)); let invoke_context = Rc::new(RefCell::new(invoke_context));
@ -3253,7 +3251,7 @@ fn call<'a, 'b: 'a>(
memory_mapping, memory_mapping,
caller_account.vm_data_addr, caller_account.vm_data_addr,
new_len as u64, new_len as u64,
invoke_context.get_check_aligned(), false, // Don't care since it is byte aligned
invoke_context.get_check_size(), invoke_context.get_check_size(),
)?; )?;
*caller_account.ref_to_len_in_vm = new_len as u64; *caller_account.ref_to_len_in_vm = new_len as u64;
@ -4330,10 +4328,12 @@ mod tests {
) )
.unwrap(); .unwrap();
invoke_context invoke_context
.set_allocator(Rc::new(RefCell::new(BpfAllocator::new( .set_syscall_context(
heap, true,
ebpf::MM_HEAP_START, true,
)))) vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap(); .unwrap();
let mut syscall = SyscallAllocFree { let mut syscall = SyscallAllocFree {
invoke_context: Rc::new(RefCell::new(&mut invoke_context)), invoke_context: Rc::new(RefCell::new(&mut invoke_context)),
@ -4370,12 +4370,13 @@ mod tests {
) )
.unwrap(); .unwrap();
invoke_context invoke_context
.set_allocator(Rc::new(RefCell::new(BpfAllocator::new( .set_syscall_context(
heap, false,
ebpf::MM_HEAP_START, true,
)))) vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap(); .unwrap();
invoke_context.set_check_aligned(false);
let mut syscall = SyscallAllocFree { let mut syscall = SyscallAllocFree {
invoke_context: Rc::new(RefCell::new(&mut invoke_context)), invoke_context: Rc::new(RefCell::new(&mut invoke_context)),
}; };
@ -4410,10 +4411,12 @@ mod tests {
) )
.unwrap(); .unwrap();
invoke_context invoke_context
.set_allocator(Rc::new(RefCell::new(BpfAllocator::new( .set_syscall_context(
heap, true,
ebpf::MM_HEAP_START, true,
)))) vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap(); .unwrap();
let mut syscall = SyscallAllocFree { let mut syscall = SyscallAllocFree {
invoke_context: Rc::new(RefCell::new(&mut invoke_context)), invoke_context: Rc::new(RefCell::new(&mut invoke_context)),
@ -4451,10 +4454,12 @@ mod tests {
) )
.unwrap(); .unwrap();
invoke_context invoke_context
.set_allocator(Rc::new(RefCell::new(BpfAllocator::new( .set_syscall_context(
heap, true,
ebpf::MM_HEAP_START, true,
)))) vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap(); .unwrap();
let mut syscall = SyscallAllocFree { let mut syscall = SyscallAllocFree {
invoke_context: Rc::new(RefCell::new(&mut invoke_context)), invoke_context: Rc::new(RefCell::new(&mut invoke_context)),

View File

@ -313,12 +313,10 @@ native machine code before execting it in the virtual machine.",
_ => {} _ => {}
} }
invoke_context
.set_orig_account_lengths(account_lengths)
.unwrap();
let mut vm = create_vm( let mut vm = create_vm(
&executable, &executable,
parameter_bytes.as_slice_mut(), parameter_bytes.as_slice_mut(),
account_lengths,
&mut invoke_context, &mut invoke_context,
) )
.unwrap(); .unwrap();