Sablier

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

Users cannot receive the stream by malicious users.

Summary

Users can receive streaming using the withdraw.
Users can try to withdraw current available amount using this function.
However, malicious users can prevent this by front-withdrawing just 1 wei, causing the user's transaction to revert.

This not only reverts the transaction but also prevents users from receiving their streams.
Users might not recognize the revert reason and try again several times, but they won't achieve the expected result.

When user wants to withdraw some amounts, there is still an attack vector where a malicious user front-runs and withdraws a specific amount, leaving just 1 wei less than the amount requested by the user.

Vulnerability Details

Users can receive current available streaming by calling the withdraw function.

function withdraw(
uint256 streamId,
address to,
uint128 amount
)
public
override
noDelegateCall
notNull(streamId)
updateMetadata(streamId)
{
}

However, an attacker can withdraw 1 wei by front-running the transaction.
This causes the user's transaction to revert, leaving them with only 1 wei from the attacker's action.

function withdraw(
uint256 streamId,
address to,
uint128 amount
)
public
override
noDelegateCall
notNull(streamId)
updateMetadata(streamId)
{
uint128 withdrawableAmount = _withdrawableAmountOf(streamId);
if (amount > withdrawableAmount) {
revert Errors.SablierV2Lockup_Overdraw(streamId, amount, withdrawableAmount);
}
}

In the test below, the reason for the revert is as follows:

[FAIL. Reason: SablierV2Lockup_Overdraw(1, 3209509658246656760000 [3.209e21], 3209509658246656759999 [3.209e21])]

Please add below test to the test/integration/concrete/lockup-linear/create-with-timestamps/createWithTimestamps.t.sol:

function test_Revert_Withdraw() external {
bool TEST = true;
LockupLinear.CreateWithTimestamps memory params = defaults.createWithTimestampsLL();
params.timestamps.cliff = 0;
params.timestamps.start = uint40(block.timestamp - 1 days);
params.recipient = address(this);
uint256 streamId = lockupLinear.createWithTimestamps(params);
uint128 withdrawable = lockupLinear.withdrawableAmountOf(streamId); // 3209509658246656760000
if (TEST == true) {
resetPrank({ msgSender: address(1) });
lockupLinear.withdraw(streamId, address(this), 1); // front-withdraw 1 wei
}
resetPrank({ msgSender: address(this )});
lockupLinear.withdraw(streamId, address(this), withdrawable);
}

Impact

I believe this issue is more severe than a typical gas griefing attack.
In a normal gas griefing attack, the user's transaction is reverted, but they eventually achieve the expected result by attacker.
In this case, users do not receive their streams at all.

Tools Used

Manual

Recommendations

If the requested amount is larger than the current available amount, simply use the current available amount, as is done in most protocols.

function withdraw(
uint256 streamId,
address to,
uint128 amount
)
public
override
noDelegateCall
notNull(streamId)
updateMetadata(streamId)
{
uint128 withdrawableAmount = _withdrawableAmountOf(streamId);
if (amount > withdrawableAmount) {
- revert Errors.SablierV2Lockup_Overdraw(streamId, amount, withdrawableAmount);
+ amount = withdrawableAmount;
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0xnevi Judge
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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