Welcome to our next episode. During recent weeks we have spent a lot of time analysing IOST. Unlike the previous projects we have analysed so far, this one is not based on the code of Bitcoin. Therefore there was much more to analyse than before.
On one hand, this effort was very fruitful as we found several critical bugs in IOST. On the other hand, even after a full month of trying, we failed to find a responsible person from IOST with whom we could discuss these findings. We tried many times with different people from developers to management and they were either too busy to be bothered, or just refused to reply at all, or we were told to wait only for nothing to happen after. Therefore we conclude that IOST is not interested in discussion of security of their product and we have to move on.
However, this also means that we have 4 reports of serious bugs in IOST ready and many other bugs suspected but unverified due to lack of incentive to do so. One of the reports we are publishing today. Feel free to contact us in case you are interested in the other reports, some of which we may release later on our blog as usual.
Today's vulnerability has not been fixed yet, hence it does not need additional proof of knowledge.
Using DateTimeFormat To Selectively Crash Miners
Bug type: DoS
Bug severity: 7/10
Scenario 1
Attacker cost: very low
In this scenario the attacker's goal is to halt the network. The attacker crashes all miners in the system, after which the network becomes dysfunctional. In order to do that, the attacker creates a special transaction and propagates it to the network. When a miner attempts to create a new block with the special transaction included, it crashes. The attacker can repeat this process until all registered miners are eliminated.
Scenario 2
Attacker cost: high
In this scenario, the attacker buys a nontrivial amount of coins and she also performs a sybil attack against the network in terms of creating multiple mining identities. In order to do this, the attacker also needs to apply to the IOST central authority and be approved by its administrator. With a reasonable effort this seems doable for a committed attacker provided long enough time frame. Then she will perform Scenario 1 attack against all (or almost all) other miners in order to crash them. This will eventually cause all the block production in the network to rely solely on blocks from the attacker's miners. This allows the attacker to censor any transaction as well as censor any block of remaining miners. However, due to various factors, this scenario is only theoretical. The attacker needs to achieve over 66% of dominance in block production in IOST. But in order to completely remove other miners from the system, these miners have to be inactive for long enough time. We do expect that the central administrator of IOST would be alerted if such an attack was executed and the administrator could simply remove all attacker's miners from the system as well as add new miners to compensate on removed ones. This can be done with a few contract calls by the administrator, just as in case of approval of new miners. Therefore we believe that in practice, IOST is well protected against this scenario by its centralisation.
Description
The codebase state at the time of writing can be seen here.
IOST implements smart contract functionality leveraging V8 JavaScript engine from Google. At the time of our analysis, V8 in IOST presented itself to be V8 version 7.0.0. This version was released on 15th October 2018. This allows users of IOST to deploy smart contracts written in JavaScript. However, V8 by itself is not suitable for consensus critical operation, for example because it does not guarantee determinism of execution.
This is why IOST implemented a lot of restrictions on the top of V8. We can see some of these restrictions inside of environment.js file, which is one of the scripts that are used to prepare the execution environment in a way it is safe to run an arbitrary supported smart contract. As a typical restriction implemented by IOST we can take the elimination of the Date object. Obviously, if a contract called Date.now(), each validating node would see a different value. Thus before the contract is executed, environment.js sets all "dangerous" objects to null in order to make them unavailable for the contract code.
However, IOST developers failed to include the Intl object among those that are nulled and in combination with the specific V8 engine version used in IOST, this presents a critical vulnerability in IOST consensus.
Malicious Contact Code
Let's consider the following contract code:
class Contract {
init() {
}
doom() {
return new Intl.DateTimeFormat('en-US');
}
}
module.exports = Contract;
This contact is perfectly valid according to the rules of IOST and Intl object is available because it was not nulled. When this contract is deployed, a malicious attacker can create a transaction calling doom(). Such a transaction is propagated through the network and reaches a miner which will try to add it to a block, which means the contract has to be loaded and the call to doom() executed. At that moment the miner crashes. This repeats with other miners in the network up until the expiration time of the transaction. The expiration time allows the attacker to specify how many miners in a row will be affected. By setting the expiration to very low number, the attacker is able to target any single specific miner of her choice. It is possible because the order of next miners is known upfront in IOST. By specifying maximum expiration time of 90 seconds it is possible to eliminate up to about 30 miners with just a single transaction. Obviously, the attacker can then create a new transaction to kill the next batch of miners until all are eliminated, if she wishes so.
Why Does It Crash?
In order to understand why the code above crashes the node, we need to look at the source code of V8 engine version 7.0.0. We can see that in case V8 engine is not set up correctly to support DateTimeFormat functionality, FATAL is invoked, which leads to an immediate termination of the host process. And because V8 engine runs inside the node's process, the node is terminated. IOST is using V8 in a way this functionality is not supported and therefore the code above causes the node to crash.