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

Denial of Service Attack in `GlobalConfiguration::removeCollateralFromLiquidationPriority` function

Summary

The GlobalConfiguration::removeCollateralFromLiquidationPriority function removes any collateral of choice from the collateralLiquidationPriority array. In order to achieve this, the function loops through the array, copies each element to a different array neglecting the element to be removed. While copying the elements, all elements are removed from the collateralLiquidationPriority array. After all elements have been removed from the collateralLiquidationPriority array in storage, the other elements earlier copied to a different array are then added to the collateralLiquidationPriority array in storage one after the other in order to maintain the initial order of the elements instead of changing the order as is the case in openzeppelin's EnumerableSets. This creates a potential denial of service (DoS), continuously incrementing gas costs as the array gets larger.

Vulnerability Details

The GlobalConfiguration::removeCollateralFromLiquidationPriority function loops through the GlobalConfiguration::collateralLiquidationPriority array, copying each collateralType in the array one after the other into a new array copyCollateralLiquidationPriority except the collateralType to be removed. Hereafter, each collateralType in the copyCollateralLiquidationPriority is added into the collateralLiquidationPriority array in storage. However, the longer the GlobalConfiguration::removeCollateralFromLiquidationPriority array is, the more collateralTypes involved in the looping. This means the gas costs for removing a collateralType when the GlobalConfiguration::collateralLiquidationPriority is small in size is way less than when the GlobalConfiguration::collateralLiquidationPriority is larger in size.

// cache length
uint256 copyCollateralLiquidationPriorityLength = copyCollateralLiquidationPriority.length;
// create a new array to store the new order
address[] memory newCollateralLiquidationPriority = new address[](copyCollateralLiquidationPriorityLength - 1);
uint256 indexCollateral;
// iterate over the in-memory copies
for (uint256 i; i < copyCollateralLiquidationPriorityLength; i++) {
// fetch current collateral
address collateral = copyCollateralLiquidationPriority[i];
// remove current collateral from storage set
self.collateralLiquidationPriority.remove(collateral);
// if the current collateral is the one we want to remove, skip
// to the next loop iteration
if (collateral == collateralType) {
continue;
}
// otherwise add current collateral to the new in-memory
// order we are building
newCollateralLiquidationPriority[indexCollateral] = collateral;
indexCollateral++;
}
// the collateral priority in storage has been emptied and the new
// order has been built in memory, so iterate through the new order
// and add it to storage
for (uint256 i; i < copyCollateralLiquidationPriorityLength - 1; i++) {
self.collateralLiquidationPriority.add(newCollateralLiquidationPriority[i]);
}

Impact

The gas costs for removing a collateralType will greatly increase as more collateralTypes are added into the collateralLiquidationPriority array, making it difficult to remove collaterals from the collateralLiquidationPriority array in the long run.

Tools Used

Manual Review

Foundry

Proof of Concept: Consider having two arrays of collateralTypes, one of size 2 and the second of size 50. We first add the first array to the collateralLiquidationPriority array and then remove one collateralType from the collateralLiquidationPriority. Secondly, add the second array to the collateralLiquidationPriority array and then remove one collateralType from the collateralLiquidationPriority. The gas costs will be as such:

  • 1st removing from array of size 2: ~133167 gas

  • 2nd removing from array of size (1+50): ~2217719 gas

This is about 16 times more expensive removing from the array when it is larger.

PoC Place the following code into `removeCollateralFromLiquidationPriority.t.sol`.
function test_RemoveCollateralFromLiquidationPriority_LeadsToDoS() external {
vm.txGasPrice(1);
address collateralType1 = address(123);
address collateralType2 = address(456);
address[] memory collateralTypes = new address[](2);
collateralTypes[0] = collateralType1;
collateralTypes[1] = collateralType2;
perpsEngine.exposed_configureCollateralLiquidationPriority(collateralTypes);
uint256 gasBeforeFirstRemoval = gasleft();
perpsEngine.exposed_removeCollateralFromLiquidationPriority(collateralType1);
uint256 gasAfterFirstRemoval = gasleft();
uint256 firstRemovalGas = (gasBeforeFirstRemoval - gasAfterFirstRemoval)*tx.gasprice;
// Add 50 collaterals
uint160 newLength = 50;
address[] memory otherCollateralTypes = new address[](newLength);
uint160 i;
for(i = 1; i <= newLength; i++){
address newCollateral = address(i);
otherCollateralTypes[i-1] = newCollateral;
}
perpsEngine.exposed_configureCollateralLiquidationPriority(otherCollateralTypes);
address otherCollateralTypes1 = otherCollateralTypes[1];
uint256 gasBeforeSecondRemoval = gasleft();
perpsEngine.exposed_removeCollateralFromLiquidationPriority(otherCollateralTypes1);
uint256 gasAfterSecondRemoval = gasleft();
uint256 secondRemovalGas = (gasBeforeSecondRemoval - gasAfterSecondRemoval)*tx.gasprice;
console2.log("First Removal Gas Used", firstRemovalGas);
console2.log("Second Removal Gas Used", secondRemovalGas);
assertGt(secondRemovalGas, firstRemovalGas);
}

Recommendations

  1. Consider using the remove function from openzeppelin's EnumerableSets without rearranging the array to maintain its original order. Though, the order may have changed, each collateralType can easily be accessed.

function removeCollateralFromLiquidationPriority(Data storage self, address collateralType) internal {
// does the collateral being removed exist?
bool isInCollateralLiquidationPriority = self.collateralLiquidationPriority.contains(collateralType);
// if not, revert
if (!isInCollateralLiquidationPriority) revert Errors.MarginCollateralTypeNotInPriority(collateralType);
+ self.collateralLiquidationPriority.remove(collateral);
- // the following code is required since EnumerableSet::remove
- // uses the swap-and-pop technique which corrupts the order
- // copy the existing collateral order
- address[] memory copyCollateralLiquidationPriority = self.collateralLiquidationPriority.values();
- // cache length
- uint256 copyCollateralLiquidationPriorityLength = copyCollateralLiquidationPriority.length;
- // create a new array to store the new order
- address[] memory newCollateralLiquidationPriority = new address[](copyCollateralLiquidationPriorityLength - 1);
- uint256 indexCollateral;
- // iterate over the in-memory copies
- for (uint256 i; i < copyCollateralLiquidationPriorityLength; i++) {
- // fetch current collateral
- address collateral = copyCollateralLiquidationPriority[i];
- // remove current collateral from storage set
- self.collateralLiquidationPriority.remove(collateral);
- // if the current collateral is the one we want to remove, skip
- // to the next loop iteration
- if (collateral == collateralType) {
- continue;
- }
- // otherwise add current collateral to the new in-memory
- // order we are building
- newCollateralLiquidationPriority[indexCollateral] = collateral;
- indexCollateral++;
- }
- // the collateral priority in storage has been emptied and the new
- // order has been built in memory, so iterate through the new order
- // and add it to storage
- for (uint256 i; i < copyCollateralLiquidationPriorityLength - 1; i++) {
- self.collateralLiquidationPriority.add(newCollateralLiquidationPriority[i]);
- }
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

spomaria Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!