Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions program/src/processor/allocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use pinocchio::{
instruction::{Seed, Signer},
program_error::ProgramError,
pubkey::find_program_address,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
};
use pinocchio_system::instructions::{Allocate, Assign};
Expand Down Expand Up @@ -81,10 +82,6 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
// Allocates the space for the buffer account and assigns it to
// the program.

if buffer.lamports() == 0 {
return Err(ProgramError::AccountNotRentExempt);
}

let space = Buffer::LEN as u64;

match (is_pda, canonical) {
Expand Down Expand Up @@ -138,6 +135,12 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
_ => return Err(ProgramError::InvalidAccountData),
}

let minimum_balance = Rent::get()?.minimum_balance(buffer.data_len());

if buffer.lamports() < minimum_balance {
return Err(ProgramError::AccountNotRentExempt);
}

// Writes the buffer header.

// SAFETY: single mutable borrow of the `buffer` account data. The legth of the buffer account
Expand Down
23 changes: 17 additions & 6 deletions program/src/processor/extend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use core::mem::size_of;
use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult};
use pinocchio::{
account_info::AccountInfo,
program_error::ProgramError,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
};

use crate::state::{buffer::Buffer, AccountDiscriminator};

Expand Down Expand Up @@ -31,7 +36,7 @@ pub fn extend(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResul
// - must be a buffer or metadata account
// - must have a valid authority
// - must be rent exempt (pre-funded account) since we are reallocating the buffer
// account (this is tested implicity)
// account

if account.data_is_empty() {
return Err(ProgramError::InvalidAccountData);
Expand All @@ -54,11 +59,17 @@ pub fn extend(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResul
}
}

// Reallocates the account size.

// The length of the data is never more than `10_000_000`; adding a `u16`
// will never overflow the `usize` limit.
account.realloc(account.data_len() + extend_length as usize, false)?;
let length = account.data_len() + extend_length as usize;

let minimum_balance = Rent::get()?.minimum_balance(length);

if account.lamports() < minimum_balance {
return Err(ProgramError::AccountNotRentExempt);
}

// Reallocates the account size.

Ok(())
account.realloc(length, false)
}
18 changes: 12 additions & 6 deletions program/src/processor/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use pinocchio::{
memory::sol_memcpy,
program_error::ProgramError,
pubkey::{find_program_address, Pubkey},
seeds, ProgramResult,
seeds,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
};
use pinocchio_system::instructions::{Allocate, Assign};

Expand Down Expand Up @@ -101,9 +103,6 @@ pub fn initialize(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramR
return Err(ProgramError::AccountAlreadyInitialized)
}
None => {
if metadata.lamports() == 0 {
return Err(ProgramError::AccountNotRentExempt);
}
// Ensure remaining data is provided.
if remaining_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
Expand All @@ -124,11 +123,12 @@ pub fn initialize(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramR
)
};
let signer = &[Signer::from(signer_seeds)];
// Instruction data is limited to ~1232 bytes.
let space = Header::LEN + remaining_data.len();

Allocate {
account: metadata,
// Instruction data is limited to 1232 bytes.
space: (Header::LEN + remaining_data.len()) as u64,
space: space as u64,
}
.invoke_signed(signer)?;

Expand All @@ -138,6 +138,12 @@ pub fn initialize(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramR
}
.invoke_signed(signer)?;

let minimum_balance = Rent::get()?.minimum_balance(space);

if metadata.lamports() < minimum_balance {
return Err(ProgramError::AccountNotRentExempt);
}

// SAFETY: scoped mutable borrow of `metadata` account data. The data is
// guaranteed to be allocated and assigned to the program.
let metadata_account_data = unsafe { metadata.borrow_mut_data_unchecked() };
Expand Down
16 changes: 12 additions & 4 deletions program/src/processor/trim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,27 @@ pub fn trim(accounts: &[AccountInfo]) -> ProgramResult {

let minimum_balance = {
// SAFETY: single immutable borrow of `rent_sysver` account data.
let rent = unsafe { Rent::from_bytes(rent_sysvar.borrow_data_unchecked())? };
let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar)? };
rent.minimum_balance(length)
};

account.realloc(length, false)?;

// Current lamports should always be greater than or equal to the minimum
// balance since the account must be rent exempt.
let excess_lamports = account
.lamports()
.checked_sub(minimum_balance)
.ok_or(ProgramError::AccountNotRentExempt)?;

