Errors in contracts

Piotr Nazimek
Calendar icon
25 września 2017

What can be the consequences of errors in contracts? One of them is the loss of funds, as some Parity wallet users had a painful experience in July this year. Blockchain does not forget, what has occurred in it cannot be revoked. This is its undoubted advantage, which in certain situations becomes a disadvantage. The contract is governed by the rules as implemented in it. These are the only ones we can use to save our funds if there is a suspicion that they are no longer safe in the contract. Even if the rules are wrong they are still rules in the contract.

Parity hole

Parity is one of the client implementations along with the wallet contract for Ethereum. In a previous post I discussed another wallet implementation which is Ethereum Wallet.

In simple terms, the Parity wallet implementation consists of two contracts. One of them acts as the WalletLibrary, which provides an implementation of the functions used by the Wallet contract. Such a technique is used in order not to incur high costs when creating instances of contracts, if some of their code can be shared. The library is created in the blockchain only once, wallet instances will be many.

The WalletLibrary library provides, among other things, the initWallet function, which was called from the wallet constructor, and the changeOwner function, which is used to change the owner, and was called from the changeOwner method. Below I have provided a simplified implementation of this part of the Parity contract.

1contract WalletLibrary {
2    address public owner;
3
4    // funkcja wołana przy tworzeniu portfela
5    function initWallet(address _owner) {
6        owner = _owner;
7        // ...
8    }
9
10    // zmiana właściciela portfela
11    function changeOwner(address _new_owner) external {
12        // sprawdzamy czy funkcję wywołuje obecny właściciel
13        if (msg.sender == owner) {
14            owner = _new_owner;
15        }
16    }
17}
18
19contract Wallet {
20    address public _walletLibrary;
21    address public owner;
22
23    // konstruktor portfela, woła initWallet
24    function Wallet(address _owner) {
25        // ...
26        _walletLibrary.delegatecall(bytes4(sha3("initWallet(address)")), _owner);
27    }
28
29    // zmiana właściciela portfela
30    function changeOwner(address _new_owner) {
31        _walletLibrary.delegatecall(bytes4(sha3("changeOwner(address)")), _new_owner);
32    }
33
34    // funkcja awaryjna, wołana w razie braku możliwości dopasowania innej funkcji
35    function () payable {
36        _walletLibrary.delegatecall(msg.data);
37    }
38}

At this point it is necessary to explain how delegatecall works. Calling a function of another contract from the contract level using delegatecall causes code execution for the calling contract. This means that a delegatecall to the changeOwner function will actually modify the owner field of a specific Wallet contract instance and not the owner field in WalletLibrary. This is sensible behavior, after all, all wallets do not share a common owner.

The above implementation performs a delegatecall from the constructor to the initWallet function and from changeOwner to the corresponding function in the library. Note that the initWallet function changes the owner of the contract unconditionally. This seems correct, in the constructor we just create the contract, so it will be the first owner. The constructor can only be called once. The changeOwner function verifies that it is the current owner who calls it, and only then allows the new address to be set.

Inside the Ethereum Virtual Machine, calling a contract function from another contract involves calculating its signature and passing (pasting to the signature) the parameter values. The signature of a function is four bytes from the abbreviation of its name along with the types of parameters. It is calculated by the contract from the string. In the above example for initWallet(address) it will be 9da8be21. So such a signature can be calculated independently outside the contract. In the presented contract, is there any way to call the initWallet function when the contract is already created? Let's look at the implementation of the unnamed function, which is a so-called fallback function called when no other function can be matched. It performs a delegatecall by passing as a parameter the data passed in the transaction. Bingo! So, all you need to do is to bring the fallback function to a call with the signature initWallet(address) passing the new owner's address of your choice as a parameter. This worked because the Wallet contract didn't have a function implementation with the signature initWallet(address), so the call would go to the fallback function and be passed to the WalletLibrary, after which it would execute unimpeded for the calling contract.

The bug in the Parity contract was that the ability to call the initWallet function was not limited to the case of the new wallet creation process only. It was used by attackers to move Ether out of existing contracts. Losses reached tens of millions of dollars, and would have been even greater had the good hackers not reacted in time to secure funds from the erroneous contracts by using the same attack. The difference was that they later returned the diverted funds to the rightful owners of the contracts.

The fix in the Parity contract additionally included functions that unconditionally modify the owner's address. They should be internal functions, which means there is no way to call them from outside the contract. It is important to remember that in Solidity, by default, functions have public status.

The patch in the Parity portfolio that eliminated these bugs can be seen at this link. To quote some of the comments: internal worth $30 million.

Summary

The above example shows how much care must be taken when implementing contracts in Solidity and using the prepared code as their user. The nuances of the Solidity language and EVM can have dire consequences for contracts and their owners. Many tools are being developed to help analyze contract code. Various techniques, such as a daily payout limit, are also being used to minimize the impact of exploiting potential holes. Many clever attacks are yet to come. Certainly one way to avoid them is to learn by implementing various contracts and analyze them in your own Ethereum network.

Read also

Calendar icon

27 wrzesień

Omega-PSIR and the Employee Assessment System at the Warsaw School of Economics

Implementation of Omega-PSIR and the Employee Evaluation System at SGH. See how our solutions support university management and resea...

Calendar icon

12 wrzesień

Playwright vs Cypress vs Selenium: which is better?

Playwright, Selenium or Cypress? Discover the key differences and advantages of each of these web application test automation tools. ...

Calendar icon

22 sierpień

A new era of knowledge management: Omega-PSIR at Kozminski University

Kozminski University in Warsaw, one of the leading universities in Poland, has been using the Omega-PSIR system we have implemented t...