Skip to content

Latest commit

 

History

History
 
 

NomadBridge

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

Nomad Bridge

Step-by-step

  1. Call process with an arbitrary message to the bridge.

Detailed Description

The root of the problem lies in the initialize method. In the bad initialization tx the _commitedRoot was sent as 0x00. This causes the confirmAt[0x00] value to be 1.

    function initialize(uint32 _remoteDomain, address _updater, bytes32 _committedRoot, uint256 _optimisticSeconds) public initializer {
        __NomadBase_initialize(_updater);
        // set storage variables
        entered = 1;
        remoteDomain = _remoteDomain;
        committedRoot = _committedRoot;
        // pre-approve the committed root.
        confirmAt[_committedRoot] = 1;
        _setOptimisticTimeout(_optimisticSeconds);
    }

So far, there are no obvious issues. The problem is apparent when you check the process and acceptableRoot methods: sending an arbitrary message results in a call to acceptableRoot(messages[_messageHash]). If the message is not in the messages map (ie: it has not been processed before), this triggers a call with to acceptableRoot(0x00).

Because the update set confirmAt[0x00] at 1, this will end up giving true for all messages! So anyone can send any message to process and get it approved by the contract.

    function process(bytes memory _message) public returns (bool _success) {
        // ensure message was meant for this domain
        bytes29 _m = _message.ref(0);
        require(_m.destination() == localDomain, "!destination");
        // ensure message has been proven
        bytes32 _messageHash = _m.keccak();
        require(acceptableRoot(messages[_messageHash]), "!proven");
        // check re-entrancy guard
        require(entered == 1, "!reentrant");
        entered = 0;
        // update message status as processed
        messages[_messageHash] = LEGACY_STATUS_PROCESSED;
        // call handle function
        IMessageRecipient(_m.recipientAddress()).handle(
            _m.origin(),
            _m.nonce(),
            _m.sender(),
            _m.body().clone()
        );
        // emit process results
        emit Process(_messageHash, true, "");
        // reset re-entrancy guard
        entered = 1;
        // return true
        return true;
    }

    function acceptableRoot(bytes32 _root) public view returns (bool) {
        // this is backwards-compatibility for messages proven/processed
        // under previous versions
        if (_root == LEGACY_STATUS_PROVEN) return true;
        if (_root == LEGACY_STATUS_PROCESSED) return false;

        uint256 _time = confirmAt[_root];
        if (_time == 0) {
            return false;
        }
        return block.timestamp >= _time;
    }

Possible mitigations

  • Make sure that initializers uphold invariants. In this case, a require(_committedRoot != 0) would have prevented the attack.

Diagrams and graphs

Class

class

Call graph

call

Sources and references