Open code423n4 opened 1 year ago
The issue is well demonstrated, properly formatted, contains a coded POC. Marking as HQ.
0xSorryNotSorry marked the issue as high quality report
0xSorryNotSorry marked the issue as primary issue
Sidu28 marked the issue as sponsor confirmed
The Warden has shown how, due to an incorrect placement of the loop increment, malicious strategies cannot be skipped when slashing queued withdrawals
Because this breaks a core functionality of the contracts, which will also cause loss of funds, I agree with High Severity
Mitigation is straightforward
GalloDaSballo marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2023-04-eigenlayer/blob/main/src/contracts/core/StrategyManager.sol#L556-L579
Vulnerability details
StrategyManager::slashQueuedWithdrawal()
contains anindicesToSkip
parameter to skip malicious strategies, as documented in the function definition:The problem is that the function does not work as expected, and
indicesToSkip
is in fact ignored. If the queued withdrawal contains a malicious strategy, it will make the slash always revert.Impact
Owners won't be able to slash queued withdrawals that contain a malicious strategy.
An adversary can take advantage of this, and create withdrawal queues that won't be able to be slashed, completely defeating the slash system. The adversary can later complete the withdrawal.
Proof of Concept
The
++i;
statement inStrategyManager::slashQueuedWithdrawal()
is misplaced. It is only executed on theelse
statement:Link to code
Let's suppose that the owner tries to slash a queued withdrawal, and wants to skip the first strategy (index
0
) because it is malicious and makes the whole transaction to revert.1 . It defines
indicesToSkipIndex = 0
2 . It enters thefor
loop starting ati = 0
3 .if (indicesToSkipIndex < indicesToSkip.length && indicesToSkip[indicesToSkipIndex] == i)
will be true:0 < 1 && 0 == 0
4 . It increments++indicesToSkipIndex;
to "skip" the malicious strategy, soindicesToSkipIndex = 1
now. 5 . It goes back to thefor
loop. Buti
hasn't been modified, soi = 0
still 6 .if (indicesToSkipIndex < indicesToSkip.length && indicesToSkip[indicesToSkipIndex] == i)
will be false now:1 < 1 && 0 == 0
7 . It will enter theelse
statement and attempt to slash the strategy anyway 8 . If the strategy is malicious it will revert, making it impossible to slash 9 . The adversary can later complete the withdrawalPOC Test
This test shows how the
indicesToSkip
parameter is completely ignored.For the sake of simplicity of the test, it uses a normal strategy, which will be slashed, proving that it ignores the
indicesToSkip
parameter and it indeed callsqueuedWithdrawal.strategies[i].withdraw()
.A malicious strategy that makes
withdraw()
revert would make the whole transaction revert (not shown on this test but easily checkeable as the function won't catch it).Add this test to
src/tests/StrategyManagerUnit.t.sol
and runforge test -m "testSlashQueuedWithdrawal_IgnoresIndicesToSkip"
.Tools Used
Manual Review
Recommended Mitigation Steps
Place the
++i
outside of the if/else statement. This way it will increment each time the loop runs.Assessed type
Loop