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

Blacklisting a collection on the Starknet-side Bridge fails if the collection's position in the whitelisted list is >= 3

Summary

The bridge _white_list_collection funcion fails when trying to set a collection's whitelist status to false (blacklist) if the collection is already in the enabled (whitelisted) collections list and is not the first or second on the list.

Vulnerability Details

The Starknet bridge _white_list_collection has the following bug: in case a collection's whitelisting status is being changed from enabled=true to enabled=false, and the collection is not at the head of the whitelisted collection list, the following code is executed:

// removed element from linked list
loop {
let (active, next) = self.white_listed_list.read(prev);
if next.is_zero() {
// end of list
break;
}
if !active {
break;
}
if next == collection {
let (_, target) = self.white_listed_list.read(collection);
self.white_listed_list.write(prev, (active, target));
break;
}
};

The loop is intended to find the changed collection in the list, and remove it from the list (by linking the element before it to the element after it). However, the loop never progresses the current element. Note that the loop starts with prev (the current head of the list) and then retrieves the next elememt into next. At some point in the loop, prev should be set to next (prev = next;) for the loop to progress, but this never happen. Therefore the code will only work if the exit conditions (next.is_zero() or !active) are met on the first run of the loop (when the collection is the second on the list). If the collection is positioned third or more, the function enters an endless loop and eventually fails.

POC

Add the following test function to bridge_t.cairo and run it:

#[test]
fn whitelist_endless_loop_test() {
let erc721b_contract_class = declare("erc721_bridgeable");
let BRIDGE_ADMIN = starknet::contract_address_const::<'starklane'>();
let BRIDGE_L1 = EthAddress { address: 'starklane_l1' };
let bridge_address = deploy_starklane(BRIDGE_ADMIN, BRIDGE_L1, erc721b_contract_class.class_hash);
let bridge = IStarklaneDispatcher { contract_address: bridge_address };
start_prank(CheatTarget::One(bridge_address), BRIDGE_ADMIN);
bridge.enable_white_list(true);
stop_prank(CheatTarget::One(bridge_address));
let collection1 = starknet::contract_address_const::<'collection1'>();
let collection2 = starknet::contract_address_const::<'collection2'>();
let collection3 = starknet::contract_address_const::<'collection3'>();
start_prank(CheatTarget::One(bridge_address), BRIDGE_ADMIN);
bridge.white_list_collection(collection1, true);
bridge.white_list_collection(collection2, true);
bridge.white_list_collection(collection3, true);
bridge.white_list_collection(collection2, false);
stop_prank(CheatTarget::One(bridge_address));
}

The function fails because of the endless loop

Impact

Inability to take urgent preventive action when rogue/disfuctional collections are detected on the Starknet bridge (Examples: whitelisted collections that upgrade to malicious code, or to code that breaks bridge functionality causing sent tokens to be permanently locked). Can result in permanent loss of tokens or any other loss the rogue collection may cause.

Tools Used

Manual Review, snForge

Recommended Mitigation

Fix the code mentioned above by adding prev = next; at the appropriate place in the loop.

Updates

Lead Judging Commences

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

finding-L2-unwhitelist-from-third collection-impossible

Likelyhood: High, owner can only unwhitelist the 2 first collections. Impact: Medium/High, owner has to empty the list to remove any collection, and replace all the new ones.

Support

FAQs

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