// SAFETY: single mutable borrow if `account` and `destination` lamports.
unsafe {
let account_lamports = account.borrow_mut_lamports_unchecked();
let destination_lamports = destination.borrow_mut_lamports_unchecked();
// Current lamports should always be greater than or equal to the minimum
// balance since the account is rent exempt.
*destination_lamports += *account_lamports - minimum_balance;

*destination_lamports = destination_lamports
.checked_add(excess_lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
*account_lamports = minimum_balance;
}

Expand Down
14 changes: 12 additions & 2 deletions program/src/processor/write.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use core::cmp::max;

use pinocchio::{
account_info::AccountInfo, memory::sol_memcpy, program_error::ProgramError, ProgramResult,
account_info::AccountInfo,
memory::sol_memcpy,
program_error::ProgramError,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
};

use crate::state::{buffer::Buffer, header::Header, AccountDiscriminator};
Expand Down Expand Up @@ -34,7 +38,7 @@ pub fn write(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult
// target_buffer
// - must be initialized
// - must be rent exempt (pre-funded account) since we are reallocating the buffer
// account (this is tested implicity)
// account

let (required_length, source_data) = {
// SAFETY: scoped immutable borrow of `buffer` account data. There
Expand Down Expand Up @@ -78,6 +82,12 @@ pub fn write(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult
(max(data.len(), offset + source_data.len()), source_data)
};

let minimum_balance = Rent::get()?.minimum_balance(required_length);

if target_buffer.lamports() < minimum_balance {
return Err(ProgramError::AccountNotRentExempt);
}

// Writes the source data to the buffer account.

target_buffer.realloc(required_length, false)?;
Expand Down
50 changes: 49 additions & 1 deletion program/tests/trim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ pub use setup::*;

use mollusk_svm::{program::keyed_account_for_system_program, result::Check};
use solana_account::Account;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use solana_sdk_ids::system_program;
use solana_sdk_ids::{system_program, sysvar::rent};
use spl_program_metadata::state::{buffer::Buffer, header::Header};

const EXCESS_LAMPORTS: usize = 90;
Expand Down Expand Up @@ -256,3 +257,50 @@ fn test_trim_buffer() {
],
);
}

#[test]
fn fail_trim_non_rent_exempt_account() {
let destination_key = Pubkey::new_unique();
let destination_account = create_empty_account(Buffer::LEN, PROGRAM_ID);

// A program-owned account non-rent exempt on purpose to try trigger
// an underflow
let fake_buffer_key = Pubkey::new_unique();
let mut fake_buffer_account = Account {
lamports: 0,
owner: PROGRAM_ID,
data: vec![1; Buffer::LEN], // buffer discriminator
..Default::default()
};
// Set the authority to a non-zero value to pass the authority validation.
fake_buffer_account.data[33..65].copy_from_slice(fake_buffer_key.as_array());

let destination_lamports = destination_account.lamports;

process_instructions(
&[(
&trim(
&fake_buffer_key,
&fake_buffer_key,
None,
None,
&destination_key,
)
.unwrap(),
&[
Check::err(ProgramError::AccountNotRentExempt),
Check::account(&destination_key)
.lamports(destination_lamports)
.build(),
Check::account(&fake_buffer_key).lamports(0).build(),
],
)],
&[
(fake_buffer_key, fake_buffer_account),
(PROGRAM_ID, Account::default()),
(destination_key, destination_account),
keyed_account_for_system_program(),
(rent::ID, rent_sysvar()),
],
);
}
86 changes: 86 additions & 0 deletions scripts/rust/audit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,92 @@ const advisories = [
// URL: https://rustsec.org/advisories/RUSTSEC-2024-0376
// Solution: Upgrade to >=0.12.3
'RUSTSEC-2024-0376',

// Remove the ignores below once the dependencies are updated to versions
// that have the vulnerabilities fixed.

// Crate: quinn-proto
// Version: 0.11.12
// Title: Denial of service in Quinn endpoints
// Date: 2026-03-09
// ID: RUSTSEC-2026-0037
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0037
// Severity: 8.7 (high)
// Solution: Upgrade to >=0.11.14
'RUSTSEC-2026-0037',

//Crate: rustls-webpki
// Version: 0.101.7
// Title: Reachable panic in certificate revocation list parsing
// Date: 2026-04-22
// ID: RUSTSEC-2026-0104
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0104
// Solution: Upgrade to >=0.103.13, <0.104.0-alpha.1 OR >=0.104.0-alpha.7
'RUSTSEC-2026-0104',

// Crate: rustls-webpki
// Version: 0.101.7
// Title: Name constraints for URI names were incorrectly accepted
// Date: 2026-04-14
// ID: RUSTSEC-2026-0098
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0098
// Solution: Upgrade to >=0.103.12, <0.104.0-alpha.1 OR >=0.104.0-alpha.6
'RUSTSEC-2026-0098',

// Crate: rustls-webpki
// Version: 0.101.7
// Title: Name constraints were accepted for certificates asserting a wildcard name
// Date: 2026-04-14
// ID: RUSTSEC-2026-0099
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0099
// Solution: Upgrade to >=0.103.12, <0.104.0-alpha.1 OR >=0.104.0-alpha.6
'RUSTSEC-2026-0099',

// Crate: rustls-webpki
// Version: 0.103.4
// Title: Reachable panic in certificate revocation list parsing
// Date: 2026-04-22
// ID: RUSTSEC-2026-0104
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0104
// Solution: Upgrade to >=0.103.13, <0.104.0-alpha.1 OR >=0.104.0-alpha.7
'RUSTSEC-2026-0104',

// Crate: rustls-webpki
// Version: 0.103.4
// Title: CRLs not considered authoritative by Distribution Point due to faulty matching logic
// Date: 2026-03-20
// ID: RUSTSEC-2026-0049
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0049
// Solution: Upgrade to >=0.103.10
'RUSTSEC-2026-0049',

// Crate: rustls-webpki
// Version: 0.103.4
// Title: Name constraints for URI names were incorrectly accepted
// Date: 2026-04-14
// ID: RUSTSEC-2026-0098
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0098
// Solution: Upgrade to >=0.103.12, <0.104.0-alpha.1 OR >=0.104.0-alpha.6
'RUSTSEC-2026-0098',

// Crate: rustls-webpki
// Version: 0.103.4
// Title: Name constraints were accepted for certificates asserting a wildcard name
// Date: 2026-04-14
// ID: RUSTSEC-2026-0099
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0099
// Solution: Upgrade to >=0.103.12, <0.104.0-alpha.1 OR >=0.104.0-alpha.6
'RUSTSEC-2026-0099',

// Crate: time
// Version: 0.3.41
// Title: Denial of Service via Stack Exhaustion
// Date: 2026-02-05
// ID: RUSTSEC-2026-0009
// URL: https://rustsec.org/advisories/RUSTSEC-2026-0009
// Severity: 6.8 (medium)
// Solution: Upgrade to >=0.3.47
'RUSTSEC-2026-0009'
];
const ignores = []
advisories.forEach(x => {
Expand Down
Loading