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
4,611 changes: 2,334 additions & 2,277 deletions Cargo.lock

Large diffs are not rendered by default.

77 changes: 19 additions & 58 deletions program/src/processor/create_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use assertions::{check_zero_data, sol_assert_bytes_eq};
use no_padding::NoPadding;
use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Seed, Signer},
program::invoke_signed,
instruction::Seed,
program_error::ProgramError,
pubkey::{find_program_address, Pubkey},
ProgramResult,
Expand Down Expand Up @@ -140,41 +139,22 @@ pub fn process(
.and_then(|val| val.checked_add(rent_base))
.ok_or(ProgramError::ArithmeticOverflow)?;

let mut create_wallet_ix_data = Vec::with_capacity(52);
create_wallet_ix_data.extend_from_slice(&0u32.to_le_bytes());
create_wallet_ix_data.extend_from_slice(&wallet_rent.to_le_bytes());
create_wallet_ix_data.extend_from_slice(&(wallet_space as u64).to_le_bytes());
create_wallet_ix_data.extend_from_slice(program_id.as_ref());

let wallet_accounts_meta = [
AccountMeta {
pubkey: payer.key(),
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: wallet_pda.key(),
is_signer: true, // Must be true even with invoke_signed
is_writable: true,
},
];
let create_wallet_ix = Instruction {
program_id: system_program.key(),
accounts: &wallet_accounts_meta,
data: &create_wallet_ix_data,
};
// Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4)
let wallet_bump_arr = [wallet_bump];
let wallet_seeds = [
Seed::from(b"wallet"),
Seed::from(&args.user_seed),
Seed::from(&wallet_bump_arr),
];
let wallet_signer: Signer = (&wallet_seeds).into();

invoke_signed(
&create_wallet_ix,
&[&payer.clone(), &wallet_pda.clone(), &system_program.clone()],
&[wallet_signer],
crate::utils::initialize_pda_account(
payer,
wallet_pda,
system_program,
wallet_space,
wallet_rent,
program_id,
&wallet_seeds,
)?;

// Write Wallet Data
Expand Down Expand Up @@ -209,42 +189,23 @@ pub fn process(
.and_then(|val| val.checked_add(897840))
.ok_or(ProgramError::ArithmeticOverflow)?;

let mut create_auth_ix_data = Vec::with_capacity(52);
create_auth_ix_data.extend_from_slice(&0u32.to_le_bytes());
create_auth_ix_data.extend_from_slice(&auth_rent.to_le_bytes());
create_auth_ix_data.extend_from_slice(&(auth_space as u64).to_le_bytes());
create_auth_ix_data.extend_from_slice(program_id.as_ref());

let auth_accounts_meta = [
AccountMeta {
pubkey: payer.key(),
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: auth_pda.key(),
is_signer: true, // Must be true even with invoke_signed
is_writable: true,
},
];
let create_auth_ix = Instruction {
program_id: system_program.key(),
accounts: &auth_accounts_meta,
data: &create_auth_ix_data,
};
// Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4)
let auth_bump_arr = [auth_bump];
let auth_seeds = [
Seed::from(b"authority"),
Seed::from(wallet_key.as_ref()),
Seed::from(id_seed),
Seed::from(&auth_bump_arr),
];
let auth_signer: Signer = (&auth_seeds).into();

invoke_signed(
&create_auth_ix,
&[&payer.clone(), &auth_pda.clone(), &system_program.clone()],
&[auth_signer],
crate::utils::initialize_pda_account(
payer,
auth_pda,
system_program,
auth_space,
auth_rent,
program_id,
&auth_seeds,
)?;

// Write Authority Data
Expand Down
48 changes: 11 additions & 37 deletions program/src/processor/manage_authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use assertions::{check_zero_data, sol_assert_bytes_eq};
use no_padding::NoPadding;
use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Seed, Signer},
program::invoke_signed,
instruction::Seed,
program_error::ProgramError,
pubkey::{find_program_address, Pubkey},
ProgramResult,
Expand Down Expand Up @@ -201,48 +200,23 @@ pub fn process_add_authority(
.and_then(|val| val.checked_add(897840))
.ok_or(ProgramError::ArithmeticOverflow)?;

// ... (create_ix logic same) ...

let mut create_ix_data = Vec::with_capacity(52);
create_ix_data.extend_from_slice(&0u32.to_le_bytes());
create_ix_data.extend_from_slice(&rent.to_le_bytes());
create_ix_data.extend_from_slice(&(space as u64).to_le_bytes());
create_ix_data.extend_from_slice(program_id.as_ref());

let accounts_meta = [
AccountMeta {
pubkey: payer.key(),
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: new_auth_pda.key(),
is_signer: true, // Must be true even with invoke_signed
is_writable: true,
},
];
let create_ix = Instruction {
program_id: system_program.key(),
accounts: &accounts_meta,
data: &create_ix_data,
};
// Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4)
let bump_arr = [bump];
let seeds = [
Seed::from(b"authority"),
Seed::from(wallet_pda.key().as_ref()),
Seed::from(id_seed),
Seed::from(&bump_arr),
];
let signer: Signer = (&seeds).into();

invoke_signed(
&create_ix,
&[
&payer.clone(),
&new_auth_pda.clone(),
&system_program.clone(),
],
&[signer],

crate::utils::initialize_pda_account(
payer,
new_auth_pda,
system_program,
space,
rent,
program_id,
&seeds,
)?;

let data = unsafe { new_auth_pda.borrow_mut_data_unchecked() };
Expand Down
40 changes: 10 additions & 30 deletions program/src/processor/transfer_ownership.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use assertions::{check_zero_data, sol_assert_bytes_eq};
use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Seed, Signer},
program::invoke_signed,
instruction::Seed,
program_error::ProgramError,
pubkey::{find_program_address, Pubkey},
ProgramResult,
Expand Down Expand Up @@ -173,42 +172,23 @@ pub fn process(
.and_then(|val| val.checked_add(897840))
.ok_or(ProgramError::ArithmeticOverflow)?;

let mut create_ix_data = Vec::with_capacity(52);
create_ix_data.extend_from_slice(&0u32.to_le_bytes());
create_ix_data.extend_from_slice(&rent.to_le_bytes());
create_ix_data.extend_from_slice(&(space as u64).to_le_bytes());
create_ix_data.extend_from_slice(program_id.as_ref());

let accounts_meta = [
AccountMeta {
pubkey: payer.key(),
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: new_owner.key(),
is_signer: true,
is_writable: true,
},
];
let create_ix = Instruction {
program_id: system_program.key(),
accounts: &accounts_meta,
data: &create_ix_data,
};
// Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4)
let bump_arr = [bump];
let seeds = [
Seed::from(b"authority"),
Seed::from(wallet_pda.key().as_ref()),
Seed::from(id_seed),
Seed::from(&bump_arr),
];
let signer: Signer = (&seeds).into();

invoke_signed(
&create_ix,
&[&payer.clone(), &new_owner.clone(), &system_program.clone()],
&[signer],
crate::utils::initialize_pda_account(
payer,
new_owner,
system_program,
space,
rent,
program_id,
&seeds,
)?;

let data = unsafe { new_owner.borrow_mut_data_unchecked() };
Expand Down
129 changes: 129 additions & 0 deletions program/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Seed, Signer},
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
ProgramResult,
};

/// Wrapper around the `sol_get_stack_height` syscall
pub fn get_stack_height() -> u64 {
#[cfg(target_os = "solana")]
Expand All @@ -7,3 +16,123 @@ pub fn get_stack_height() -> u64 {
#[cfg(not(target_os = "solana"))]
0
}

/// Safely initializes a PDA account using transfer-allocate-assign pattern.
///
/// This prevents DoS attacks where malicious actors pre-fund target accounts
/// with small amounts of lamports, causing the System Program's `create_account`
/// instruction to fail (since it rejects accounts with non-zero balances).
///
/// The transfer-allocate-assign pattern works in three steps:
/// 1. **Transfer**: Add lamports to reach rent-exemption (if needed)
/// 2. **Allocate**: Set the account's data size
/// 3. **Assign**: Transfer ownership to the target program
///
/// # Security
/// - Prevents Issue #4: Create Account DoS vulnerability
/// - Still enforces rent-exemption requirements
/// - Properly assigns ownership to prevent unauthorized access
/// - Works even if account is pre-funded by attacker
///
/// # Arguments
/// * `payer` - Account paying for initialization (must be signer & writable)
/// * `target_pda` - PDA being initialized (will be writable)
/// * `system_program` - System Program account
/// * `space` - Number of bytes to allocate for account data
/// * `rent_lamports` - Minimum lamports for rent-exemption
/// * `owner` - Program that will own this account
/// * `pda_seeds` - Seeds for PDA signing (for allocate & assign)
///
/// # Errors
/// Returns ProgramError if:
/// - Payer has insufficient funds
/// - Any CPI call fails
/// - Account is already owned by another program
pub fn initialize_pda_account(
payer: &AccountInfo,
target_pda: &AccountInfo,
system_program: &AccountInfo,
space: usize,
rent_lamports: u64,
owner: &Pubkey,
pda_seeds: &[Seed],
) -> ProgramResult {
let current_balance = target_pda.lamports();

// Step 1: Transfer lamports if needed to reach rent-exemption
if current_balance < rent_lamports {
let transfer_amount = rent_lamports
.checked_sub(current_balance)
.ok_or(ProgramError::ArithmeticOverflow)?;

// System Program Transfer instruction (discriminator: 2)
let mut transfer_data = Vec::with_capacity(12);
transfer_data.extend_from_slice(&2u32.to_le_bytes());
transfer_data.extend_from_slice(&transfer_amount.to_le_bytes());

let transfer_accounts = [
AccountMeta {
pubkey: payer.key(),
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: target_pda.key(),
is_signer: false,
is_writable: true,
},
];

let transfer_ix = Instruction {
program_id: system_program.key(),
accounts: &transfer_accounts,
data: &transfer_data,
};

pinocchio::program::invoke(&transfer_ix, &[&payer, &target_pda, &system_program])?;
}

// Step 2: Allocate space
// System Program Allocate instruction (discriminator: 8)
let mut allocate_data = Vec::with_capacity(12);
allocate_data.extend_from_slice(&8u32.to_le_bytes());
allocate_data.extend_from_slice(&(space as u64).to_le_bytes());

let allocate_accounts = [AccountMeta {
pubkey: target_pda.key(),
is_signer: true,
is_writable: true,
}];

let allocate_ix = Instruction {
program_id: system_program.key(),
accounts: &allocate_accounts,
data: &allocate_data,
};

let signer: Signer = pda_seeds.into();
invoke_signed(&allocate_ix, &[&target_pda, &system_program], &[signer])?;

// Step 3: Assign ownership to target program
// System Program Assign instruction (discriminator: 1)
let mut assign_data = Vec::with_capacity(36);
assign_data.extend_from_slice(&1u32.to_le_bytes());
assign_data.extend_from_slice(owner.as_ref());

let assign_accounts = [AccountMeta {
pubkey: target_pda.key(),
is_signer: true,
is_writable: true,
}];

let assign_ix = Instruction {
program_id: system_program.key(),
accounts: &assign_accounts,
data: &assign_data,
};

let signer: Signer = pda_seeds.into();
invoke_signed(&assign_ix, &[&target_pda, &system_program], &[signer])?;

Ok(())
}
Loading