hats-finance / Circles-0x6ca9ca24d78af44582951825bef9eadcb210e5cf

Circles Protocol contracts
https://aboutcircles.com
GNU Affero General Public License v3.0
0 stars 0 forks source link

Gas Griefing Attack in operateFlowMatrix Function #116

Open hats-bug-reporter[bot] opened 4 days ago

hats-bug-reporter[bot] commented 4 days ago

Github username: @nicholeconn1024 Twitter username: -- Submission hash (on-chain): 0xc19e2c201ed7b9e4a79102d322acb264b2a95776cc36e4b055cec8216b71af98 Severity: medium

Description: Description\ The Hub v2 contract's operateFlowMatrix function is vulnerable to a combination of a gas griefing attack and a zero-token transfer exploit, which could lead to significant disruptions in the contract's operation.

Gas Griefing Attack: The function allows processing a large number of flow vertices, streams, and coordinates. An attacker can exploit this by submitting excessive input data, causing the function to consume an inordinate amount of gas. This results in legitimate transactions failing due to running out of gas, leading to a denial of service (DoS).

Zero-Token Transfer Exploit: The operateFlowMatrix function also processes transfers where the token amounts may be zero. This allows attackers to inflate the size of transactions with unnecessary zero-value transfers, further increasing gas consumption without any meaningful outcome.

Attack Scenario

  1. Gas Griefing with Zero-Token Transfer:

Step 1: An attacker submits a transaction with a large number of flow vertices, streams, and edges, many of which involve zero-token transfers. Step 2: The function processes each transfer, including those with zero-value tokens, increasing gas consumption. Step 3: Due to the high gas cost from processing a large number of vertices and zero-token transfers, the transaction runs out of gas and fails. Result: Legitimate users attempting to process similar transactions experience the same failure, resulting in a denial of service.

  1. Potential Consequence:

Step 1: Attackers repeatedly exploit the vulnerability, submitting transactions with inflated zero-token transfers. Step 2: This disrupts normal operations by forcing transactions to fail, preventing users from successfully completing transfers, effectively blocking the use of the protocol.

Recommendation

Input Size Limits: The function now enforces strict limits on the size of input arrays to prevent gas exhaustion from overly large transactions.

Zero-Token Transfers: Any flow with a zero-token amount is immediately rejected, preventing unnecessary processing and gas consumption for meaningless transfers.

Attachments

  1. Proof of Concept (PoC) File

    uint256 M = N;
    
    address[] memory flowVertices = new address[](M);
    TypeDefinitions.FlowEdge[] memory flow = new Hub.FlowEdge[](M - 1);
    
    // allocate three coordinates per flow edge
    uint16[] memory coordinates = new uint16[]((M - 1) * 3);
    
    // the flow vertices need to be provided in ascending order
    for (uint256 i = 0; i < M; i++) {
        flowVertices[i] = sortedAddresses[i];
    }
    
    // the "flow matrix" is a rang three tensor:
    // Circles identifier, flow edge, and flow vertex (location)
    uint256 index = 0;
    
    // for each row in the flow matrix specify the coordinates and amount
    for (uint256 i = 0; i < M - 1; i++) {
        // flow is the amount of Circles to send, here constant for each edge
        flow[i].amount = uint192(0 * CRC);
        flow[i].streamSinkId = uint16(0);
        // first index indicates which Circles to use
        // for our example, we use the Circles of the sender
        coordinates[index++] = lookupMap[i];
        // the second coordinate refers to the sender
        coordinates[index++] = lookupMap[i];
        // the third coordinate specifies the receiver
        coordinates[index++] = lookupMap[i + 1];
    }
    
    // only the last flow edge is a terminal edge in this example to Charlie->David
    // and it then refers to the single stream Alice -> David of 5 (Charlie) Circles
    // start counting from 1, to reserve 0 for the non-terminal edges
    flow[2].streamSinkId = uint16(1);
    
    // we have to pack the coordinates into bytes
    bytes memory packedCoordinates = packCoordinates(coordinates);
    
    // Lastly we need to define the streams (only one from Alice to David)
    TypeDefinitions.Stream[] memory streams = new Hub.Stream[](1);
    // the source coordinate for Alice
    streams[0].sourceCoordinate = lookupMap[0];
    // the flow edges that constitute the termination of this stream
    streams[0].flowEdgeIds = new uint16[](1);
    streams[0].flowEdgeIds[0] = uint16(2);
    // and optional data to pass to the receiver David from Alice
    streams[0].data = new bytes(0);
    
    for (uint256 i = 0; i < 100000; i++) {
        // Alice needs to authorize the operator who sends the flow matrix
        // for the test she can approve herselve as an operator
        vm.prank(addresses[0]);
        mockHub.setApprovalForAll(addresses[0], true);
    
        // act as oeprator and send the flow matrix
        vm.prank(addresses[0]);
        mockHub.operateFlowMatrix(
            flowVertices,
            flow,
            streams,
            packedCoordinates
        );
    }
  2. Revised Code File (Optional)

    // New Input Limits\ require(_flowVertices.length <= 100, "Flow vertices exceed limit");\ require(_streams.length <= 50, "Streams exceed limit");\ require(_flow.length <= 150, "Flow edges exceed limit");

    // Unpacking coordinates\ uint16[] memory coordinates = _unpackCoordinates(_packedCoordinates, _flow.length);

    // Check for zero-token transfers and remove them\ for (uint256 i = 0; i < _flow.length; i++) {\ require(_flow[i].amount > 0, "Zero-token transfer not allowed");\ }

    // Netted flow calculation\ int256[] memory matrixNettedFlow = _verifyFlowMatrix(_flowVertices, _flow, coordinates);

    // Effect transfers\ _effectPathTransfers(_flowVertices, _flow, _streams, coordinates);\ int256[] memory streamsNettedFlow = _callAcceptanceChecks(_flowVertices, _flow, _streams, coordinates);

    // Matching netted flows\ _matchNettedFlows(streamsNettedFlow, matrixNettedFlow);

NicholeConn1024 commented 22 hours ago

This is gas griefing attack. Why is it invalid? Screenshot 2024-09-22 232735

benjaminbollen commented 6 hours ago

because an attacker burning his own money by spending gas is not grieving anyone but himself. that's why gas exists in Ethereum.