The contract SablierV2Lockup
which is inherrited by SablierV2LockupLinear
, SablierV2LockupTranched
and SablierV2LockupDynamic
, has a function SablierV2Lockup::withdraw
which implements a hook to report back to the sender and recipient if they are a contract without reverting, this function could be reentered by a malicious users to report false information back to the sender/recipient.
This happens because although the function doesn't catch anything it doesn't have a protection for reentrancy i.e. recipient or the sender contract can reenter to manipulate each other.
Lets take an example where a sender is contract, example- sender is a contract which has created an airstream campaign and records amounts withdrawn for each stream created after the recipients claim
airdrops.
and run the test;
here the recipient successfully withdraws all and reports only dust amount to the sender(airstream creator) when it should have been reporting 999_999_999_999_999_999_999
which is the second call, instead it allows the sender contract to store only the first withdrawn amount i.e 1(dust).
Note: This doesn't end here actually, after much testing we could see that solidity behaves weird when a function is reentered that has two try catch hooks. When reentered state changes will be done with operations +=
but not with =
, which actually opens more attack vectors(see MORE POC section). I couldn't give a reason why but the examples given should be enough to proof that reentrancy guard is a must.
Warning: test code might be messy forgive me your honor.
run the test:
here we can see that the first stream recipient and the first id is recorded and ignores the second call but bob successfully withdraws all of his second stream amount without reporting.
what this does is;
bob has 2 streams
bob sends one of his stream to the contract that can reenter
contract reenters withdraw
but this time with another streamId
sender contract only records the first streamId which is 1 and ignores the second one
Because reentrancy is not handeled stream recipients/senders can recieve false/outdated information.
The impact of this attack really depends on what the sender/recipient contract use the reported information for, and as the protocol grows, with more streams there could be many different types of impact ranging from critical to low.
impact: medium, because it affects the users of the protocol and the type of impact would depend on how the contracts handle the information. Example- critical if it is used as a reward mechanism, low if used only for like view functions.
likelihood: high, because anyone could do this.
manual
Import the ReentrancyGuard
contract by OZ which has the nonReentrant
modifier:
add this modifer in the withdraw
function in SablierV2Lockup
contract and also in other state changing functions like cancel
, renounce
, etc. for unknown impacts.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.