Flow

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

Reentrancy Vulnerability in SablierFlow.sol

Summary

The SablierFlow contract is susceptible to a reentrancy attack due to inadequate protection in its withdrawal function, allowing a malicious actor to exploit this weakness to drain funds from the contract.

Finding Description

Reentrancy vulnerabilities occur when a contract calls an external contract and that external contract can call back into the original contract before the first call completes. This breaks the principle of atomicity, leading to potential inconsistencies in the contract's state and unauthorized access to funds.

In the case of SablierFlow.sol, if an attacker controls a malicious contract that calls the _withdraw function, they can repeatedly invoke this function before the state updates are completed. This allows them to withdraw more funds than intended, effectively draining the contract's balance.

Security Guarantees Broken:

  • Atomicity: The principle that a transaction should be fully completed or not at all is violated.

  • Invariants: The expected state of the contract (i.e., user balances) may not hold, as funds can be withdrawn multiple times without proper checks.

Malicious Input Propagation:

  1. An attacker deploys a malicious contract that calls _withdraw on SablierFlow.

  2. Upon executing _withdraw, the contract transfers funds to the attacker's address.

  3. Before the withdrawal completes, the attacker’s fallback function triggers another call to _withdraw.

  4. This process repeats, allowing the attacker to withdraw funds continuously until the balance is depleted.

Vulnerability Details

The specific vulnerability lies within the _withdraw function, which executes a transfer of Ether or tokens to a specified address without sufficient guards to prevent reentrant calls.

Impact

The potential impact of this vulnerability is critical. If exploited, an attacker could drain all funds from the SablierFlow contract, resulting in significant financial losses for users and undermining trust in the contract's reliability.

Proof of Concept

Here is a simplified representation of how the reentrancy could occur:

contract Attacker {
SablierFlow sablier;
constructor(address _sablier) {
sablier = SablierFlow(_sablier);
}
function attack() public {
sablier.withdraw(); // Initial call to withdraw
}
receive() external payable {
if (address(sablier).balance > 0) {
sablier.withdraw(); // Recursive call to withdraw
}
}
}

Recommendations

To mitigate the reentrancy vulnerability, it is recommended to implement the checks-effects-interactions pattern and use a reentrancy guard. Below is a modified version of the _withdraw function:

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SablierFlow is ReentrancyGuard {
// Existing contract variables and functions...
function _withdraw(address to, uint256 amount) external nonReentrant {
require(to != address(0), "Cannot withdraw to zero address");
// Update state before the external call
userBalances[msg.sender] -= amount;
// Interact with external contract after state update
payable(to).transfer(amount);
}
}

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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