Preventing Unexpected RAM Consumption
Some users and EOSIO-based smart contracts have unintentionally had their RAM resources consumed by malicious 3rd parties who have created specialized smart contracts. This occurs due to a misunderstanding of a feature designed to enable contracts to notify other contracts of events, such as an incoming transfer. The malicious contracts use this notification feature to fill other people’s RAM with random data without informed consent and then provide no way to free this data.
- No tokens were stolen
- Properly implemented contracts are not vulnerable
- Governance processes can fix code behaving contrary to the intent of parties
- Block Producer-only mitigation options are available to protect
- Long term upgrade can make default behavior safer
No Tokens were Lost
The malicious contract exploited a misunderstanding of smart contract developers and users regarding best practices in developing smart contracts. This attack is similar to vandalism rather than theft and should do no long term damage to the parties involved once the EOS governance process can review and remedy the situation.
Best Practices to Prevent Abuse
Each user is expected to review the contracts they interact with or trust a 3rd party to review the contracts on their behalf. This means that reviewers should be careful interacting with smart contracts that use the notification feature. One example is sending tokens to smart contracts they do not trust with their CPU and RAM resources.
Developers who programmatically send tokens to accounts specified by untrusted 3rd parties should relay the transfer through an account without free RAM resources. This applies both to centralized exchanges processing user withdraws and decentralized exchanges that do so from a smart contract. There are several trusted relay contracts available.
Many wallet providers have already taken actions to warn users when a transaction may consume RAM.
Freeing Consumed RAM via Governance Processes
I believe that the intent of the code, as understood by the users and developers, should be enforced. If a malicious contract is clearly taking advantage of a mismatch between the intent of the users and the actual effect of the code, then block producers have the ability to blacklist the malicious contract while a dispute is arbitrated between the author of the malicious contract and those who interacted with it.
If arbitration finds that the behavior of the code is contrary to the intent of the parties who interacted with the code, then the elected producers are free to update the code in such a way that the outcome matches the original intent of the parties as closely as possible. In this case, the code would be updated to free the unexpectedly consumed RAM and not consume RAM in the future.
Why is this a feature of EOSIO?
There are many valid use cases for the feature that has been abused to consume RAM. The most basic use case is an game contract that wants to accept deposits of user funds. The exchange would implement code to handle a transfer notice from the token contract and then credit the sender with a balance on the exchange. In this case, the exchange and the user making a deposit could reasonably intend to authorize the exchange contract to consume the user’s RAM to store their balance. An exchange wouldn’t necessarily want to store the user’s balance in their own RAM because that could enable a different attack where many accounts send dust balances to the exchange.
EOSIO has a related feature, inline actions, that allow a contract to call code of another contract as part of the current transaction. Unlike the action notification feature, inline actions are limited to allocating the resources of the contract that generated the inline action. Action notifications forward on the resource allocation permission of the original action.
Preventing Unexpected behavior Going Forward
While there are many legitimate uses for the existing design, we now believe that the default behavior of the code operates contrary to the intuition of users and developers. The steps that must be taken to prevent abuse make the normal use case more complicated in order to simplify less common use cases.
Going forward we propose that notification handlers only have authority to consume the RAM of the contract receiving the notification. This would make it safe to send tokens to any contract without having to worry about it consuming your RAM in unexpected ways. Exchanges would be able to process withdraws safely without having to vet the contracts deployed to the users account before processing a withdraw request.
Under this proposed change, existing contracts will have to get direct authorization to consume some of user’s available RAM. An exchange contract would ask a user to “open account to reserve space for storing their balance” before accepting deposits from the user. An incoming deposit notification would then simply increment pre-allocated RAM rather than allocating new RAM.
Proposed Upgrade Path
We are preparing a producer-only upgrade that would change the default behavior to prevent the receiver of an action notification (e.g. a token transfer notification) from unexpectedly consuming the sender’s RAM. If all block producers adopt this upgrade, then the abuse mitigation strategies will be unnecessary. Unfortunately, this mitigation may break valid contracts until they can be updated to not rely on RAM allocation on user accounts during action notification handlers.
Fortunately, the process of upgrading contracts to adopt the new best practice should be relatively simple.
The producer-only upgrade be adopted by all nodes as part of the next referendum.
EOSIO is behaving as designed and is secure when used properly. We believe we can make using EOSIO properly easier for the majority of use cases and are working with the EOS community to develop the most robust overall solutions possible.