Preventing Smart Contract Attacks on Ethereum — “DELEGATECALL”
Let’s write a vulnerable smart contract code, see how attacks work, and understand the preventing techniques to fix it
As a result of the context-preserving nature of DELEGATECALL, building vulnerability-free custom libraries isn’t as easy as one might think. The code in libraries themselves can be secure and vulnerability-free; however, when run within the context of another application new vulnerabilities can arise. Let’s have a look at a fairly complex example of this, using Fibonacci numbers.
Let’s consider the library in FibonacciLib.sol, which can generate the Fibonacci sequence and sequences of similar form.
This library provides a function that will generate the n-th Fibonacci number within the sequence. It allows users to alter the starting number of the sequence (start) and calculate the n-th Fibonacci-like numbers in this new sequence.
Now consider a contract that uses this library, shown in FibonacciBalance.sol.
This contract allows a participant to withdraw ether from the contract, with the amount of ether being equal to the Fibonacci number corresponding to the participant’s withdrawal order; that is, the 1st participant gets 1 ether, the 2nd also gets 1, the 3rd gets 2, the 4th gets 3, the 5th gets 5, and so on until the balance of the contract is less than the Fibonacci number being withdrawn.
You may have noticed that the state variable start has been used in both library and main calling contracts. Within the library contract, start is used to specify the beginning of the Fibonacci sequence and is set to 0, whereas it is set to 3 in the calling contract. You will even have noticed that the fallback function in the FibonacciBalance contract allows all calls to be passed to the library contract, which allows for the setStart function of the library contract to be called. Recalling that we preserve the state of the contract, it is going to seem that this function would allow you to change the state of the start variable in the local FibonnacciBalance contract. If so, this can allow one to withdraw more ether because the resulting calculatedFibNumber is dependent on the start variable (as seen in the library contract). In actual fact, the setStart function does not (and cannot) modify the start variable within the FibonacciBalance contract. The underlying vulnerability inside this contract is significantly worse than just modifying the start variable.
Now notice that in withdraw on line 21 we have executed fibonacciLibrary.delegatecall(fibSig,withdrawalCounter). This calls the setFibonacci function, which modifies storage slot, which in our current context is calculatedFibNumber. This can be of course expected (i.e., after execution, calculatedFibNumber is modified). However, recall that the start variable in the FibonacciLib contract is located in storage slot, i.e., the fibonacciLibrary address in the current contract. It means that the function fibonacci will give an unexpected result because it references start (slot), which in the current calling context is the fibonacciLibrary address (which will often be quite large, when interpreted as a uint). Thus it is likely that the withdraw function will revert because it won’t contain uint(fibonacciLibrary) amount of ether, which is what calculatedFibNumber will return.
Much worse, the FibonacciBalance contract lets users call all of the fibonacciLibrary functions via the fallback function at line 26. As we discussed, this includes the setStart function. As we discussed that this function let anyone change or set storage slot. In this, storage slot is the fibonacciLibrary address. Therefore, an attacker could create a malicious contract, convert the address to a uint (this can be done in Python easily using int(‘<address>’,16)), and then call setStart(<attack_contract_address_as_uint>). It will change fibonacciLibrary to the address of the attack contract. Then, whenever a user calls withdraw or the fallback function, the malicious contract will run which will steal the entire balance of the contract.
An example of such kind of attack contract is:
Solidity provides the library keyword for implementing library contracts. This ensures the library contract is stateless and non-self-destructible.
Real-world hack example: Parity Multisig Wallet (2nd Hack)
Notice that the Wallet contract passes all calls to the WalletLibrary contract via a delegate call. The constant _walletLibrary address in this code acts as a placeholder for the actually deployed WalletLibrary contract (which was at 0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4).
Check out also another vulnerability “Reentrancy” if haven’t
Preventing Smart Contract Attacks on Ethereum — “DELEGATECALL” was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.