Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Invalid

Withdraw DoS enabled by external call and `updateMetadata` modifier

Description

The SablierV2Lockup::withdraw function allows a recipient to withdraw an asset from a Sablier, which is a crucial function of the protocol. However, a malicious sender can exploit this by implementing a hook to consume 63/64 of the remaining gas in the hook function, thereby increasing the gas cost for the recipient's withdrawal. In the worst-case scenario, if the recipient does not allocate enough gas, the function will revert. This event adds 1433 gas after the try-catch block, implying that there must be 91,172 gas at the start of this block.

Currently, the minimum gwei per gas on the Ethereum mainnet ranges from 20-40 gwei, and 1 ether is approximately 4k$. This means that the transaction will cost a minimum of 14.60, and this cost could be significantly higher considering all the instructions above the try/catch block. During periods of high congestion, as seen in 2021, this price could increase tenfold.

A less convenient workaround for the user could be to wait for the total funds to unlock and for a very low gwei per gas price before withdrawing all the money at once.

function withdraw(
uint256 streamId,
address to,
uint128 amount
@> ) public override noDelegateCall notNull(streamId) updateMetadata(streamId) {
...
if (msg.sender != recipient && recipient.code.length > 0) {
try
ISablierV2Recipient(recipient).onLockupStreamWithdrawn({
streamId: streamId,
caller: msg.sender,
to: to,
amount: amount
})
{} catch {}
}
if (msg.sender != sender && sender.code.length > 0 && sender != recipient) {
try
@> ISablierV2Sender(sender).onLockupStreamWithdrawn({
streamId: streamId,
caller: msg.sender,
to: to,
amount: amount
})
{} catch {}
}
}

This same vulnerability was previously identified in version 1.1.0 in the cancel function here (section 3.2.1)

Risk

Likelyhood: Medium/High

  • Any malicious sender can prevent a recipient from withdrawing funds or increase the recipient's gas consumption.

  • Recipients can mitigate the attack by withdrawing large amounts of money during periods of low network congestion. However, this means they cannot withdraw whenever they want.

Impact: High

  • High gas consumption for the recipient.

  • Funds may get stuck in the contract, especially if the Sablier is not cancellable or during network congestion.

Recommended Mitigation

Emit the MetadataUpdate event before both try/catch statements. Since this event will be checked off-chain (once the transaction is fully completed), the order of emission does not have a significant impact.

function withdraw(
uint256 streamId,
address to,
uint128 amount
- ) public override noDelegateCall notNull(streamId) updateMetadata(streamId) {
+ ) public override noDelegateCall notNull(streamId) {
...
+ emit MetadataUpdate({ _tokenId: streamId });
if (msg.sender != recipient && recipient.code.length > 0) {
try
ISablierV2Recipient(recipient).onLockupStreamWithdrawn({
streamId: streamId,
caller: msg.sender,
to: to,
amount: amount
})
{} catch {}
}
if (msg.sender != sender && sender.code.length > 0 && sender != recipient) {
try
ISablierV2Sender(sender).onLockupStreamWithdrawn({
streamId: streamId,
caller: msg.sender,
to: to,
amount: amount
})
{} catch {}
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Withdraw DoS enabled by external call and `updateMetadata` modifier

0xnevi Judge
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
vesla0x1 Auditor
over 1 year ago
vesla0x1 Auditor
over 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Withdraw DoS enabled by external call and `updateMetadata` modifier

Support

FAQs

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