_safeMint from OZ ERC721 invokes onERC721Received on contract receivers between successive mints. The nonReentrant modifier on SnowmanAirdrop.claimSnowman only protects the airdrop's own state, not the Snowman contract or any external integration.
A malicious receiver gets execution between mint iterations and can re-enter Snowman.mintSnowman directly (because C-01 leaves it open) or read mid-state from any integrator.
Likelihood:
Reason 1: The receiver in claimSnowman can be any contract that implements onERC721Received — no allowlist.
Reason 2: With C-01 unfixed, the same callback re-enters mintSnowman to inflate the receiver's balance further within the same transaction.
Impact:
Impact 1: Amplifies C-01: a malicious receiver mints additional Snowman NFTs in the callback.
Impact 2: Read-only reentrancy exposure for any future integration that reads Snowman.getTokenCounter or Snowman.balanceOf mid-callback.
The PoC is a single contract (EvilReceiver) deployed as the airdrop's receiver. When _safeMint calls onERC721Received during the very first iteration of the mint loop, control hands back to EvilReceiver while mintSnowman is still executing. From inside the callback, the receiver re-enters mintSnowman(address(this), 100) directly — possible because C-01 leaves the function unguarded. Each nested call also fires another callback, allowing arbitrary recursion depth bounded only by gas. The vulnerability is the loss of atomicity: by the time the outer mintSnowman finishes its amount iterations, the receiver holds far more NFTs than the airdrop authorized.
There are two complementary mitigations. The first is structural: once C-01 is fixed and mintSnowman is gated to onlyAirdrop, the callback-driven re-entry path closes because EvilReceiver would need to be the airdrop contract. The second hardens the airdrop itself by enforcing strict Checks-Effects-Interactions order: every state change (including the s_hasClaimedSnowman flag from C-03) must happen before any external call that could yield control. This makes the read-only reentrancy surface harmless to any external integrator that might later read Snowman mid-mint.
If C-01 cannot be fixed for some external reason, an alternative is to replace _safeMint with _mint inside Snowman — eliminating the callback path at the cost of breaking ERC721 receiver-safety guarantees for contract holders.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.