Skip to content

[pallet-revive] Re-entrant CREATE2 bypasses the duplicate contract collision check #12639

Description

@Nathy-bajo

The only defense against deploying two contracts to one address is ContractInfo::new's is_contract(address) check at storage.rs, which reads AccountInfoOf, and a contract is deliberately not written into AccountInfoOf until its constructor frame pops (see exec.rs, "we do not store on instantiate"), so an attacker who deploys a factory D that runs CREATE2(salt=S, code=C), lets the resulting contract B1's constructor (written by the attacker) re enter D (reentrancy is on by default, allows_reentry = true at exec.rs), and has D call the same nonce independent CREATE2(salt=S, code=C) a second time will find the collision check still sees address X as empty and constructs a second contract B2 at X, which, because inc_consumers and increment_refcount run once per constructor frame (exec.rs and exec.rs) but terminate decrements only once, permanently leaks a consumer (the account can never be reaped and its ED is locked forever), permanently leaks the code refcount (the blob can never be pruned), and orphans B2's child trie with its storage deposit locked forever once B1 overwrites AccountInfoOf[X] on pop, all from a single transaction fully controlled by the attacker and in direct violation of EIP-684 (which sets the target nonce to 1 at the start of construction precisely to block this).

Fix: make the collision check nonce based (EIP-684), or track in construction addresses on the frame stack and reject a nested instantiate targeting an address already present as a Constructor frame.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions