plasma-contracts: DOS / griefing attacks on calls from processExits()

Issue Type

[ x ] bug report
[ ] feature request

Current Behavior

processExits is susceptible to attacks. The first scenario is a DOS attack that would break processing of items in the PriorityQueue. Consider the following scenario that in a reduced version mimics how PriorityQueue is used for exit game.

1.) Add items to the queue 2.) Wait a defined period of time minExitPeriod 3.) Reduce the queue by N items (in order they were added) and while doing so make a call to another untrusted contract/EOA (all contracts that are not part of the deployment)

If during removeItemsfromQueue a contract is called that drains the entire gas stipend then even only one item from the queue could fail and permanently lock the queue. The scenario below fails when processing two items but this depends on the amount of gas used after the failed call and how much gas was provided. Likely exiting to GasDos will beak the exit game queue with only one item due to more instruction being executed after a call.

pragma solidity 0.5.11;

import "../../src/framework/utils/PriorityQueue.sol";

contract PriorityQueueDOS {
    
    PriorityQueue pq;
    address callee;
    uint numberOfRuns;
    
    constructor (address _callee) public {
        pq = new PriorityQueue();
        callee = _callee;
    }
    
    function addItemstoQueue(uint _numberOfRuns) public {
        numberOfRuns = _numberOfRuns;
        for (uint i=0; i<numberOfRuns; i++){
            pq.insert(1);
        }
        assert(pq.currentSize() == numberOfRuns);
    }
    
    function removeItemsfromQueue() public{
        for (uint i=0; i<numberOfRuns; i++){
            callee.call.value(0)("");
            pq.delMin();
        }    
        
        assert(pq.currentSize() == 0);
    }
}

contract GasDos {
    function () external payable{
        while (1==1){
            
        }
    }
}

The second attack scenario is a griefing attack. The paradigm of using a queue where items are strictly processed after their ordering and where users might call processExits() for exiting UTXOs that they have not initiated, is by design susceptible to this type of attack. The problem can only be mitigated by limiting the amount of gas that is provided to a call. There will be proportional griefing attack possible where a malicious user starts a large number of exits and pays X amount in fees and “good users” are interested in getting their exists processed and they have to pay the Y amount in processing fees to get to process their exits. Malicious users can use gas siphoning attacks to balance the spent fee ratio more in their favor.

Suggested Fix

I propose to set the fees for all external calls to untrusted addresses as low as possible. We probably want to provide enough so that a fallback function can process it and emit an event.We also need to consider the call SafeERC20.

We should make the gas stipend update-able so that we can respond to changing gas prices in the future. Let’s consider a similar mechanism than we have for updateBondSize. Up for discussion.

As for the griefing attack I don’t see how we can fix that with the current design. I think a malicious user needs proportionally more gas than a ‘good user’ but I want to look more into the economics.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

In terms of the ERC20 transfers ... It seems to me this is still covered by the exit queue isolation protection. @boolafish I came to the same conclusion. I still wanted to think about it some more and also get some feedback from the auditors.

I thought about it again and I am ok if we stick to a static gas stipend. We need to warn users though and put up a big fat warning that if they exit ETH or send a bond to a smart contract then they have to ensure that they create a fallback function that leaves enough buffer and it stays way under the gas stipend that we pass along.

As for the implementation I would suggest that we set the gas stipend with the constructor and set it for all of the calls that we are using. We have then more time to investigate what that value is going to be https://github.com/omisego/plasma-contracts/issues/386.