Sablier

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

Lack of Gas Limit in Hook Allows Malicious Sender to Block Withdrawals

Summary

Calling a hook is a well-known increase in gas usage, but it can also introduce the possibility of denying withdrawals or causing losses for users trying to withdraw their funds from the stream due to a malicious sender.

Vulnerability Details

When the stream is active, users can withdraw any amount up to what has been streamed up to that point. Before the withdrawal ends, two hooks are called. The vulnerability arises in the sender hook:

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 allows the contract (if meeting the requirements) to make an external call to the sender where any amount of gas can be used without restriction. A malicious sender can implement a function that consumes all the gas, preventing the transaction from completing.

Moreover, there is no function to change the sender once set, allowing the sender to cancel and withdraw the remaining funds, while leaving the unclaimed funds locked in the contract.

In the case of an airdrop campaign, these funds can be locked forever, potentially leading to fake campaigns where users can never withdraw their funds. These locked funds do not affect the protocol as they effectively reduce the total supply due to the inability to recover them.

Reason for a malicious sender to do this:

  • To trick the recipient into providing a service without paying and then cancel the stream to recover part of the money.

  • The sender's contract might be unverified, allowing it to activate other functionalities at the recipient's expense.

  • The sender's contract could be a proxy, which can change the functionality of onLockupStreamWithdrawn and manipulate gas usage to deceive the recipient.

PoC

If you implement this minimal test in Remix IDE, you may encounter the error transact to Sablier.withdraw errored: estimated gas for this transaction (113203666) is higher than gasLimit set in the configuration (0x2710). Please raise the gas limit.

contract Sablier {
MaliciousSender recipient;
constructor(address _recipient) {
recipient = MaliciousSender(_recipient);
}
function withdraw(uint256 _amount) public {
try recipient.onLockupStreamWithdrawn({
streamId: 2,
caller: msg.sender,
to: address(recipient),
amount: uint128(_amount)
}) {
} catch { }
}
}
contract MaliciousSender {
function onLockupStreamWithdrawn(uint256 streamId, address caller, address to, uint128 amount) external pure {
uint256 a = 20; uint256 b = 30;uint256 c = 40; uint256 d = 10;uint256 e = 2;uint256 f = 60;uint256 x = 2;uint256 y = 10;uint256 z = 100;uint256 result;
while (true) {
result = (((((((x ** y) % z) + a) * b) / c) - d) ** e) % f;
}
}
}

Impact

Recipients cannot withdraw their funds and their unclaimed asset will be stuck in the contract.

Tools Used

  • Manual code review

Recommendations

Set a specific gasLimit that the sender can use, such as:

if (msg.sender != sender && sender.code.length > 0 && sender != recipient) {
+ try ISablierV2Sender(sender).onLockupStreamWithdrawn{gas: gasLimitSender }({
- try ISablierV2Sender(sender).onLockupStreamWithdrawn({
streamId: streamId,
caller: msg.sender,
to: to,
amount: amount
}) { } catch { }
}

This way, the sender can perform their functionality without consuming excessive gas or denying withdrawals. Test the complexity to estimate the sender's needs.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Known - Contest Details

https://www.codehawks.com/contests/clvb9njmy00012dqjyaavpl44

Support

FAQs

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