SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
Submission Details
Impact: medium
Likelihood: low

Missing Zero-Address Validation in updateVerifier

Author Revealed upon completion

Root + Impact

Description

  • Normal Behavior: When updating critical external dependencies, such as the address of a ZK-proof Verifier contract, the input parameters must be validated to ensure they are not set to the zero address (0x0000000000000000000000000000000000000000). This prevents the main contract from attempting to execute calls on non-existent bytecode.

  • Specific Issue: The updateVerifier function accepts an IVerifier type address and assigns it directly to the verifier state variable without validating the input. If the protocol owner accidentally passes address(0) during an emergency update, the state is saved. Subsequently, when any user attempts to call the claim function, the contract will execute verifier.verify(proof, publicInputs). Because high-level Solidity calls to EOAs or the zero address revert if the target has no code, the entire claim function will consistently revert. This causes a complete Denial of Service (DoS) for the core application logic until the owner submits another transaction to fix it.

/// @notice In case of a bug, allow the owner to update the verifier address.
function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
// @> Root cause: newVerifier is assigned without checking against address(0)
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}

Risk

Likelihood:

  • This relies entirely on an administrative mistake (human error) during a contract upgrade or emergency intervention.

Impact:

  • A complete Denial of Service (DoS) for the claim function. Users will be entirely unable to claim their rewards.

  • Funds and rewards will remain temporarily locked in the contract until the administrator recognizes the mistake, unpauses, pauses again, and executes another update transaction with a valid contract address.

Proof of Concept

The following steps outline the exact execution flow that leads to the DoS:

  1. The owner pauses the contract to perform an upgrade.

  2. The owner accidentally submits address(0) as the parameter for updateVerifier.

  3. The transaction succeeds, setting the verifier state variable to 0x00...00.

  4. The owner unpauses the contract.

  5. A user submits a valid ZK proof to the claim function.

  6. The EVM attempts to call verify() on address(0). Because it is a high-level call to an address with no deployed contract code, the EVM immediately reverts the transaction.

function testUpdateVerifierToZeroAddressCausesDoS() public {
// 1. Owner pauses the contract
vm.prank(owner);
treasureHunt.pause();
// 2 & 3. Owner accidentally passes the zero address
vm.prank(owner);
treasureHunt.updateVerifier(IVerifier(address(0)));
// 4. Owner unpauses
vm.prank(owner);
treasureHunt.unpause();
// 5 & 6. Valid user attempts to claim, but the call to address(0) reverts
vm.prank(user);
vm.expectRevert(); // High-level call to non-contract address reverts
treasureHunt.claim(validProof, validTreasureHash, payable(user));
}

Recommended Mitigation

Explanation of the fix: Adding a specific if statement or require check against address(0) acts as an automated safeguard against human error. This mirrors the exact safety check that the developer originally included in the constructor, ensuring consistency across the codebase.

function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
+ if (address(newVerifier) == address(0)) revert InvalidVerifier();
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!