Due to not limiting gas sent to external contract when cancelling a stream, recipient will be able to escape stream cancellation.
This makes the cancel
funtion not useful.
Although it was stated on the contest page that "This is intended to support complex transactions implemented in those hooks.", this report explains how severe this issue is, especially in airdrop events.
If a stream is cancellable, sender can call cancel() at any time to cancel the stream and get a refund.
It is stated under the "known issues" section of the contest page that:
"In SablierV2Lockup, when onLockupStreamCanceled(), onLockupStreamWithdrawn() and onLockupStreamRenounced() hooks are called, there could be gas bomb attacks because we do not limit the gas transferred to the external contract. This is intended to support complex transactions implemented in those hooks."
From the comment above, it shows that protocol doesn't want to fix this, but let me explain how this can be a big issue:
I'm sure protocol decide not to fix this based on the assumption that sender knows recipient. So senders can check if the recipient is a contract, and check their onLockupStreamCanceled
implementation before starting a stream with them.
But the problem is, recipient can appear innocent during stream creation (that is, it can be an EoA, or a contract with a harmless onLockupStreamCanceled
function). Then after sender has created the stream, recipient frontruns sender's cancel()
transaction by transferring the stream NFT to a malicious contract that implements a complex onLockupStreamCanceled
function that wastes all the gas(e.g. infinite looping).
This would cause the sender's cancel()
call to revert due to out of gas.
This is a big issue in Sablier airdropping feature.
Sablier allows protocols that want to organize airdrop events to call SablierV2MerkleLockupFactory#createMerkleLL function, setting the merkle root which corresponds to the list of "recipients" eligible for the airdrop.
As is seen below, Creating a merkle lockup contract also allows the sender(protocol organizing airdrop) to specify a cancelable
parameter, which according to the natspec, "Indicates if the stream will be cancelable after claiming."
Now, if the protocol organizing airdrop decides to cancel the stream to all recipients, some malicious recipients will be able to stop the sender from canceling their stream, by sending the Stream NFT to a contract that implements an onLockupStreamCanceled
function that wastes gas.
The result is that, sender was able to cancel the airdrop for some recipients, while some were able to escape cancellation.
Sender will not be able to cancel stream, even when isCancelable
parameter for that stream is set to true.
This allows recipients of an airdrop to escape cancellation, whereas streams of other recipients of that airdrop gets cancelled
Manual Review
Limit the gas transferred to the external contract when calling onLockupStreamCanceled
.
https://www.codehawks.com/contests/clvb9njmy00012dqjyaavpl44
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.