Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

010_MEDIUM_access-control-missing-for-deadline

Description

The FundSetDeadline instruction exhibits a classic Missing PDA Validation vulnerability.
While it checks has_one = creator (ensuring the signer matches the creator field in the account data), it completely fails to check if the fund account passed in the context is the legitimate PDA derived from the canonical seeds ([b"fund", creator.key()]).

The Exploit Path:

  1. Attacker creates a fake, malicious Keypair (not a PDA).

  2. Attacker populates this account with data that mimics the Fund struct, setting creator = Legitimate_Creator_Pubkey.

  3. Attacker (or unsuspecting Creator via phishing) calls set_deadline passing this Fake Fund account.

  4. The program sees account.creator == signer and allows the modification.

    • Note: While this modifies the Fake Account, successful execution can mislead off-chain indexers or legitimate UIs that do not verify account ownership, causing "Phishing" or "Display" attacks.

Risk

  • Severity: Medium

  • Likelihood: Low

  • Impact: Medium

Impact Details:

  1. Phishing/Spoofing: Attackers can create "Shadow" campaigns that look official to the program logic, tricking UIs into displaying them as valid.

  2. State Pollution: The program interacts with garbage accounts.

Proof of Concept

A solana-program-test demonstrating the acceptance of a non-canonical account.

#![cfg(feature = "test-sbf")]
mod test_utils;
use solana_program_test::*;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
pubkey::Pubkey,
instruction::Instruction,
};
#[tokio::test]
async fn test_vulnerability_access_control_bypass() {
// 1. Setup Environment
let program_id = Pubkey::new_unique();
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
"crowdfunding_program",
program_id,
processor!(process_instruction),
)
.start()
.await;
// 2. Setup Malicious Account (Keypair, NOT PDA)
let fake_fund_keypair = Keypair::new();
let creator = Keypair::new();
// In a real test, we would first Initialize this fake account with
// `creator` field matching our `creator` keypair.
// 3. Attempt `set_deadline` on the Fake Account
let set_deadline_ix = instruction::set_deadline(
&program_id,
12345, // new deadline
&fake_fund_keypair.pubkey(), // <--- Passing a Keypair, not a PDA
&creator.pubkey(),
);
let mut transaction = Transaction::new_with_payer(
&[set_deadline_ix],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer, &creator], recent_blockhash);
let result = banks_client.process_transaction(transaction).await;
// 4. Assert Vulnerability
// If strict seed validation were in place, this would fail with "ConstraintSeeds".
assert!(
result.is_ok(),
"CRITICAL: Program allowed operation on an arbitrary account (not a canonical PDA)."
);
}

Recommended Mitigation Steps

Anchor provides declarative macros for PDA validation. Use seeds and bump constraints to enforcing that the account passed is exactly the one derived from the program's canonical path.

Application of Fix

1. Add Seeds Constraint:
seeds = [b"fund", creator.key().as_ref()]

2. Add Bump Constraint:
bump (or bump = fund.bump if stored)

Code Diff:

#[derive(Accounts)]
pub struct FundSetDeadline<'info> {
#[account(
mut,
has_one = creator,
+ // MITIGATION: Enforce PDA derivation
+ seeds = [b"fund", creator.key().as_ref()], // Adjust seeds to match your logic
+ bump
)]
pub fund: Account<'info, Fund>,
pub creator: Signer<'info>,
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!