Prevent add/subtract from executable account (#9132)

This commit is contained in:
Jack May 2020-03-27 16:43:25 -07:00 committed by GitHub
parent 4a8b1d9b2c
commit e2491c6322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 45 deletions

View File

@ -2974,11 +2974,14 @@ mod tests {
genesis_config.hash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
assert_eq!(
bank.get_balance(&account_pubkey),
account_balance + transfer_lamports
bank.process_transaction(&tx),
Err(TransactionError::InstructionError(
0,
InstructionError::ExecutableLamportChange
))
);
assert_eq!(bank.get_balance(&account_pubkey), account_balance);
}
#[test]

View File

@ -20,6 +20,7 @@ use libloading::os::windows::*;
// The relevant state of an account before an Instruction executes, used
// to verify account integrity after the Instruction completes
#[derive(Debug)]
pub struct PreAccount {
pub is_writable: bool,
pub lamports: u64,
@ -84,11 +85,14 @@ impl PreAccount {
return Err(InstructionError::ExternalAccountLamportSpend);
}
// The balance of read-only accounts may not change.
if !self.is_writable // line coverage used to get branch coverage
&& self.lamports != post.lamports
{
return Err(InstructionError::ReadonlyLamportChange);
// The balance of read-only and executable accounts may not change
if self.lamports != post.lamports {
if !self.is_writable {
return Err(InstructionError::ReadonlyLamportChange);
}
if self.executable {
return Err(InstructionError::ExecutableLamportChange);
}
}
// Only the system program can change the size of the data
@ -487,76 +491,138 @@ mod tests {
#[test]
fn test_verify_account_changes_executable() {
let owner = Pubkey::new_rand();
let change_executable = |program_id: &Pubkey,
is_writable: bool,
pre_executable: bool,
post_executable: bool,
pre_data: Vec<u8>,
post_data: Vec<u8>|
-> Result<(), InstructionError> {
let pre = PreAccount::new(
&Account {
owner,
executable: pre_executable,
data: pre_data,
..Account::default()
},
is_writable,
&program_id,
);
let post = Account {
owner,
executable: post_executable,
data: post_data,
..Account::default()
};
pre.verify(&program_id, &post)
};
let mallory_program_id = Pubkey::new_rand();
let system_program_id = system_program::id();
struct Change {
pre: PreAccount,
post: Account,
program_id: Pubkey,
}
impl Change {
pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
Self {
pre: PreAccount::new(
&Account {
owner: *owner,
data: vec![],
..Account::default()
},
true,
&Pubkey::new_rand(), // Force some data, ignored if not needed
),
post: Account {
owner: *owner,
..Account::default()
},
program_id: *program_id,
}
}
pub fn read_only(mut self) -> Self {
self.pre.is_writable = false;
self
}
pub fn executable(mut self, pre: bool, post: bool) -> Self {
self.pre.executable = pre;
self.post.executable = post;
self
}
pub fn lamports(mut self, pre: u64, post: u64) -> Self {
self.pre.lamports = pre;
self.post.lamports = post;
self
}
pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
self.pre.data_len = pre.len();
self.pre.data = Some(pre);
self.post.data = post;
self
}
pub fn verify(self) -> Result<(), InstructionError> {
self.pre.verify(&self.program_id, &self.post)
}
}
assert_eq!(
change_executable(&system_program_id, true, false, true, vec![1], vec![1]),
Change::new(&owner, &system_program_id)
.executable(false, true)
.verify(),
Err(InstructionError::ExecutableModified),
"system program can't change executable if system doesn't own the account"
);
assert_eq!(
change_executable(&system_program_id, true, true, true, vec![1], vec![2]),
Change::new(&owner, &system_program_id)
.executable(true, true)
.data(vec![1], vec![2])
.verify(),
Err(InstructionError::ExecutableDataModified),
"system program can't change executable data if system doesn't own the account"
);
assert_eq!(
change_executable(&owner, true, false, true, vec![1], vec![1]),
Change::new(&owner, &owner).executable(false, true).verify(),
Ok(()),
"owner should be able to change executable"
);
assert_eq!(
change_executable(&owner, false, false, true, vec![1], vec![1]),
Change::new(&owner, &owner)
.executable(false, true)
.read_only()
.verify(),
Err(InstructionError::ExecutableModified),
"owner can't modify executable of read-only accounts"
);
assert_eq!(
change_executable(&owner, true, true, false, vec![1], vec![1]),
Change::new(&owner, &owner).executable(true, false).verify(),
Err(InstructionError::ExecutableModified),
"owner program can't reverse executable"
);
assert_eq!(
change_executable(&mallory_program_id, true, false, true, vec![1], vec![1]),
Change::new(&owner, &mallory_program_id)
.executable(false, true)
.verify(),
Err(InstructionError::ExecutableModified),
"malicious Mallory should not be able to change the account executable"
);
assert_eq!(
change_executable(&owner, true, false, true, vec![1], vec![2]),
Change::new(&owner, &owner)
.executable(false, true)
.data(vec![1], vec![2])
.verify(),
Ok(()),
"account data can change in the same instruction that sets the bit"
);
assert_eq!(
change_executable(&owner, true, true, true, vec![1], vec![2]),
Change::new(&owner, &owner)
.executable(true, true)
.data(vec![1], vec![2])
.verify(),
Err(InstructionError::ExecutableDataModified),
"owner should not be able to change an account's data once its marked executable"
);
assert_eq!(
Change::new(&owner, &owner)
.executable(true, true)
.lamports(1, 2)
.verify(),
Err(InstructionError::ExecutableLamportChange),
"owner should not be able to add lamports once makred executable"
);
assert_eq!(
Change::new(&owner, &owner)
.executable(true, true)
.lamports(1, 2)
.verify(),
Err(InstructionError::ExecutableLamportChange),
"owner should not be able to add lamports once marked executable"
);
assert_eq!(
Change::new(&owner, &owner)
.executable(true, true)
.lamports(2, 1)
.verify(),
Err(InstructionError::ExecutableLamportChange),
"owner should not be able to subtract lamports once marked executable"
);
}
#[test]

View File

@ -65,8 +65,8 @@ pub enum InstructionError {
#[error("instruction modified data of an account it does not own")]
ExternalAccountDataModified,
/// Read-only account modified lamports
#[error("instruction changed balance of a read-only account")]
/// Read-only account's lamports modified
#[error("instruction changed the balance of a read-only account")]
ReadonlyLamportChange,
/// Read-only account's data was modified
@ -126,6 +126,10 @@ pub enum InstructionError {
/// Executable account's data was modified
#[error("instruction changed executable accounts data")]
ExecutableDataModified,
/// Executable account's lamports modified
#[error("instruction changed the balance of a executable account")]
ExecutableLamportChange,
}
impl InstructionError {