Assessment reports>Airlock>Medium findings>Allocation ID Collision Enables Beneficiary Hijacking
Category: Business Logic

Allocation ID Collision Enables Beneficiary Hijacking

Medium Impact
High Severity
Low Likelihood

Description

DistributionState is keyed only by allocation_id, not by the unique leaf content. If two merkle leaves share the same allocation_id, they share the same beneficiary. The first withdrawal for a given allocation_id permanently locks the beneficiary address, and subsequent withdrawals with different leaves but the same ID will use that locked beneficiary.

The issue:

ofield::add(
    vesting_uid,
    allocation.allocation_id,  // ← Only keyed by allocation_id
    DistributionState {
        beneficiary: allocation.original_beneficiary,  // ← First caller sets this
        withdrawn: 0,
        terminated_timestamp: 0,
    },
);

// Later access retrieves shared state:
let distribution_state = ofield::borrow_mut<u256, DistributionState>(
    &mut vesting.id,
    allocation.allocation_id,  // ← Same ID = same state, regardless of leaf
);

If Alice and Bob have different leaves but both have allocation_id = 999, whoever withdraws first becomes the beneficiary for both allocations.

Impact

Without uniqueness validation, an operational error can create a race condition where the first withdrawal locks the beneficiary and the second person loses their entire allocation. Since merkle roots cannot be removed, there's no way to fix it once discovered.

Recommendations

Bind state to the unique leaf content instead of just allocation_id:

Primary fix: Key state by leaf hash

let leaf_hash = keccak256(&serialized_allocation);
ofield::add(vesting_uid, leaf_hash, DistributionState { ... })

This makes collisions impossible - each unique leaf gets its own state by design.

Additional safeguards:

  • Build off-chain validation to check for duplicate allocation_ids before generating merkle roots

  • Document that allocation_id must be globally unique across all leaves

  • Add a view function to validate roots for duplicates before calling add_merkle_root()

Remediation

Magna provided the following response to this finding:

We have strong backend guarantees (Unique key DB constraints) that prevents any duplicate allocation-ids (across all projects and all trees)

Zellic © 2025Back to top ↑