Open c4-submissions opened 11 months ago
Have to mark as LQ because the process for prover submissions is different.
bytes032 marked the issue as insufficient quality report
We will look into this, but disagree with it being a vulnerability as it doesn't show an impact nor a feasible path in-scope
GalloDaSballo (sponsor) disputed
This looks like an extreme edge case.
Following is a foundry test to verify that reinsertion works as expected for size=2
function testSortedCdpsReinsertSize2() public {
uint256 coll = borrowerOperations.MIN_NET_STETH_BALANCE() +
borrowerOperations.LIQUIDATOR_REWARD() +
16;
address user = _utils.getNextUserAddress();
vm.startPrank(user);
vm.deal(user, type(uint96).max);
collateral.approve(address(borrowerOperations), type(uint256).max);
collateral.deposit{value: coll * 2}();
bytes32 _id1 = borrowerOperations.openCdp(1000, HINT, HINT, coll);
bytes32 _id2 = borrowerOperations.openCdp(2000, HINT, HINT, coll);
assertTrue(sortedCdps.getSize() == 2);
assertTrue(sortedCdps.getFirst() == _id1);
assertTrue(sortedCdps.getLast() == _id2);
// make CDP2 as head by adjustment via reInsertion
eBTCToken.approve(address(borrowerOperations), type(uint256).max);
borrowerOperations.repayDebt(_id2, 1500, HINT, HINT);
assertTrue(sortedCdps.getSize() == 2);
assertTrue(sortedCdps.getFirst() == _id2);
assertTrue(sortedCdps.getLast() == _id1);
vm.stopPrank();
}
impact and poc have not provided a valid attack path, downgraded to qa g-b
jhsagd76 changed the severity to QA (Quality Assurance)
jhsagd76 marked the issue as grade-b
Lines of code
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/SortedCdps.sol#L382-L385
Vulnerability details
Summary
It is possible for
reInsert
to revert fordata.size == 2
anddata.tail == data.head
inSortedCdps
.Description
For a new list
insert
will assigndata.head
anddata.tail
to the same_id
value:SortedCdps.sol#L382-L385
Calling
remove
right after initializing the list by callinginsert
will not result in the _id being removed, since only one of the two ids present in the list has been removedcontains(_id)
will still return true.Additionally, it will be impossible to call
reInsert
fordata.size == 2
:SortedCdps.sol#L498-L514
This is because of the same reason,
_remove
will only remove one of the two ids and_insert
will revert at !contains(id) check.Proof of Concept
I wrote two certora rules to showcase this finding.
The first one shows that removing an _id does not mean !contains(_id) will be true. This rule should pass but doesn't in the current code. Here is the link for the certora traces.
The second rule is supposed to show that there is at least one non-reverting path in the
reInsert
function. It works withdata.size == 1
but find a violation withdata.size == 2
, which suggests thatdata.size == 2
has a reverting path. In the certora traces for this rule, you will see that there is indeed a reverting path fordata.tail == data.head
.Impact
It will force users to call
remove
twice in order to callreInsert
and it may lead to users incorrectly thinking that their id has been removed when it's actually still present in the list. It might also lead to side effects if some parts of the codebase are assuming that callingremove
imply!contains(_id) == true
afterward.Tool used
Manual Review
Recommended Mitigation Steps
Unfortunately, I don't have enough time left to come up with a good recommendation. But I would recommend adding additional logic to take into account the edge case of
data.size == 2
withdata.tail == data.head
. Make sure to take great care when modifying the code as it could add vulnerabilities in other parts of the code.Assessed type
Other