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
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.
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
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
);
}
// 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");\
}
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
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.
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
Proof of Concept (PoC) File
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);