Summary
Attempting to disable a collection whitelist after a previous enabling/whitelisting fails as long as the latest 2 entries in the white_listed_list
mapping are both set to true (i.e whitelisted) and are both not entries for the collection whitelist that is to be de-whitelisted.
Vulnerability Details
In bridge.cairo - the function _white_list_collection()
which is responsible for whitelisting and de-whitelisting has a flaw when a previously whitelisted contract collection is to be de-whitelisted:
fn _white_list_collection(ref self: ContractState, collection: ContractAddress, enabled: bool) {
let no_value = starknet::contract_address_const::<0>();
let (current, _) = self.white_listed_list.read(collection);
if current != enabled {
let mut prev = self.white_listed_head.read();
if enabled {
self.white_listed_list.write(collection, (enabled, no_value));
if prev.is_zero() {
self.white_listed_head.write(collection);
return;
}
loop {
let (_, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
let (active, _) = self.white_listed_list.read(next);
if !active {
break;
}
prev = next;
};
self.white_listed_list.write(prev, (true, collection));
} else {
if prev == collection {
let (_, next) = self.white_listed_list.read(prev);
self.white_listed_list.write(collection, (false, no_value));
self.white_listed_head.write(next);
return;
}
loop {
let (active, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
if !active {
break;
}
if next == collection {
let (_, target) = self.white_listed_list.read(collection);
self.white_listed_list.write(prev, (active, target));
break;
}
};
self.white_listed_list.write(collection, (false, no_value));
}
}
To understand the issue, lets look at the part of the code responsible for whitelisting a contract collection:
let mut prev = self.white_listed_head.read();
if enabled {
self.white_listed_list.write(collection, (enabled, no_value));
if prev.is_zero() {
self.white_listed_head.write(collection);
return;
}
loop {
let (_, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
let (active, _) = self.white_listed_list.read(next);
if !active {
break;
}
prev = next;
};
self.white_listed_list.write(prev, (true, collection));
}
To ensure progression within the loop, the prev
variable is updated with next
, this ensures progression within the loop if the first iteration does not meet the conditions to break out of the loop. This is highly necessary to avoid an infinite loop that repeats the current iteration when it doesn't break out. To simplify, look at the following code:
int i = 0;
while i != 5 {
i = i + 1;
}
Now, take a look at the part of the code responsible for turning whitelist status from true to false:
else {
if prev == collection {
let (_, next) = self.white_listed_list.read(prev);
self.white_listed_list.write(collection, (false, no_value));
self.white_listed_head.write(next);
return;
}
loop {
let (active, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
if !active {
break;
}
if next == collection {
let (_, target) = self.white_listed_list.read(collection);
self.white_listed_list.write(prev, (active, target));
break;
}
};
self.white_listed_list.write(collection, (false, no_value));
}
As I highlighted with comments, the main loop increment does not happen, thus if this first iteration of the loop is not sufficient to break the loop - the loop gets stuck repeating the same conditions which just leads to an infinite loop evaluating with same parameters over and over.
Impact
Admin cannot de-whitelist a contract collection if the current list head is not the collection to de-whitelist.
Tools Used
Manual
Recommendations
Simply update the code with prev = next;
:
else {
if prev == collection {
let (_, next) = self.white_listed_list.read(prev);
self.white_listed_list.write(collection, (false, no_value));
self.white_listed_head.write(next);
return;
}
loop {
let (active, next) = self.white_listed_list.read(prev);
if next.is_zero() {
break;
}
if !active {
break;
}
if next == collection {
let (_, target) = self.white_listed_list.read(collection);
self.white_listed_list.write(prev, (active, target));
break;
}
prev = next;
};
self.white_listed_list.write(collection, (false, no_value));
}