Input values are not sanitized after they are deserialized, making it far too easy for Leo to earn SOL (#9706)

* sanitize gossip protocol messages
* sanitize transactions
* crds protocol sanitize
This commit is contained in:
anatoly yakovenko
2020-04-27 11:06:00 -07:00
committed by GitHub
parent c372a39dd3
commit 8ef097bf6f
13 changed files with 333 additions and 31 deletions

View File

@@ -1,5 +1,6 @@
//! Defines a Transaction type to package an atomic sequence of instructions.
use crate::sanitize::{Sanitize, SanitizeError};
use crate::{
hash::Hash,
instruction::{CompiledInstruction, Instruction, InstructionError},
@@ -86,6 +87,18 @@ pub struct Transaction {
pub message: Message,
}
impl Sanitize for Transaction {
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
if self.message.header.num_required_signatures as usize > self.signatures.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
if self.signatures.len() > self.message.account_keys.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
self.message.sanitize()
}
}
impl Transaction {
pub fn new_unsigned(message: Message) -> Self {
Self {
@@ -364,22 +377,6 @@ impl Transaction {
.iter()
.all(|signature| *signature != Signature::default())
}
/// Verify that references in the instructions are valid
pub fn verify_refs(&self) -> bool {
let message = self.message();
for instruction in &message.instructions {
if (instruction.program_id_index as usize) >= message.account_keys.len() {
return false;
}
for account_index in &instruction.accounts {
if (*account_index as usize) >= message.account_keys.len() {
return false;
}
}
}
true
}
}
#[cfg(test)]
@@ -418,7 +415,7 @@ mod tests {
vec![prog1, prog2],
instructions,
);
assert!(tx.verify_refs());
assert!(tx.sanitize().is_ok());
assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
assert_eq!(tx.signer_key(0, 0), Some(&key.pubkey()));
@@ -452,7 +449,7 @@ mod tests {
vec![],
instructions,
);
assert!(!tx.verify_refs());
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_refs_invalid_account() {
@@ -466,7 +463,54 @@ mod tests {
instructions,
);
assert_eq!(*get_program_id(&tx, 0), Pubkey::default());
assert!(!tx.verify_refs());
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_sanitize_txs() {
let key = Keypair::new();
let id0 = Pubkey::default();
let program_id = Pubkey::new_rand();
let ix = Instruction::new(
program_id,
&0,
vec![
AccountMeta::new(key.pubkey(), true),
AccountMeta::new(id0, true),
],
);
let ixs = vec![ix];
let mut tx = Transaction::new_with_payer(ixs, Some(&key.pubkey()));
let o = tx.clone();
assert_eq!(tx.sanitize(), Ok(()));
assert_eq!(tx.message.account_keys.len(), 3);
tx = o.clone();
tx.message.header.num_required_signatures = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 4;
tx.message.header.num_readonly_unsigned_accounts = 0;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 2;
tx.message.header.num_readonly_unsigned_accounts = 2;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 0;
tx.message.header.num_readonly_unsigned_accounts = 4;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.instructions[0].program_id_index = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.instructions[0].accounts[0] = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
fn create_sample_transaction() -> Transaction {