name: unexpected-ecrecover-null-address
description: '- Contract uses ecrecover directly (not via OpenZeppelin''s ECDSA library)'
pattern_category: signature
detection_rules:
- regex: 'ecrecover([^\n]*)' severity: Medium confidence: Medium swc: SWC-117 description: Raw ecrecover call that requires explicit address(0) handling
Unexpected ecrecover Null Address
Preconditions
- Contract uses
ecrecoverdirectly (not via OpenZeppelin's ECDSA library) - The recovered address is not checked against
address(0) - The expected signer variable could be uninitialized (defaults to
address(0))
Vulnerable Pattern
contract Vault {
address public signer; // Uninitialized — defaults to address(0)
function withdrawWithSig(uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount));
// ecrecover returns address(0) for invalid signatures
// (e.g., v != 27 && v != 28)
address recovered = ecrecover(hash, v, r, s);
// If signer is uninitialized (address(0)) and recovered is address(0),
// this check passes — anyone can withdraw
require(recovered == signer, "invalid signature");
_withdraw(msg.sender, amount);
}
}
Detection Heuristics
- Search for
ecrecover(calls in the codebase - Check if the returned address is validated against
address(0)— flag if not - Check the variable that the recovered address is compared against — can it ever be
address(0)? (uninitialized, never set, cleared by admin) - Check if OpenZeppelin's
ECDSA.recoveris used instead — it reverts on null recovery automatically - In upgradeable contracts, check if the signer is set during
initialize()— ifinitializeis never called, signer remainsaddress(0)
False Positives
require(recovered != address(0))check is present afterecrecover- OpenZeppelin's
ECDSA.recoveris used (handles null address internally) - The expected signer is set in the constructor or initializer and can never be
address(0)(validated on set)
Remediation
- Always check
require(recovered != address(0), "invalid signature")afterecrecover - Use OpenZeppelin's
ECDSA.recoverwhich reverts on invalid signatures and null recovery - Validate that signer variables cannot be
address(0)at any point
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
function withdrawWithSig(uint256 amount, bytes memory sig) external {
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount));
bytes32 ethHash = ECDSA.toEthSignedMessageHash(hash);
address recovered = ECDSA.recover(ethHash, sig); // Reverts if address(0)
require(recovered == signer, "invalid signature");
_withdraw(msg.sender, amount);
}