NFTBridge
60,000 USDC
View results
Submission Details
Severity: high
Valid

DOS of `withdrawTokens` function on eth side

Summary

An attacker can exploit the lack of collection removal mechanism to indefinitely increase the _collections array size, leading to out-of-gas errors and denial of service when calling withdrawTokens when a fresh new L1 collection is created.

The root cause is an unbounded growth of the _collections array used for whitelist checks. As new collections are added over time, the gas cost of iterating through this array in the _isWhiteListed function increases linearly.
Eventually, this leads to out-of-gas errors when attempting to execute the withdrawTokens function, rendering it unusable.

Futhermore, the vulnerability is high as there's no mechanism to remove collections element from the _collections array.

Vulnerability Details

The vulnerability will be explained for the ETH side as the impact is greater.

The attack work flow :

  1. The attacker initiates multiple withdrawals from L2 to L1 with new arbitrary L2 collections when white_list_enabled is false (which is the default state).

  2. Each withdrawal creates a new collection on L1, increasing the _collections array.

As there's no mechanism to remove collection item from _collections array, the functions getWhiteListedCollections and _whiteListCollection which are called by withdrawTokens will be DOS as it iterates over the whole unlimited size array.

Here's the three functions affected by this vulnerability :

getWhiteListedCollections and _whiteListCollection :

function getWhiteListedCollections() external view returns (address[] memory) {
uint256 offset = 0;
BUG --> uint256 nbElem = _collections.length;
// solidity doesn't support dynamic length array in memory
address[] memory ret = new address[](nbElem);
BUG --> for (uint256 i = 0; i < nbElem ;++i) {
address cur = _collections[i];
if (_whiteList[cur]) {
ret[offset] = cur;
offset += 1;
}
}
// resize output array
assembly {
mstore(ret, offset)
}
return ret;
}
...
function _whiteListCollection(address collection, bool enable) internal {
if (enable && !_whiteList[collection]) {
bool toAdd = true;
uint256 i = 0;
BUG --> while(i < _collections.length) {
if (collection == _collections[i]) {
toAdd = false;
break;
}
i++;
}
if (toAdd) {
_collections.push(collection);
}
}
_whiteList[collection] = enable;
}

withdrawTokens :

function withdrawTokens(uint256[] calldata request) external payable returns (address) {
...
if (collectionL1 == address(0x0)) {
if (ctype == CollectionType.ERC721) {
collectionL1 = _deployERC721Bridgeable(
req.name,
req.symbol,
req.collectionL2,
req.hash
);
// update whitelist if needed
BUG --> _whiteListCollection(collectionL1, true);
} else {
revert NotSupportedYetError();
}
}
...
}

Impact

The withdrawTokens function becomes inoperable due to excessive gas consumption. This critically impairs a core feature of the Ark Project: the ability to dynamically create a collection on the L1 side to mint bridged NFTs from Starknet when no collection mapping exists. The gas-intensive operation of iterating through an ever-growing list of collections in the whitelist check causes transactions to fail, effectively breaking the bridge's L2 to L1 transfer capability.

Tools Used

Manual review

Recommendations

  1. Implement a gas-efficient data structure for whitelist checks:

    • Consider using a mapping for O(1) lookup instead of array iteration

    • Explore Merkle tree validation for whitelist proofs

  2. Bounded collection list:

    • Introduce a mechanism to remove unused or outdated collections from _collections

    • Implement a maximum size limit for the _collections array

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-collections-always-withelisted-on-both-chain-withdraw-impossible-collections-array-will-be-OOG

Likelyhood: High, once the whitelist option is disabled, collections will grow. Impact: High, withdraw won’t be possible because of Out-Of-Gas.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.