The promise of easy money can make people do crazy things. Members of the blockchain community aren’t immune to this problem. And as the recent EOS RAM exploit shows, attackers know about this weakness of human nature and know exactly how to use it to their benefit.
In this article, Apriorit blockchain experts talk about EOS and the potential EOS.IO RAM exploit hack vulnerability. We look closer at the EOS RAM hijack and how to mitigate it, discuss possible ways to prevent such attacks, and try to recreate – for research purposes – a malicious smart contract that’s able to allocate RAM from someone else’s account. We then provide a practical solution to the problem.
To begin, let’s take a close look at the structure of the EOS network, which is pretty unique in its design. In contrast to other blockchains, the EOS network is structured similarly to the operating system of a computer. It has system resources like Random Access Memory (RAM), CPU time, and network bandwidth, and users can reserve these resources by staking native SYS tokens.
In the EOS network, every piece of data stored in the blockchain takes up some RAM: account balances, smart contract code, data, etc. And just like in a regular operating system, EOS RAM is a limited resource. The network development team plans to implement unlimited RAM capacity in future, but for now, the scarcity of this resource causes a lot of market speculation.
In fact, RAM was being hoarded soon after the network’s launch because some users reserved more RAM than they actually needed. As a result, most of the network’s RAM is reserved, even though none of the active DApps has more than 500 users.
With some accounts having lots of free RAM that isn’t occupied with any data, it was only a matter of time before attackers turned their attention to this valuable resource. And in August 2018, an EOS.IO RAM exploit, locking free RAM resources of EOS users, was discovered. Let’s see how exactly this exploit works.
In the EOS network, if RAM is occupied by any data it can’t be sold back to the network and the SYS tokens that were staked for it can’t be recovered. The new EOS vulnerability allows malicious users to occupy large amounts of RAM from the accounts of other network users. The fact that many of these victim accounts actively trade the resource makes the discovered vulnerability even more dangerous.
This is how the exploit works: hackers create a malicious contract that uses certain smart contract mechanics to fill a victim’s RAM with garbage data. As a result, the hacker can lock and steal the RAM, blocking the victim from using the resource as well as selling it back to the network.
According to Dan Larimer, the CTO of Block.one and the chief architect of the EOS cryptocurrency, the attack is “similar to vandalism” because it abuses two completely legitimate and widely used smart contract features. When used properly, these exploited features present more flexibility and power to smart contracts. However, the attackers found weak spots in the mechanisms of these features and turned them into an efficient attack tool.
Let’s look closer at each of these two vulnerable features:
The first feature allows smart contracts to automatically react to notifications, such as arbitrary token transfers, without an explicit call to the contract. This functionality is similar to the fallback function in Ethereum. But in contrast to the Ethereum network, where the fallback function is called only for ETH transfers, EOS has a much more universal implementation of this function. The Intent of Code in such notifications is to provide a reasonable reaction to an external event, such as logging data or distributing tokens.
The second feature abused by the attackers is the ability of a smart contract to claim RAM in the name of another user (i.e. the user pays for RAM, not the contract). This may sound dangerous, but there’s an important detail: a smart contract may claim RAM from another user’s account only if it has permission. This means that a smart contract can’t randomly allocate RAM from any account on the network.
An invocation of the contract has to be signed by the user, explicitly giving the contract permission to access their account. The problem is that when responding to a notification, a user signs the transaction that eventually allows the exploit to take place.
To get a better understanding of the exploit, let’s look at a potential attack scenario (see Figure 1).
The attack consists of only seven steps:
- Bob has a malicious smart contract deployed on his account.
- Bob asks Alice to transfer some SYS tokens to his account.
- Alice agrees and has access to the eosio.token smart contract to transfer tokens.
- Alice signs the transaction, giving the eosio.token contract access to her token balance.
- Bob’s smart contract receives the notification about the token transfer. The smart contract is executed with the privileges of the original transaction, meaning that the contract is executed with Alice’s signature.
- Bob’s smart contract can claim an arbitrary amount of RAM from Alice’s account.
- Alice’s RAM becomes locked forever.
Now let’s see if we can recreate this EOS RAM vulnerability in practice.
To understand the mechanism of the attack and find an efficient way to mitigate it, we first need to recreate a malicious smart contract that’s able to lock unoccupied RAM in other accounts. To do so, we need to specify a filter within the action handler of the contract. Normally, you can do this with the help of the EOSIO_ABI macro. But in order to receive notifications, we have to implement the handler ourselves.
Note that if you want to handle specific types of notifications, your smart contract has to implement the same functions that you target when emulating the attack.
To run the attack, we have to implement the transfer method from the eosio.token contract, since we target the transfer of tokens in our example. Within the implementation of the transfer method, the malicious contract can simply write some junk data into RAM using a multi-index table.
Here is what our full malicious contract looks like:
To test the contract, we have to deploy it to an account and attempt to transfer some tokens from a victim account to ours. Here are some logs from a few transactions:
As you can see from these logs, an ordinary transfer may use some RAM in order to store the updated balance. This amount, however, is usually insignificant. The malicious contract, on the other hand, was able to allocate more than 100 KiB of RAM. And at the current prices, 100 KiB of RAM would cost around 10 SYS, or $50.
Now it’s time to see how we can mitigate this kind of attack.
At first glance, there are two ways we can solve this issue (see Figure 2).
The first option is to prevent smart contracts from allocating RAM in notifications altogether. This solution has been implemented in a patch for nodeos. However, it has two major drawbacks:
- Forbidding RAM allocation in notifications may break some older contracts that use this functionality. Basically, you would have to remove a legitimate feature from the system.
- This restriction can be removed within the node’s configuration file. So some nodes may still allow memory allocation during notification handling.
The second option is to send transactions to untrusted accounts through a proxy. Since the proxy has no available RAM, the malicious contract won’t be able to lock any resources. So the proxy protects the user from the attack. This kind of proxy has been created by some community developers, and a proxy smart contract for safe transfers has already been deployed to the EOS mainnet.
This approach, however, also has its disadvantages. The main drawback of this method is that the proxy can’t be used to interact with DApps, as smart contracts won’t be able to send a response back to the user’s account and will interact with the proxy instead.
As you can see, none of these solutions is completely flawless. There still may be some accounts with vulnerable RAM. So is there a way to free any of the claimed RAM in order to use it normally?
The good news is that freeing allocated RAM isn’t too difficult. The most challenging part of trying to recover allocated memory is figuring out what parts of memory to free. The tricky thing is that the memory has to be freed the same way it was allocated in the first place. So in order to free RAM, you need to know exactly how it was used.
This part can be complicated, because it requires either having access to the original source code for the attacker’s smart contract or reverse engineering the contract to retrieve the necessary data structures. Fortunately, in our case we have the original sources, so we can simply copy the multi-index table that was used to claim the RAM. But even if you have to reverse engineer the attacker’s smart contract, the cost of reverse engineering may turn out to be less than the price of the RAM you want to recover. So you should at least consider this option.
After recreating the structures, all that’s left to do is delete the multi-index table that was used before. In order to free the allocated RAM, you need to delete each entry in the table. Fortunately, you can do this with a simple loop.
So here’s what a full contract able to free the RAM claimed by our malicious contract looks like:
Note that in order to use this smart contract, the victim has to deploy it and invoke the clean-up function.
There’s at least one more way you can try to recover RAM allocated by attackers – by addressing the EOS community. The Intent of Code we mentioned before works pretty much as the constitution of the EOS network. And since malicious usage of the notification feature of smart contracts breaks the Intent of Code, a victim can at least try to appeal to the community. Chances are high that the network community will agree to resolve the matter in a more efficient way (for example, with a fork).
The recently discovered EOS RAM exploit can play a low-down trick on EOS users who have reserved additional memory in hopes of trading it at a higher rate. However, now you know what can be done to either prevent the attack from happening altogether or free allocated RAM in case of a successful attack.
Want to know more about security issues and vulnerabilities in EOS? Check out our blockchain blog for more information about the most recent hacks and exploits found in the popular blockchain networks. Feel free to contact us if you have any questions!