Rust Fund

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

Root cause: name is declared max_len(200) but is also a PDA seed, and a Solana seed is capped at 32 bytes, while fund_create validates nothing. Impact: most of the advertised name range is unusable and rent is paid for unreachable space

Root cause: name is declared max_len(200) but is also a PDA seed, and a Solana seed is capped at 32 bytes, while fund_create validates nothing. Impact: most of the advertised name range is unusable and rent is paid for unreachable space

Description

The README advertises campaigns "with custom names, descriptions, and funding goals", and the Fund account reserves room for a 200 byte name. Space is allocated and paid for on that basis.

The same name is used as a PDA seed in FundCreate, and a Solana seed cannot exceed 32 bytes. Anything longer cannot even have its address derived, so the campaign is impossible to create. fund_create also writes both strings straight into the account without checking either length.

// programs/rustfund/src/lib.rs
#[instruction(name: String)]
pub struct FundCreate<'info> {
@> #[account(init, payer = creator, space = 8 + Fund::INIT_SPACE, seeds = [name.as_bytes(), creator.key().as_ref()], bump)]
pub fund: Account<'info, Fund>,
pub struct Fund {
@> #[max_len(200)] // 200 bytes reserved and paid for
pub name: String, // but only 32 are ever usable as a seed
#[max_len(5000)]
pub description: String,
pub fn fund_create(ctx: Context<FundCreate>, name: String, description: String, goal: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
@> fund.name = name; // no length check
@> fund.description = description; // no length check

Risk

Likelihood:

  • Any creator picking a name longer than 32 bytes hits this immediately. Thirty-two bytes is short for a campaign title, so this is ordinary usage rather than an edge case.

  • The failure surfaces as Max seed length exceeded during address derivation, which gives no hint that the name is the cause.

Impact:

  • Six sevenths of the advertised name range is unusable, so the documented feature does not work as described.

  • Every campaign pays rent for 5273 bytes, of which 168 name bytes can never be filled, and there is no validation to reject oversized input early or to keep the reserved space in line with what is reachable.

Proof of Concept

There is no attacker here. The affected party is any creator whose campaign title runs past 32 bytes. Run with anchor test.

it("F1 a 32 byte name works, 33 bytes is impossible", async () => {
const ok = "a".repeat(32); // exactly at the seed limit
const pda = fundPda(ok, creator.publicKey);
await program.methods
.fundCreate(ok, "poc", GOAL)
.accounts({ fund: pda, creator: creator.publicKey, systemProgram: SystemProgram.programId })
.rpc();
const st = await program.account.fund.fetch(pda);
expect(st.name.length).to.equal(32); // accepted
let failed = false;
try {
const tooLong = "a".repeat(33); // one byte more
const bad = fundPda(tooLong, creator.publicKey);
await program.methods
.fundCreate(tooLong, "poc", GOAL)
.accounts({ fund: bad, creator: creator.publicKey, systemProgram: SystemProgram.programId })
.rpc();
} catch (e) {
failed = true; // Max seed length exceeded
}
expect(failed).to.be.true;
});

Output:

32 byte name accepted
33 byte name rejected: Max seed length exceeded
✔ F1 a 32 byte name works, 33 bytes is impossible

A second test reads the created account back: it occupies 5273 bytes and holds 37,590,960 lamports of rent, while 168 of the reserved name bytes are permanently unreachable.

To be clear about the limits of this finding, no funds are at risk and nothing can be stolen through it. A third test confirms a normally named campaign contributes and accounts correctly, so this is a functionality and cost issue rather than a security one, which is why I am filing it as Low.

Recommended Mitigation

Validate the inputs in fund_create and align the declared length with what a seed allows:

require!(name.as_bytes().len() <= 32, ErrorCode::NameTooLong);
require!(description.len() <= 5000, ErrorCode::DescriptionTooLong);

and change the account definition to #[max_len(32)] for name, which also reduces the rent every campaign pays. If longer titles are wanted, derive the PDA from a hash of the name, or from a counter, and keep the readable title purely as stored data.

Updates

Lead Judging Commences

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