Enforce an executable's rent exemption in the runtime (#9134)

This commit is contained in:
Jack May 2020-03-31 10:07:38 -07:00 committed by GitHub
parent 974848310c
commit 130c0b484d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 92 deletions

View File

@ -21,7 +21,6 @@ use solana_sdk::{
program_utils::DecodeError, program_utils::DecodeError,
program_utils::{is_executable, limited_deserialize, next_keyed_account}, program_utils::{is_executable, limited_deserialize, next_keyed_account},
pubkey::Pubkey, pubkey::Pubkey,
sysvar::rent,
}; };
use std::{io::prelude::*, mem}; use std::{io::prelude::*, mem};
use thiserror::Error; use thiserror::Error;
@ -229,7 +228,6 @@ pub fn process_instruction(
LoaderInstruction::Finalize => { LoaderInstruction::Finalize => {
let mut keyed_accounts_iter = keyed_accounts.iter(); let mut keyed_accounts_iter = keyed_accounts.iter();
let program = next_keyed_account(&mut keyed_accounts_iter)?; let program = next_keyed_account(&mut keyed_accounts_iter)?;
let rent = next_keyed_account(&mut keyed_accounts_iter)?;
if program.signer_key().is_none() { if program.signer_key().is_none() {
warn!("key[0] did not sign the transaction"); warn!("key[0] did not sign the transaction");
@ -241,8 +239,6 @@ pub fn process_instruction(
return Err(InstructionError::InvalidAccountData); return Err(InstructionError::InvalidAccountData);
} }
rent::verify_rent_exemption(&program, &rent)?;
program.try_account_ref_mut()?.executable = true; program.try_account_ref_mut()?.executable = true;
info!("Finalize: account {:?}", program.signer_key().unwrap()); info!("Finalize: account {:?}", program.signer_key().unwrap());
} }
@ -254,8 +250,8 @@ pub fn process_instruction(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::account::Account; use solana_sdk::{account::Account, rent::Rent};
use std::{cell::RefCell, fs::File, io::Read}; use std::{fs::File, io::Read};
#[test] #[test]
#[should_panic(expected = "ExceededMaxInstructions(10)")] #[should_panic(expected = "ExceededMaxInstructions(10)")]
@ -324,14 +320,13 @@ mod tests {
fn test_bpf_loader_finalize() { fn test_bpf_loader_finalize() {
let program_id = Pubkey::new_rand(); let program_id = Pubkey::new_rand();
let program_key = Pubkey::new_rand(); let program_key = Pubkey::new_rand();
let rent_key = rent::id();
let mut file = File::open("test_elfs/noop.so").expect("file open failed"); let mut file = File::open("test_elfs/noop.so").expect("file open failed");
let mut elf = Vec::new(); let mut elf = Vec::new();
let rent = rent::Rent::default(); let rent = Rent::default();
file.read_to_end(&mut elf).unwrap(); file.read_to_end(&mut elf).unwrap();
let program_account = Account::new_ref(rent.minimum_balance(elf.len()), 0, &program_id); let program_account = Account::new_ref(rent.minimum_balance(elf.len()), 0, &program_id);
program_account.borrow_mut().data = elf; program_account.borrow_mut().data = elf;
let mut keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; let keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)];
let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap(); let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap();
// Case: Empty keyed accounts // Case: Empty keyed accounts
@ -340,9 +335,6 @@ mod tests {
process_instruction(&bpf_loader::id(), &vec![], &instruction_data) process_instruction(&bpf_loader::id(), &vec![], &instruction_data)
); );
let rent_account = RefCell::new(rent::create_account(1, &rent));
keyed_accounts.push(KeyedAccount::new(&rent_key, false, &rent_account));
// Case: Not signed // Case: Not signed
assert_eq!( assert_eq!(
Err(InstructionError::MissingRequiredSignature), Err(InstructionError::MissingRequiredSignature),
@ -350,10 +342,7 @@ mod tests {
); );
// Case: Finalize // Case: Finalize
let keyed_accounts = vec![ let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
KeyedAccount::new(&program_key, true, &program_account),
KeyedAccount::new(&rent_key, false, &rent_account),
];
assert_eq!( assert_eq!(
Ok(()), Ok(()),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)
@ -364,10 +353,7 @@ mod tests {
// Case: Finalize // Case: Finalize
program_account.borrow_mut().data[0] = 0; // bad elf program_account.borrow_mut().data[0] = 0; // bad elf
let keyed_accounts = vec![ let keyed_accounts = vec![KeyedAccount::new(&program_key, true, &program_account)];
KeyedAccount::new(&program_key, true, &program_account),
KeyedAccount::new(&rent_key, false, &rent_account),
];
assert_eq!( assert_eq!(
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data) process_instruction(&bpf_loader::id(), &keyed_accounts, &instruction_data)

View File

@ -13,7 +13,6 @@ use solana_sdk::{
program_utils::DecodeError, program_utils::DecodeError,
program_utils::{is_executable, limited_deserialize, next_keyed_account}, program_utils::{is_executable, limited_deserialize, next_keyed_account},
pubkey::Pubkey, pubkey::Pubkey,
sysvar::rent,
}; };
use thiserror::Error; use thiserror::Error;
use types::{ use types::{
@ -73,7 +72,6 @@ pub enum MoveLoaderInstruction {
/// bit of the Account /// bit of the Account
/// ///
/// * key[0] - the account to prepare for execution /// * key[0] - the account to prepare for execution
/// * key[1] - rent sysvar account
/// ///
/// The transaction must be signed by key[0] /// The transaction must be signed by key[0]
Finalize, Finalize,
@ -301,15 +299,12 @@ impl MoveProcessor {
pub fn do_finalize(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> { pub fn do_finalize(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
let mut keyed_accounts_iter = keyed_accounts.iter(); let mut keyed_accounts_iter = keyed_accounts.iter();
let finalized = next_keyed_account(&mut keyed_accounts_iter)?; let finalized = next_keyed_account(&mut keyed_accounts_iter)?;
let rent = next_keyed_account(&mut keyed_accounts_iter)?;
if finalized.signer_key().is_none() { if finalized.signer_key().is_none() {
debug!("Error: account to finalize did not sign the transaction"); debug!("Error: account to finalize did not sign the transaction");
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
rent::verify_rent_exemption(&finalized, &rent)?;
match finalized.state()? { match finalized.state()? {
LibraAccountState::CompiledScript(string) => { LibraAccountState::CompiledScript(string) => {
let script: Script = serde_json::from_str(&string).map_err(map_json_error)?; let script: Script = serde_json::from_str(&string).map_err(map_json_error)?;
@ -479,8 +474,6 @@ impl MoveProcessor {
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::rent::Rent;
use solana_sdk::sysvar::rent;
use std::cell::RefCell; use std::cell::RefCell;
const BIG_ENOUGH: usize = 10_000; const BIG_ENOUGH: usize = 10_000;
@ -518,11 +511,8 @@ mod tests {
let code = "main() { return; }"; let code = "main() { return; }";
let sender_address = AccountAddress::default(); let sender_address = AccountAddress::default();
let script = LibraAccount::create_script(&sender_address, code, vec![]); let script = LibraAccount::create_script(&sender_address, code, vec![]);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();
let _ = MoveProcessor::deserialize_verified_script(&script.account.borrow().data).unwrap(); let _ = MoveProcessor::deserialize_verified_script(&script.account.borrow().data).unwrap();
@ -561,11 +551,8 @@ mod tests {
let script = LibraAccount::create_script(&sender_address, code, vec![]); let script = LibraAccount::create_script(&sender_address, code, vec![]);
let genesis = LibraAccount::create_genesis(1_000_000_000); let genesis = LibraAccount::create_genesis(1_000_000_000);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();
@ -591,11 +578,8 @@ mod tests {
} }
"; ";
let module = LibraAccount::create_module(code, vec![]); let module = LibraAccount::create_module(code, vec![]);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&module.key, true, &module.account), KeyedAccount::new(&module.key, true, &module.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
keyed_accounts[0] keyed_accounts[0]
.account .account
@ -620,11 +604,8 @@ mod tests {
let script = LibraAccount::create_script(&sender_address, code, vec![]); let script = LibraAccount::create_script(&sender_address, code, vec![]);
let genesis = LibraAccount::create_genesis(1_000_000_000); let genesis = LibraAccount::create_genesis(1_000_000_000);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();
@ -695,11 +676,8 @@ mod tests {
let script = LibraAccount::create_script(&genesis.address, code, vec![]); let script = LibraAccount::create_script(&genesis.address, code, vec![]);
let payee = LibraAccount::create_unallocated(BIG_ENOUGH); let payee = LibraAccount::create_unallocated(BIG_ENOUGH);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();
@ -753,11 +731,8 @@ mod tests {
); );
let module = LibraAccount::create_module(&code, vec![]); let module = LibraAccount::create_module(&code, vec![]);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&module.key, true, &module.account), KeyedAccount::new(&module.key, true, &module.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
keyed_accounts[0] keyed_accounts[0]
.account .account
@ -799,11 +774,8 @@ mod tests {
); );
let payee = LibraAccount::create_unallocated(BIG_ENOUGH); let payee = LibraAccount::create_unallocated(BIG_ENOUGH);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();
@ -849,11 +821,8 @@ mod tests {
let script = LibraAccount::create_script(&genesis.address.clone(), code, vec![]); let script = LibraAccount::create_script(&genesis.address.clone(), code, vec![]);
let payee = LibraAccount::create_unallocated(BIG_ENOUGH); let payee = LibraAccount::create_unallocated(BIG_ENOUGH);
let rent_id = rent::id();
let rent_account = RefCell::new(rent::create_account(1, &Rent::free()));
let keyed_accounts = vec![ let keyed_accounts = vec![
KeyedAccount::new(&script.key, true, &script.account), KeyedAccount::new(&script.key, true, &script.account),
KeyedAccount::new(&rent_id, false, &rent_account),
]; ];
MoveProcessor::do_finalize(&keyed_accounts).unwrap(); MoveProcessor::do_finalize(&keyed_accounts).unwrap();

View File

@ -3,7 +3,7 @@
extern crate test; extern crate test;
use log::*; use log::*;
use solana_runtime::message_processor::PreAccount; use solana_runtime::{message_processor::PreAccount, rent_collector::RentCollector};
use solana_sdk::{account::Account, pubkey::Pubkey}; use solana_sdk::{account::Account, pubkey::Pubkey};
use test::Bencher; use test::Bencher;
@ -15,11 +15,12 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
let non_owner = Pubkey::new_rand(); let non_owner = Pubkey::new_rand();
let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), true, &owner); let pre = PreAccount::new(&Account::new(0, BUFSIZE, &owner), true, &owner);
let post = Account::new(0, BUFSIZE, &owner); let post = Account::new(0, BUFSIZE, &owner);
assert_eq!(pre.verify(&owner, &post), Ok(())); assert_eq!(pre.verify(&owner, &RentCollector::default(), &post), Ok(()));
// this one should be faster // this one should be faster
bencher.iter(|| { bencher.iter(|| {
pre.verify(&owner, &post).unwrap(); pre.verify(&owner, &RentCollector::default(), &post)
.unwrap();
}); });
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data no change by owner: {} ns/iter", summary.median); info!("data no change by owner: {} ns/iter", summary.median);
@ -32,7 +33,8 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data compare {} ns/iter", summary.median); info!("data compare {} ns/iter", summary.median);
bencher.iter(|| { bencher.iter(|| {
pre.verify(&non_owner, &post).unwrap(); pre.verify(&non_owner, &RentCollector::default(), &post)
.unwrap();
}); });
let summary = bencher.bench(|_bencher| {}).unwrap(); let summary = bencher.bench(|_bencher| {}).unwrap();
info!("data no change by non owner: {} ns/iter", summary.median); info!("data no change by non owner: {} ns/iter", summary.median);

View File

@ -1375,6 +1375,7 @@ impl Bank {
tx.message(), tx.message(),
&loader_refcells, &loader_refcells,
&account_refcells, &account_refcells,
&self.rent_collector,
); );
Self::from_refcells(accounts, loaders, account_refcells, loader_refcells); Self::from_refcells(accounts, loaders, account_refcells, loader_refcells);

View File

@ -1,4 +1,4 @@
use crate::{native_loader, system_instruction_processor}; use crate::{native_loader, rent_collector::RentCollector, system_instruction_processor};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::{create_keyed_readonly_accounts, Account, KeyedAccount}, account::{create_keyed_readonly_accounts, Account, KeyedAccount},
@ -66,7 +66,12 @@ impl PreAccount {
|| is_executable || is_executable
} }
pub fn verify(&self, program_id: &Pubkey, post: &Account) -> Result<(), InstructionError> { pub fn verify(
&self,
program_id: &Pubkey,
rent_collector: &RentCollector,
post: &Account,
) -> Result<(), InstructionError> {
// Only the owner of the account may change owner and // Only the owner of the account may change owner and
// only if the account is writable and // only if the account is writable and
// only if the data is zero-initialized or empty // only if the data is zero-initialized or empty
@ -120,12 +125,19 @@ impl PreAccount {
} }
// executable is one-way (false->true) and only the account owner may set it. // executable is one-way (false->true) and only the account owner may set it.
if self.executable != post.executable if self.executable != post.executable {
&& (!self.is_writable // line coverage used to get branch coverage if !rent_collector
|| self.executable // line coverage used to get branch coverage .rent
|| *program_id != self.owner) .is_exempt(post.lamports, post.data.len())
{ {
return Err(InstructionError::ExecutableModified); return Err(InstructionError::ExecutableAccountNotRentExempt);
}
if !self.is_writable // line coverage used to get branch coverage
|| self.executable // line coverage used to get branch coverage
|| *program_id != self.owner
{
return Err(InstructionError::ExecutableModified);
}
} }
// No one modifies rent_epoch (yet). // No one modifies rent_epoch (yet).
@ -156,7 +168,6 @@ pub struct MessageProcessor {
#[serde(skip)] #[serde(skip)]
symbol_cache: SymbolCache, symbol_cache: SymbolCache,
} }
impl Default for MessageProcessor { impl Default for MessageProcessor {
fn default() -> Self { fn default() -> Self {
let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![( let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![(
@ -170,7 +181,6 @@ impl Default for MessageProcessor {
} }
} }
} }
impl MessageProcessor { impl MessageProcessor {
/// Add a static entrypoint to intercept instructions before the dynamic loader. /// Add a static entrypoint to intercept instructions before the dynamic loader.
pub fn add_instruction_processor( pub fn add_instruction_processor(
@ -275,6 +285,7 @@ impl MessageProcessor {
pre_accounts: &[PreAccount], pre_accounts: &[PreAccount],
executable_accounts: &[(Pubkey, RefCell<Account>)], executable_accounts: &[(Pubkey, RefCell<Account>)],
accounts: &[Rc<RefCell<Account>>], accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
// Verify all accounts have zero outstanding refs // Verify all accounts have zero outstanding refs
Self::verify_account_references(executable_accounts, accounts)?; Self::verify_account_references(executable_accounts, accounts)?;
@ -285,7 +296,7 @@ impl MessageProcessor {
let program_id = instruction.program_id(&message.account_keys); let program_id = instruction.program_id(&message.account_keys);
let mut work = |unique_index: usize, account_index: usize| { let mut work = |unique_index: usize, account_index: usize| {
let account = accounts[account_index].borrow(); let account = accounts[account_index].borrow();
pre_accounts[unique_index].verify(&program_id, &account)?; pre_accounts[unique_index].verify(&program_id, rent_collector, &account)?;
pre_sum += u128::from(pre_accounts[unique_index].lamports); pre_sum += u128::from(pre_accounts[unique_index].lamports);
post_sum += u128::from(account.lamports); post_sum += u128::from(account.lamports);
Ok(()) Ok(())
@ -310,6 +321,7 @@ impl MessageProcessor {
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
executable_accounts: &[(Pubkey, RefCell<Account>)], executable_accounts: &[(Pubkey, RefCell<Account>)],
accounts: &[Rc<RefCell<Account>>], accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let pre_accounts = Self::create_pre_accounts(message, instruction, accounts); let pre_accounts = Self::create_pre_accounts(message, instruction, accounts);
self.process_instruction(message, instruction, executable_accounts, accounts)?; self.process_instruction(message, instruction, executable_accounts, accounts)?;
@ -319,6 +331,7 @@ impl MessageProcessor {
&pre_accounts, &pre_accounts,
executable_accounts, executable_accounts,
accounts, accounts,
rent_collector,
)?; )?;
Ok(()) Ok(())
} }
@ -331,6 +344,7 @@ impl MessageProcessor {
message: &Message, message: &Message,
loaders: &[Vec<(Pubkey, RefCell<Account>)>], loaders: &[Vec<(Pubkey, RefCell<Account>)>],
accounts: &[Rc<RefCell<Account>>], accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector,
) -> Result<(), TransactionError> { ) -> Result<(), TransactionError> {
for (instruction_index, instruction) in message.instructions.iter().enumerate() { for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let executable_index = message let executable_index = message
@ -338,8 +352,14 @@ impl MessageProcessor {
.ok_or(TransactionError::InvalidAccountIndex)?; .ok_or(TransactionError::InvalidAccountIndex)?;
let executable_accounts = &loaders[executable_index]; let executable_accounts = &loaders[executable_index];
self.execute_instruction(message, instruction, executable_accounts, accounts) self.execute_instruction(
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?; message,
instruction,
executable_accounts,
accounts,
rent_collector,
)
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
} }
Ok(()) Ok(())
} }
@ -410,8 +430,11 @@ mod tests {
post: &Pubkey, post: &Pubkey,
is_writable: bool, is_writable: bool,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
PreAccount::new(&Account::new(0, 0, pre), is_writable, ix) PreAccount::new(&Account::new(0, 0, pre), is_writable, ix).verify(
.verify(ix, &Account::new(0, 0, post)) ix,
&RentCollector::default(),
&Account::new(0, 0, post),
)
} }
let system_program_id = system_program::id(); let system_program_id = system_program::id();
@ -448,7 +471,6 @@ mod tests {
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"system program should not be able to change the account owner of a non-system account" "system program should not be able to change the account owner of a non-system account"
); );
assert_eq!( assert_eq!(
change_owner( change_owner(
&mallory_program_id, &mallory_program_id,
@ -459,7 +481,6 @@ mod tests {
Ok(()), Ok(()),
"mallory should be able to change the account owner, if she leaves clear data" "mallory should be able to change the account owner, if she leaves clear data"
); );
assert_eq!( assert_eq!(
PreAccount::new( PreAccount::new(
&Account::new_data(0, &[42], &mallory_program_id).unwrap(), &Account::new_data(0, &[42], &mallory_program_id).unwrap(),
@ -468,6 +489,7 @@ mod tests {
) )
.verify( .verify(
&mallory_program_id, &mallory_program_id,
&RentCollector::default(),
&Account::new_data(0, &[0], &alice_program_id).unwrap(), &Account::new_data(0, &[0], &alice_program_id).unwrap(),
), ),
Ok(()), Ok(()),
@ -481,6 +503,7 @@ mod tests {
) )
.verify( .verify(
&mallory_program_id, &mallory_program_id,
&RentCollector::default(),
&Account::new_data(0, &[42], &alice_program_id).unwrap(), &Account::new_data(0, &[42], &alice_program_id).unwrap(),
), ),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
@ -505,6 +528,7 @@ mod tests {
pre: PreAccount::new( pre: PreAccount::new(
&Account { &Account {
owner: *owner, owner: *owner,
lamports: std::u64::MAX,
data: vec![], data: vec![],
..Account::default() ..Account::default()
}, },
@ -513,6 +537,7 @@ mod tests {
), ),
post: Account { post: Account {
owner: *owner, owner: *owner,
lamports: std::u64::MAX,
..Account::default() ..Account::default()
}, },
program_id: *program_id, program_id: *program_id,
@ -539,7 +564,8 @@ mod tests {
self self
} }
pub fn verify(self) -> Result<(), InstructionError> { pub fn verify(self) -> Result<(), InstructionError> {
self.pre.verify(&self.program_id, &self.post) self.pre
.verify(&self.program_id, &RentCollector::default(), &self.post)
} }
} }
@ -623,6 +649,25 @@ mod tests {
Err(InstructionError::ExecutableLamportChange), Err(InstructionError::ExecutableLamportChange),
"owner should not be able to subtract lamports once marked executable" "owner should not be able to subtract lamports once marked executable"
); );
let data = vec![1; 100];
let min_lamports = RentCollector::default().rent.minimum_balance(data.len());
assert_eq!(
Change::new(&owner, &owner)
.executable(false, true)
.lamports(0, min_lamports)
.data(data.clone(), data.clone())
.verify(),
Ok(()),
);
assert_eq!(
Change::new(&owner, &owner)
.executable(false, true)
.lamports(0, min_lamports - 1)
.data(data.clone(), data.clone())
.verify(),
Err(InstructionError::ExecutableAccountNotRentExempt),
"owner should not be able to change an account's data once its marked executable"
);
} }
#[test] #[test]
@ -635,6 +680,7 @@ mod tests {
) )
.verify( .verify(
&system_program::id(), &system_program::id(),
&RentCollector::default(),
&Account::new_data(0, &[0, 0], &system_program::id()).unwrap() &Account::new_data(0, &[0, 0], &system_program::id()).unwrap()
), ),
Ok(()), Ok(()),
@ -648,7 +694,7 @@ mod tests {
true, true,
&system_program::id(), &system_program::id(),
).verify( ).verify(
&system_program::id(), &system_program::id(), &RentCollector::default(),
&Account::new_data(0, &[0, 0], &alice_program_id).unwrap(), &Account::new_data(0, &[0, 0], &alice_program_id).unwrap(),
), ),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
@ -659,7 +705,6 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_data() { fn test_verify_account_changes_data() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let change_data = let change_data =
|program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> { |program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> {
let pre = PreAccount::new( let pre = PreAccount::new(
@ -668,7 +713,7 @@ mod tests {
&program_id, &program_id,
); );
let post = Account::new_data(0, &[42], &alice_program_id).unwrap(); let post = Account::new_data(0, &[42], &alice_program_id).unwrap();
pre.verify(&program_id, &post) pre.verify(&program_id, &RentCollector::default(), &post)
}; };
let mallory_program_id = Pubkey::new_rand(); let mallory_program_id = Pubkey::new_rand();
@ -694,6 +739,7 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_rent_epoch() { fn test_verify_account_changes_rent_epoch() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let rent_collector = RentCollector::default();
let pre = PreAccount::new( let pre = PreAccount::new(
&Account::new(0, 0, &alice_program_id), &Account::new(0, 0, &alice_program_id),
false, false,
@ -702,14 +748,14 @@ mod tests {
let mut post = Account::new(0, 0, &alice_program_id); let mut post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Ok(()), Ok(()),
"nothing changed!" "nothing changed!"
); );
post.rent_epoch += 1; post.rent_epoch += 1;
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Err(InstructionError::RentEpochModified), Err(InstructionError::RentEpochModified),
"no one touches rent_epoch" "no one touches rent_epoch"
); );
@ -728,7 +774,7 @@ mod tests {
// positive test of this capability // positive test of this capability
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &post), pre.verify(&alice_program_id, &RentCollector::default(), &post),
Ok(()), Ok(()),
"alice should be able to deduct lamports and give the account to bob if the data is zeroed", "alice should be able to deduct lamports and give the account to bob if the data is zeroed",
); );
@ -737,6 +783,7 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_lamports() { fn test_verify_account_changes_lamports() {
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let rent_collector = RentCollector::default();
let pre = PreAccount::new( let pre = PreAccount::new(
&Account::new(42, 0, &alice_program_id), &Account::new(42, 0, &alice_program_id),
false, false,
@ -745,7 +792,7 @@ mod tests {
let post = Account::new(0, 0, &alice_program_id); let post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Err(InstructionError::ExternalAccountLamportSpend), Err(InstructionError::ExternalAccountLamportSpend),
"debit should fail, even if system program" "debit should fail, even if system program"
); );
@ -757,7 +804,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &post,), pre.verify(&alice_program_id, &rent_collector, &post),
Err(InstructionError::ReadonlyLamportChange), Err(InstructionError::ReadonlyLamportChange),
"debit should fail, even if owning program" "debit should fail, even if owning program"
); );
@ -769,7 +816,7 @@ mod tests {
); );
let post = Account::new(0, 0, &system_program::id()); let post = Account::new(0, 0, &system_program::id());
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Err(InstructionError::ModifiedProgramId), Err(InstructionError::ModifiedProgramId),
"system program can't debit the account unless it was the pre.owner" "system program can't debit the account unless it was the pre.owner"
); );
@ -781,7 +828,7 @@ mod tests {
); );
let post = Account::new(0, 0, &alice_program_id); let post = Account::new(0, 0, &alice_program_id);
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Ok(()), Ok(()),
"system can spend (and change owner)" "system can spend (and change owner)"
); );
@ -789,6 +836,7 @@ mod tests {
#[test] #[test]
fn test_verify_account_changes_data_size_changed() { fn test_verify_account_changes_data_size_changed() {
let rent_collector = RentCollector::default();
let alice_program_id = Pubkey::new_rand(); let alice_program_id = Pubkey::new_rand();
let pre = PreAccount::new( let pre = PreAccount::new(
&Account::new_data(42, &[0], &alice_program_id).unwrap(), &Account::new_data(42, &[0], &alice_program_id).unwrap(),
@ -797,7 +845,7 @@ mod tests {
); );
let post = Account::new_data(42, &[0, 0], &alice_program_id).unwrap(); let post = Account::new_data(42, &[0, 0], &alice_program_id).unwrap();
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"system program should not be able to change another program's account data size" "system program should not be able to change another program's account data size"
); );
@ -807,7 +855,7 @@ mod tests {
&alice_program_id, &alice_program_id,
); );
assert_eq!( assert_eq!(
pre.verify(&alice_program_id, &post), pre.verify(&alice_program_id, &rent_collector, &post),
Err(InstructionError::AccountDataSizeChanged), Err(InstructionError::AccountDataSizeChanged),
"non-system programs cannot change their data size" "non-system programs cannot change their data size"
); );
@ -817,7 +865,7 @@ mod tests {
&system_program::id(), &system_program::id(),
); );
assert_eq!( assert_eq!(
pre.verify(&system_program::id(), &post), pre.verify(&system_program::id(), &rent_collector, &post),
Ok(()), Ok(()),
"system program should be able to change acount data size" "system program should be able to change acount data size"
); );
@ -857,6 +905,7 @@ mod tests {
} }
let mock_system_program_id = Pubkey::new(&[2u8; 32]); let mock_system_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default(); let mut message_processor = MessageProcessor::default();
message_processor message_processor
.add_instruction_processor(mock_system_program_id, mock_system_process_instruction); .add_instruction_processor(mock_system_program_id, mock_system_process_instruction);
@ -883,7 +932,8 @@ mod tests {
account_metas.clone(), account_metas.clone(),
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
assert_eq!(accounts[0].borrow().lamports, 100); assert_eq!(accounts[0].borrow().lamports, 100);
assert_eq!(accounts[1].borrow().lamports, 0); assert_eq!(accounts[1].borrow().lamports, 0);
@ -894,7 +944,8 @@ mod tests {
account_metas.clone(), account_metas.clone(),
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!( assert_eq!(
result, result,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
@ -909,7 +960,8 @@ mod tests {
account_metas, account_metas,
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!( assert_eq!(
result, result,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
@ -976,6 +1028,7 @@ mod tests {
} }
let mock_program_id = Pubkey::new(&[2u8; 32]); let mock_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default(); let mut message_processor = MessageProcessor::default();
message_processor message_processor
.add_instruction_processor(mock_program_id, mock_system_process_instruction); .add_instruction_processor(mock_program_id, mock_system_process_instruction);
@ -1005,7 +1058,8 @@ mod tests {
&MockSystemInstruction::BorrowFail, &MockSystemInstruction::BorrowFail,
account_metas.clone(), account_metas.clone(),
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!( assert_eq!(
result, result,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
@ -1020,7 +1074,8 @@ mod tests {
&MockSystemInstruction::MultiBorrowMut, &MockSystemInstruction::MultiBorrowMut,
account_metas.clone(), account_metas.clone(),
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
// Do work on the same account but at different location in keyed_accounts[] // Do work on the same account but at different location in keyed_accounts[]
@ -1032,7 +1087,8 @@ mod tests {
}, },
account_metas, account_metas,
)]); )]);
let result = message_processor.process_message(&message, &loaders, &accounts); let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector);
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
assert_eq!(accounts[0].borrow().lamports, 80); assert_eq!(accounts[0].borrow().lamports, 80);
assert_eq!(accounts[1].borrow().lamports, 20); assert_eq!(accounts[1].borrow().lamports, 20);

View File

@ -130,6 +130,10 @@ pub enum InstructionError {
/// Executable account's lamports modified /// Executable account's lamports modified
#[error("instruction changed the balance of a executable account")] #[error("instruction changed the balance of a executable account")]
ExecutableLamportChange, ExecutableLamportChange,
/// Executable accounts must be rent exempt
#[error("executable accounts must be rent exempt")]
ExecutableAccountNotRentExempt,
} }
impl InstructionError { impl InstructionError {