Skip to content

Commit fb104a2

Browse files
authored
program: Improve account length validation (#55)
* Stricter account length validation * Address review comments
1 parent 519673d commit fb104a2

10 files changed

Lines changed: 49 additions & 55 deletions

File tree

program/src/processor/allocate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ pub fn allocate(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra
150150

151151
// SAFETY: single mutable borrow of the `buffer` account data. The legth of the buffer account
152152
// data has been checked to be at least `Buffer::LEN` and uninitialized.
153-
let buffer_header = unsafe { Buffer::from_bytes_mut_unchecked(buffer.borrow_unchecked_mut()) };
153+
let buffer_header = Buffer::from_bytes_mut(unsafe { buffer.borrow_unchecked_mut() })?;
154154
buffer_header.discriminator = AccountDiscriminator::Buffer as u8;
155155
buffer_header.authority = (*authority.address()).into();
156156

program/src/processor/close.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ pub fn close(accounts: &mut [AccountView]) -> ProgramResult {
3232
let account_data = if account.is_data_empty() {
3333
return Err(ProgramError::UninitializedAccount);
3434
} else {
35+
// SAFETY: Single borrow of the `account` data.
3536
unsafe { account.borrow_unchecked() }
3637
};
3738

3839
match AccountDiscriminator::try_from(account_data[0])? {
3940
AccountDiscriminator::Buffer => {
40-
let buffer = unsafe { Buffer::from_bytes_unchecked(account_data) };
41+
let buffer = Buffer::from_bytes(account_data)?;
4142
validate_authority(buffer, authority, program, program_data)?
4243
}
4344
AccountDiscriminator::Metadata => {
44-
let header = validate_metadata(account)?;
45+
let header = validate_metadata(account_data)?;
4546
validate_authority(header, authority, program, program_data)?
4647
}
4748
_ => return Err(ProgramError::InvalidAccountData),

program/src/processor/extend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub fn extend(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramR
5555
validate_authority(buffer, authority, program, program_data)?
5656
}
5757
Ok(AccountDiscriminator::Metadata) => {
58-
let metadata = validate_metadata(account)?;
58+
let metadata = validate_metadata(data)?;
5959
validate_authority(metadata, authority, program, program_data)?
6060
}
6161
_ => return Err(ProgramError::InvalidAccountData),

program/src/processor/initialize.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,14 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog
9696
// When using a pre-allocated buffer, no remaining instruction data
9797
// is allowed.
9898
if !remaining_data.is_empty() {
99-
return Err(ProgramError::InvalidAccountData);
99+
return Err(ProgramError::InvalidInstructionData);
100100
}
101-
// A pre-allocated buffer length is at least the size of the
101+
// A pre-allocated buffer length should be at least the size of the
102102
// `Header`.
103-
metadata.data_len() - Header::LEN
103+
metadata
104+
.data_len()
105+
.checked_sub(Header::LEN)
106+
.ok_or(ProgramError::InvalidAccountData)?
104107
}
105108
Some(AccountDiscriminator::Metadata) => {
106109
return Err(ProgramError::AccountAlreadyInitialized)
@@ -177,7 +180,7 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog
177180
// Initialize the metadata account.
178181

179182
// SAFETY: there are no other active borrows to `metadata` account data and
180-
// the account discriminator has been validated.
183+
// the account length has been validated to be sufficient to hold a `Header`.
181184
let header = unsafe { Header::from_bytes_mut_unchecked(metadata.borrow_unchecked_mut()) };
182185

183186
header.discriminator = AccountDiscriminator::Metadata as u8;

program/src/processor/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ fn is_program_authority(
114114
/// be [`AccountDiscriminator::Metadata`].
115115
/// - The `metadata` account must be mutable (`mutable = true`).
116116
#[inline(always)]
117-
fn validate_metadata(metadata: &AccountView) -> Result<&Header, ProgramError> {
118-
let header = unsafe { Header::from_bytes_unchecked(metadata.borrow_unchecked()) };
117+
fn validate_metadata(bytes: &[u8]) -> Result<&Header, ProgramError> {
118+
let header = Header::from_bytes(bytes)?;
119119
if header.discriminator != AccountDiscriminator::Metadata as u8 {
120120
return Err(ProgramError::UninitializedAccount);
121121
}

program/src/processor/set_data.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,31 @@ pub fn set_data(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra
4040
//
4141
// Note that program owned and writable checks are done implicitly by writing
4242
// to the account.
43+
{
44+
// metadata
45+
// - must be initialized
46+
// - must be mutable
4347

44-
// metadata
45-
// - must be initialized
46-
// - must be mutable
48+
// SAFETY: Scoped immutable borrow of `metadata` account data for validation.
49+
let metadata_account_data = unsafe { metadata.borrow_unchecked() };
50+
let header = validate_metadata(metadata_account_data)?;
4751

48-
let header = validate_metadata(metadata)?;
52+
// authority
53+
// - must be a signer
54+
// - must match the authority set on the `metadata` account OR it must be the
55+
// program upgrade authority if the `metadata` account is canonical
4956

50-
// authority
51-
// - must be a signer
52-
// - must match the authority set on the `metadata` account OR it must be the
53-
// program upgrade authority if the `metadata` account is canonical
54-
55-
validate_authority(header, authority, program, program_data)?;
57+
validate_authority(header, authority, program, program_data)?;
58+
}
5659

57-
// Get data from buffer or remaining instruction data, if any.
5860
let has_buffer = buffer.address() != &crate::ID;
61+
// Get data from buffer or remaining instruction data, if any.
5962
let data = match (optional_data, has_buffer) {
6063
(Some((data_source, Some(remaining_data))), false) => Some((data_source, remaining_data)),
6164
(Some((data_source, None)), true) => {
62-
// SAFETY: singe immutable borrow of `buffer` account data.
65+
// SAFETY: single immutable borrow of `buffer` account data.
6366
let buffer_data = unsafe { buffer.borrow_unchecked() };
67+
6468
match AccountDiscriminator::try_from_bytes(buffer_data)? {
6569
Some(AccountDiscriminator::Buffer) => {
6670
Some((data_source, &buffer_data[Header::LEN..]))

program/src/processor/set_immutable.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use pinocchio::{account::AccountView, error::ProgramError, ProgramResult};
22

3-
use crate::{error::ProgramMetadataError, state::header::Header};
3+
use crate::state::header::Header;
44

55
use super::{validate_authority, validate_metadata};
66

@@ -22,7 +22,9 @@ pub fn set_immutable(accounts: &mut [AccountView]) -> ProgramResult {
2222
// - must be initialized
2323
// - must be mutable
2424

25-
let header = validate_metadata(metadata)?;
25+
// SAFETY: There are no active borrows of the `metadata` account data.
26+
let metadata_account_data = unsafe { metadata.borrow_unchecked_mut() };
27+
let header = validate_metadata(metadata_account_data)?;
2628

2729
// authority
2830
// - must be a signer
@@ -33,15 +35,10 @@ pub fn set_immutable(accounts: &mut [AccountView]) -> ProgramResult {
3335

3436
// Make the metadata account immutable.
3537

36-
// SAFETY: There are no active borrows of the `metadata` account data and the
37-
// account has been validated.
38-
let header = unsafe { Header::from_bytes_mut_unchecked(metadata.borrow_unchecked_mut()) };
39-
40-
if header.mutable() {
41-
header.mutable = 0;
42-
} else {
43-
return Err(ProgramMetadataError::ImmutableMetadataAccount.into());
44-
}
38+
// SAFETY: `metadata_account_data` has been validated to be initialized
39+
// and mutable.
40+
let header = unsafe { Header::from_bytes_mut_unchecked(metadata_account_data) };
41+
header.mutable = 0;
4542

4643
Ok(())
4744
}

program/src/processor/trim.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ pub fn trim(accounts: &mut [AccountView]) -> ProgramResult {
4343
account.data_len()
4444
}
4545
Ok(AccountDiscriminator::Metadata) => {
46-
let metadata = validate_metadata(account)?;
47-
validate_authority(metadata, authority, program, program_data)?;
46+
let header = validate_metadata(data)?;
47+
validate_authority(header, authority, program, program_data)?;
4848
// The length of the data is never more than `10_000_000`.
49-
Header::LEN + metadata.data_length() as usize
49+
Header::LEN + header.data_length() as usize
5050
}
5151
_ => return Err(ProgramError::UninitializedAccount),
5252
}

program/src/processor/write.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,28 @@ pub fn write(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramRe
4747
return Err(ProgramError::InvalidAccountData);
4848
}
4949

50-
// SAFETY: `buffer` account data is guaranteed to be a `Buffer`.
51-
let buffer_header = unsafe { Buffer::from_bytes_unchecked(data) };
50+
// `data` was validated to have a `Buffer` discriminator.
51+
let buffer_header = Buffer::from_bytes(data)?;
5252

5353
if Some(authority.address()) != buffer_header.authority.as_ref() {
5454
return Err(ProgramError::IncorrectAuthority);
5555
}
5656

57-
// Determine from where to copy the data.
57+
// Determine from where to copy the data, ether from the instruction data
58+
// or the source buffer account.
5859
let instruction_data = match args.data() {
5960
source_data if !source_data.is_empty() => Some(source_data),
6061
_ => None,
6162
};
6263

63-
let buffer_data = if source_buffer.address() != &crate::ID {
64+
let source_buffer_data = if source_buffer.address() != &crate::ID {
6465
// SAFETY: singe immutable borrow of `source_buffer` account data.
6566
Some(unsafe { source_buffer.borrow_unchecked() })
6667
} else {
6768
None
6869
};
6970

70-
let source_data = match (instruction_data, buffer_data) {
71+
let source_data = match (instruction_data, source_buffer_data) {
7172
(Some(instruction_data), None) => instruction_data,
7273
(None, Some(buffer_data)) => match AccountDiscriminator::try_from_bytes(buffer_data)? {
7374
Some(AccountDiscriminator::Buffer) => &buffer_data[Header::LEN..],
@@ -76,7 +77,8 @@ pub fn write(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramRe
7677
_ => return Err(ProgramError::InvalidInstructionData),
7778
};
7879

79-
// The length of the data to write is validated by the `try_minimum_balance`.
80+
// The length of the data to write is validated by the `try_minimum_balance` and
81+
// `offset` is a `u32` value and `source_data` is at most `10_000_000` bytes.
8082
(max(data.len(), offset + source_data.len()), source_data)
8183
};
8284

program/src/state/mod.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@ impl<'a> Metadata<'a> {
3232
let data = Data::from_bytes(header.data_source()?, &bytes[Header::LEN..])?;
3333
Ok(Self { header, data })
3434
}
35-
36-
/*
37-
/// Return a `Metadata` from the given bytes.
38-
///
39-
/// # Safety
40-
///
41-
/// The caller must ensure that `bytes` contains a valid representation of `Metadata`.
42-
pub(crate) unsafe fn from_bytes_unchecked(bytes: &'a [u8]) -> Self {
43-
let header = Header::from_bytes_unchecked(bytes);
44-
let data = Data::from_bytes_unchecked(header.data_source().unwrap(), &bytes[Header::LEN..]);
45-
Self { header, data }
46-
}
47-
*/
4835
}
4936

5037
/// Utility trait for an account.

0 commit comments

Comments
 (0)