Skip to content

[L-2] Public input concatenation enables memory DoS #239

@this-vishalsingh

Description

@this-vishalsingh

Context: folding-schemes/src/folding/nova/decider.rs

Description:

verify() takes z_0 and z_i as owned Vecs and then builds c1_public_input/c2_public_input via .concat(), which allocates and copies all elements into new vectors.
If verify() is reachable with attacker-controlled z_0/z_i sizes, this can cause large allocations and excessive copying (memory/CPU DoS) even when the proof is invalid.

Exploit path: a verifier service that accepts user-supplied z_0/z_i (or deserializes them from user input) can be forced to allocate/copy very large buffers by sending oversized arrays.

Impacted code

let c1_public_input = [
    &[vp.pp_hash, i][..],
    &z_0,
    &z_i,
    &U_final_commitments.inputize_nonnative(),
    &cf_U.inputize_nonnative(),
    &proof.cs1_challenges,
    &proof.cs1_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
    &proof.cmT.inputize_nonnative(),
]
.concat();

let c2_public_input: Vec<C2::ScalarField> = [
    &[pp_hash_Fq][..],
    &cf_U.inputize(),
    &proof.cs2_challenges,
    &proof.cs2_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
]
.concat();

Recommendation

Avoid .concat() over attacker-sized vectors.
Instead, pre-check expected public input length from the SNARK verifying keys (e.g., Groth16 vk.gamma_abc_g1.len() - 1) and reject mismatched sizes early, then build the public input with Vec::with_capacity(expected) and extend_from_slice to avoid repeated allocations/copies.

Consider taking z_0/z_i as slices (&[C1::ScalarField]) rather than owned Vecs.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions