Skip to content

Commit bbe5202

Browse files
committed
additional account deserialization checks
1 parent 691812d commit bbe5202

3 files changed

Lines changed: 46 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/putils/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "putils"
3-
version = "0.0.9"
3+
version = "0.0.10"
44
description = "Utilities for writing Solana programs with the pinocchio framework"
55
keywords = ["solana", "pinocchio"]
66
documentation = "https://docs.rs/putils/latest/putils/"

crates/putils/src/account.rs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
use {
44
crate::discriminator::AccountDiscriminator,
55
pinocchio::{
6-
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
6+
account_info::AccountInfo,
7+
program_error::ProgramError,
8+
pubkey::{find_program_address, Pubkey},
9+
ProgramResult,
710
},
811
};
912

@@ -70,17 +73,52 @@ pub trait AccountWrite: AccountSerialize {
7073
}
7174

7275
/// The AccountRead trait is used to handle deserializing an account from [`AccountInfo`]
73-
pub trait AccountRead: AccountDeserialize + Sized {
74-
const PROGRAM_ID: Pubkey;
75-
/// Reads account data, validating the account discriminator and program owner
76+
pub trait AccountRead: AccountDeserialize + PdaDeriver + Sized {
77+
/// Reads account data, validating the following:
78+
/// * Account discriminator
79+
/// * Account program owner
80+
/// * Account address
81+
///
82+
/// Account address validation is used to prevent exploits whereby an attacker may create an account in a different program
83+
/// and store data inside this account that would pass the discriminator and deserialization checks. Then assign the owner of that account
84+
/// to your program.
85+
///
86+
/// For example if I create an account that stores a bump seed, and assign a value to that bump seed, this will still pass account discriminator
87+
/// and deserialization checks. However when the [`PdaDeriver::create_pda`] check is performed, the validation will fail.
7688
fn account_read(account_info: &AccountInfo) -> Result<Self, ProgramError> {
89+
// validate the account owner
7790
if !account_info.is_owned_by(&Self::PROGRAM_ID) {
78-
return Err(ProgramError::IllegalOwner);
91+
return Err(ProgramError::InvalidAccountOwner);
7992
}
80-
Self::try_from_bytes(&account_info.try_borrow_data()?)
93+
94+
// deserialize the account and validate the discriminator
95+
let account = Self::try_from_bytes(&account_info.try_borrow_data()?)?;
96+
97+
// validate the account address
98+
let expected_pda = account.create_pda();
99+
if !account_info.key().eq(&expected_pda) {
100+
return Err(ProgramError::InvalidSeeds);
101+
}
102+
103+
Ok(account)
81104
}
82105
}
83106

107+
/// The PdaDeriver trait is used to define how to derive a PDA for a specific account
108+
pub trait PdaDeriver: ProgramId {
109+
/// Derives a PDA from the provided seeds
110+
fn derive_pda(seeds: &[u8]) -> (Pubkey, u8) {
111+
find_program_address(&[seeds], &Self::PROGRAM_ID)
112+
}
113+
/// Creates a PDA from values in the account
114+
fn create_pda(&self) -> Pubkey;
115+
}
116+
117+
/// The ProgramId trait is used to specify the expected owner of an account
118+
pub trait ProgramId {
119+
const PROGRAM_ID: Pubkey;
120+
}
121+
84122
#[cfg(test)]
85123
mod test {
86124
use {

0 commit comments

Comments
 (0)