NFTBridge
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

No whitelist check is performed when `withdrawTokens` is called on L1.

Summary

When withdrawTokens is called and collectionL1 returns a non-zero value, it is possible that the collectionL1 has been removed from the whitelist. However, the whitelist status of collectionL1 is not checked, allowing the users to still interact with non-whitelisted collections.

Vulnerability Details

Bridge owner can add list of collections that is whitelisted, means only those collections that are allowed to bridged by users when _whiteListEnabled is set to true.

https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L284-L287
https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L340-L356

function whiteList(address collection, bool enable) external onlyOwner {
_whiteListCollection(collection, enable);
emit CollectionWhiteListUpdated(collection, enable);
}
function _whiteListCollection(address collection, bool enable) internal {
if (enable && !_whiteList[collection]) {
bool toAdd = true;
uint256 i = 0;
while(i < _collections.length) {
if (collection == _collections[i]) {
toAdd = false;
break;
}
i++;
}
if (toAdd) {
_collections.push(collection);
}
}
_whiteList[collection] = enable;
}

However, when withdrawTokens is triggered by users and and collectionL1 returns a non-zero value from _verifyRequestAddresses, it is possible that the collectionL1 is already removed from whitelist due to various reasons. However, the whitelist status of collectionL1 is not checked, allowing the users to still interact with non-whitelisted collections.

https://github.com/Cyfrin/2024-07-ark-project/blob/main/apps/blockchain/ethereum/src/Bridge.sol#L183-L210

function withdrawTokens(
uint256[] calldata request
)
external
payable
returns (address)
{
// ...
address collectionL1 = _verifyRequestAddresses(req.collectionL1, req.collectionL2);
CollectionType ctype = Protocol.collectionTypeFromHeader(header);
if (collectionL1 == address(0x0)) {
if (ctype == CollectionType.ERC721) {
collectionL1 = _deployERC721Bridgeable(
req.name,
req.symbol,
req.collectionL2,
req.hash
);
// update whitelist if needed
_whiteListCollection(collectionL1, true);
} else {
revert NotSupportedYetError();
}
}
for (uint256 i = 0; i < req.tokenIds.length; i++) {
uint256 id = req.tokenIds[i];
bool wasEscrowed = _withdrawFromEscrow(ctype, collectionL1, req.ownerL1, id);
if (!wasEscrowed) {
// TODO: perhaps, implement the same interface for ERC721 and ERC1155
// As we only want to deal with ERC1155 token with value = 1.
// Also, check what to do with URIs. If the URI storage is supported
// or not for ERC721. If supported, we may need to mint with an URI.
IERC721Bridgeable(collectionL1).mintFromBridge(req.ownerL1, id);
}
}
emit WithdrawRequestCompleted(req.hash, block.timestamp, request);
return collectionL1;
}

Impact

This means collectionL1 that is removed from whitelist, users can still interact with it even when _whiteListEnabled is set to true.

Tools Used

Manual review

Recommendations

Consider to check if collectionL1 is whitelisted when the value is non-zero.

function withdrawTokens(
uint256[] calldata request
)
external
payable
returns (address)
{
// ...
address collectionL1 = _verifyRequestAddresses(req.collectionL1, req.collectionL2);
CollectionType ctype = Protocol.collectionTypeFromHeader(header);
if (collectionL1 == address(0x0)) {
if (ctype == CollectionType.ERC721) {
collectionL1 = _deployERC721Bridgeable(
req.name,
req.symbol,
req.collectionL2,
req.hash
);
// update whitelist if needed
_whiteListCollection(collectionL1, true);
} else {
revert NotSupportedYetError();
}
- }
+ } else {
+ if (!_isWhiteListed(collectionL1)) {
+ revert NotWhiteListedError();
+ }
+ }
for (uint256 i = 0; i < req.tokenIds.length; i++) {
uint256 id = req.tokenIds[i];
bool wasEscrowed = _withdrawFromEscrow(ctype, collectionL1, req.ownerL1, id);
if (!wasEscrowed) {
// TODO: perhaps, implement the same interface for ERC721 and ERC1155
// As we only want to deal with ERC1155 token with value = 1.
// Also, check what to do with URIs. If the URI storage is supported
// or not for ERC721. If supported, we may need to mint with an URI.
IERC721Bridgeable(collectionL1).mintFromBridge(req.ownerL1, id);
}
}
emit WithdrawRequestCompleted(req.hash, block.timestamp, request);
return collectionL1;
}
Updates

Lead Judging Commences

n0kto Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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