Flow

Sablier
FoundryDeFi
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

A stream recipient can make the deposit fully withdrawable immediately, rather than following the sender’s set rate. They can also switch the stream’s token to a more valuable one to steal from depositor

Summary

The vulnerability arises when a malicious recipient creates a second stream with the same sender-recipient pair and a higher rate per second (rps) shortly after the original stream. If a blockchain reorganization (reorg) occurs, causing the original stream’s block to be dropped, the second stream inherits the original stream’s identifier. This enables the recipient to receive deposits intended for the original stream at the higher rps they set, resulting in faster, unintended access to funds.

The second part involves changing the stream token, following the same attack path as above, which allows them to steal a more valuable token from depositor

Vulnerability Details

Looking at the deposit function:

function deposit(
uint256 streamId,
uint128 amount,
address sender,
address recipient
)
external
override
noDelegateCall
notNull(streamId)
notVoided(streamId)
updateMetadata(streamId)
{
// Check: the provided sender and recipient match the stream's sender and recipient.
_verifyStreamSenderRecipient(streamId, sender, recipient);
// Checks, Effects, and Interactions: deposit on stream.
_deposit(streamId, amount);
}

Part 1: Changing the RPS of a stream

The check: _verifyStreamSenderRecipient is meant to protect the deposit function from reorg attacks.
The sender and recipient of a stream can be freely set by the creator. This means that a malicious recipient could create a new stream immediately after the one created by the original sender, setting the same sender and recipient but with a different rate per second (rps). If a reorganization (reorg) occurs, the recipient could then receive the deposit at a rate they defined.

For example,

  • token to be distributed is usdc

  • At block 10, Alice creates a stream with an rps (rate per second) of 0.1 (meaning 0.1 usdc are streamed per second) and sets Bob as the recipient. Bob is then assigned the streamId, say, 5.

  • At block 11, Bob creates another stream, sets Alice as the sender, an rps of 1_000_000 (1_000_000 tokens streamed per second), and himself as the recipient. He receives the streamId of 6.

  • At block 12, Alice (or any other funder) calls the deposit function with arguments: streamId=5, amount=259_200, sender=Alice, and recipient=Bob.

  • A chain reorganization occurs, and block 10 is dropped.

  • Consequently, the streamId of 6 (created by Bob) now becomes 5. The deposit function checks _verifyStreamSenderRecipient, which passes since Bob set the sender and recipient of streamId 6 to match those of the original streamId 5.

  • As a result, the 259_200 usdc deposit, which was intended to be streamed to Bob over a month, becomes available to him all at once.

Say original sender creates streamId n,
Note that recipient only needs to create streamId n+1 with the same sender and recipient as streamId n, but different rps
If a reorg happens at a block <= block at which n was created, deposits made to streamId n will be streamed to recipient at his defined rps

Part 2: Changing the token of a stream

Following the same attack path as above, recipient can also change the token to a more valuable one than what was specified than the original sender

It is very likely that a depositor would have deposited different erc20 tokens at different points in time, and it is not uncommon for the depositor to max approve, or at least, approve SablierFlow to spend a larger amount than what he actually wants to deposit.

Now consider the following scenario:

  • Alice creates a stream to stream usdc to Bob(StreamId=5)

  • Bob creates another stream with same sender and recipient, and changes the token to ETH(StreamId=6)

  • User who has max approved USDC and ETH to SablierFlow in the past deposits 10(USDC) to streamId 5

  • Reorg happens

  • User's deposit goes to Stream created by Bob, which specified ETH as the token

  • 10 ETH gets transferred from User to SablierFlow.

Changing both RPS and token

Attacker can change both the RPS and token to steal the more valuable token from the depositor, and have it streamed to him at once

Note also that anyone, not just the recipient can perform this attack.

Impact

In the event of a reorg, this allows a malicious recipient to

  1. manipulate the stream rate to receive deposited funds more quickly than intended

  2. change the stream token to a more valuable one to steal from depositor

Combining these, a depositor who intends to stream 100 USDC over a month can be manipulated by the recipient to steal 100 ETH from him in an instant.

Tools Used

Manual Review

Recommendations

Don't allow the creator of a stream to specify the sender.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Appeal created

emmanuel Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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