Friday, March 29, 2024

187: msg.value

 In Soldity, msg.value is a global variable that represents the amount of ether sent along with a function call. The msg.value variable is commonly used when handling payments in smart contracts and to verify if the user has sent enough money along with the contract. This works as expected in simple contracts, but as code grows in complexity various security considerations emerge. The msg.value variable persists within its scope, so any kind of looping logic will read the same total value regardless of how the ether sent has been used. Attackers can leverage this in order to apply the same ether payment to multiple functions, essentially double counting their payment. 

A potentially catastrophic exploit of this nature was caught and fixed in SushiSwap before any funds were stolen. In 2021 SushiSwap had implemented a bidding contract for auctions that utilized the BoringBatchable library to submit batches of delegateCalls to other contracts within the system. The critical flaw overlooked by the SushiSwap team is that msg.value persists across delegateCalls. This allowed an attacker to submit a batch of bids with one ether payment, submitting bids for free and exploiting the action. The exploit became even more serious when combined with the contract's refund function. By counting the same msg.value payment multiple times, the attacker could manipulate the contract state to make it seem like they had deposited a large fortune of money, and then request it back as a refund, stealing all the ether that had been legitimately deposited by other users. 

Fortunately, the exploit was found and remedied before any attack can be made. This episode in 2021 illustrates how even a blue chip exchange like SushiSwap can push code with vulnerabilities, and the risks associated with handling global variables within complex logic where their scope can become obfuscated.

 To read about the experience of the original auditor in finding this exploit, click here

Wednesday, March 27, 2024

186: DOS in Web3

 Denial of Service (DOS) attacks render web3 protocols unusable and unable to provide value to their users. Many of these attacks derive from the gas requirement users pay to interact with a blockchain. In certain scenarios an attacker can burden a protocol to the point where the code needs to do a tremendous account of calculations and the gas cost becomes prohibitive to honest users. Often these vulnerabilities come from unbounded loops in Solidity code, where there is no upper limit to the amount of times the loop must iterate and repeat. For example, the Bridges exchange dividend system was vulnerable to a Denial of Service attack because an attacker could create an arbitrary amount of users to extend a loop indefinitely long. The protocol had no minimum amount required to mint a pair token, so the attacker could mint a single wei of pair tokens and bog the users list down for a negligible price. The Bridges fixed this by changing their dividend logic: each unit of owned supply was entitled to a certain amount of dividend that the user could withdraw at one time based on the amount of pair tokens they held. This way, the withdrawal is computed at the time of the transaction and no loop is required. 

Another example of of a Denial of Service attack was found in an audit of the GMX code. In GMX, a leveraged position will be liquidated if the collateral value goes too low, and any excess collateral is sent back to the address. In this case, an attacker could create a contract without either a receive() or fallback() function that is unable to receive ether, and use the contract to create their leveraged position. In the event of liquidation, GMX would attempt to send the leftover ETH collateral the address, but the transfer will fail and cause an unintended revert. The attacker has now created a position that is unable to be liquidated, exploiting the system and threatening the solvency of the protocol. GMX fixed this by sending wrapped ERC20 ether as a backup in case the native transfer failed, solving the denial of service exploit on their liquidation function. 


When auditing code we can ask the following questions to identify DOS vulnerability:

1) Is a loop iterating a boundless number of iterations? Can a user add iterations to the loop? How much does it cost the user to add more iterations to the loop? 

2) Look for any external calls such as sending Ether to a contract. Is there a way for that call to fail? Is it possible for the external call to run out of gas? If it does, will it cause the top level transaction to fail? And how will that effect the overall system? 

Saturday, March 23, 2024

185: Encoded Function Calls

 Normally functions are called in a smart contract at a high level within a Solidity file, using the functions name and arguments. Solidity also supports the low level .call function which allows the direct calling of an encoded function. This is a powerful feature that allows us to interact with dynamically loaded contracts and to interface with contracts whose source is unavailable. The function signature and arguments are converted into a byte array using the ABI, and are passed as function calldata to the contract address, where it is then decoded and executed. Encoded function calls enable upgradeable smart contracts despite the immutable nature of deployed code on a blockchain. We can create a proxy contract who extracts call data and sends them to the implementation contract. Then at a later time, a new upgraded implementation contract can be deployed and the forwarding address updated in the proxy contract. The proxy contract remains unchanged, and the upgrade process appears seamless to external users. Overall encoded function calls allow developers to separate concerns of logic and data, and create resilient and flexible decentralized applications. 

190: Sablier

 The CodeHawks platform has an upcoming audit on the Sablier protocol, so I decided to read through the docs and familiarize myself with the...