Avoiding Smart Contract “Gridlock” with Slither
A denial-of-service (DoS) vulnerability, dubbed ‘Gridlock,’ was publicly reported on July 1st in one of Edgeware’s smart contracts deployed on Ethereum. As much as $900 million worth of Ether may have been processed by this contract. Edgeware has since acknowledged and fixed the “fatal bug.”
When we heard about Gridlock, we ran Slither on the vulnerable and fixed Edgeware contracts. Its publicly available dangerous-strict-equality
detector correctly identifies the dangerous assertion in the vulnerable contract, and shows the absence of this vulnerability in the fixed contract.
This blog post details why this vulnerability is so subtle, the implementation details behind Slither’s dangerous-strict-equality
detector that identified this vulnerability, and how Slither can help prevent developers from introducing such vulnerabilities in the future.
Strict Equality and DoS Vulnerability
The Gridlock vulnerability was identified in the snippet below. Upon discovery, the bug was acknowledged by the maintainers and a second version addressing the issue was deployed.
/** * @dev Locks up the value sent to contract in a new Lock * @param term The length of the lock up * @param edgewareAddr The bytes representation of the target edgeware key * @param isValidator Indicates if sender wishes to be a validator */ function lock(Term term, bytes calldata edgewareAddr, bool isValidator) external payable didStart didNotEnd { uint256 eth = msg.value; address owner = msg.sender; uint256 unlockTime = unlockTimeForTerm(term); // Create ETH lock contract Lock lockAddr = (new Lock).value(eth)(owner, unlockTime); // ensure lock contract has all ETH, or fail assert(address(lockAddr).balance == msg.value); // BUG emit Locked(owner, eth, lockAddr, term, edgewareAddr, isValidator, now); }
Specifically, the source of this vulnerability is the assertion which performs a strict equality check between the balance of the newly created Lock
contract and the msg.value
sent to this contract.
From the contract developer’s perspective, this assertion should hold. The new Lock
contract was just created in the previous line. Also, it was credited with an Ether value equal to the msg.value
sent as part of the current transaction.
However, this assumes that the newly created Lock
contract address will have zero Ether balance before its creation. This is incorrect. Ether can be sent to contract addresses before the contracts are instantiated at those addresses. This is possible because Ethereum address generation is based on deterministic nonces.
The DoS attack consists of pre-calculating the next Lock
contract address and sending some Wei to that address. This forces the lock()
function to fail at the assertion in all future transactions, bringing the contract to a “Gridlock.”
The fix is to replace the assertion with the one below, where the strict equality ‘==
’ is replaced by ‘>=
’, accounting for Ether already present at the address of the new Lock
contract being created.
assert(address(lockAddr).balance >= msg.value);
Avoiding strict equality to determine if an account has enough Ethers or tokens is a well-understood defensive programming technique in Solidity.
Slither’s Dangerous-Strict-Equality Detector
Slither has had a publicly available dangerous-strict-equality
detector targeting this vulnerability since version 0.5.0, released on January 14th, 2019. We classify results from this detector as Medium impact and High confidence because strict equality is nearly always misused in logic fundamental to the operation of the contract. The results of this check are worth reviewing closely!
Running Slither on the Lockdrop.sol contract immediately identifies the vulnerable assertion:
$ slither --detect incorrect-equality Lockdrop.sol INFO:Detectors: Lockdrop.lock(Lockdrop.Term,bytes,bool) (Lockdrop.sol#53-67) uses a dangerous strict equality: - assert(bool)(address(lockAddr).balance == msg.value) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities INFO:Slither:Lockdrop.sol analyzed (2 contracts), 1 result(s) found
This detector is implemented using a lightweight taint analysis, where the tainted sources are program constructs with msg.value
, now
, block.number
, block.timestamp
, and the results of ERC token balanceOf()
function calls. The taint sinks are expressions using strict equality comparisons, i.e., ‘==
‘. The analysis works on Slither’s intermediate language representation, SlithIR, and tracks the propagation of tainted values across assignments and function calls. An alert is generated when the taint sinks have a data dependency on the tainted sources.
A simple textual search might have caught this vulnerability, but syntactic regular expressions would raise a fog of false alerts or miss it entirely. This is because of the many ways this vulnerability pattern can manifest, including across function calls and variable assignments. Hardcoding such a regular expression is challenging. Other security tools lack a detector for this vulnerability, or produce a substantial number of false positives. The lightweight semantic taint analysis enabled by SlithIR greatly improves this detector’s accuracy and reduces false positives.
In the case of the Lockdrop contract, Slither’s dangerous-strict-equality detector generates such an alert because msg.value
and an address balance are used in a strict equality comparison within an assertion. This is a textbook example of a strict equality vulnerability which is caught effortlessly by Slither. We also verified that this alert is not present in the recently fixed code.
Besides this detector, Slither has 35 more that catch many Solidity smart contract vulnerabilities. They work together with 30 additional proprietary detectors in crytic.io, our continuous assurance system (think “Travis-CI but for Ethereum”). So, go ahead and give Slither a shot. We would love to hear about your experience, and welcome feedback.