Flow

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

Logical Error in _coveredDebtOf Function. The _coveredDebtOf function in the SablierFlow contract uses a strict equality check balance == 0 to determine if the balance is zero.

Summary : The _coveredDebtOf function in the SablierFlow contract uses a strict equality check balance == 0 to determine if the balance is zero. However, this check does not consider the case where the balance is a very small value (e.g., 1) that is effectively zero.

Vulnerability Details : Affected Code ..

/// @dev Calculates the amount of covered debt by the stream balance.
function _coveredDebtOf(uint256 streamId) internal view returns (uint128) {
uint128 balance = _streams[streamId].balance;
// If the balance is zero, return zero.
if (balance == 0) {
return 0;
}
uint256 totalDebt = _totalDebtOf(streamId);
// If the stream balance is less than or equal to the total debt, return the stream balance.
if (balance < totalDebt) {
return balance;
}
// At this point, the total debt fits within `uint128`, as it is less than or equal to the balance.
return totalDebt.toUint128();
}

Vulnerable Functionality: The _coveredDebtOf function is used to calculate the amount of covered debt by the stream balance.

Attack Vector: An attacker can exploit this vulnerability by creating a stream with a very small balance (e.g., 1) that is effectively zero.

Exploitation: To exploit this vulnerability, an attacker would need to create a stream with a very small balance and then call the _coveredDebtOf function to calculate the amount of covered debt.

Impact : Here are the details of the impact:

  1. Incorrect Calculation: The _coveredDebtOf function is used to calculate the amount of covered debt by the stream balance. If the function returns an incorrect result, it could lead to incorrect calculations in other parts of the code that rely on this value.

  2. Financial Losses: In the context of the SablierFlow contract, the covered debt represents the amount of debt that has been paid off by the stream balance. If the function returns an incorrect result, it could lead to incorrect calculations of the covered debt, which could result in financial losses for the users of the contract.

  3. Attack Vector: An attacker can exploit this vulnerability by creating a stream with a very small balance (e.g., 1) that is effectively zero. By doing so, the attacker can manipulate the _coveredDebtOf function to return an incorrect result, which could then be used to perform further attacks or exploit other parts of the code.

Proof of Concept Code : Here is a proof of concept code that demonstrates the vulnerability in the _coveredDebtOf function..

pragma solidity ^0.8.0;
contract SablierFlow {
struct Stream {
uint128 balance;
uint128 ratePerSecond;
uint256 startTime;
uint256 endTime;
}
mapping(address => Stream) public streams;
function _coveredDebtOf(address streamId) internal view returns (uint128) {
uint128 balance = streams[streamId].balance;
// If the balance is zero, return zero.
if (balance == 0) {
return 0;
}
uint256 totalDebt = _totalDebtOf(streamId);
// If the stream balance is less than or equal to the total debt, return the stream balance.
if (balance < totalDebt) {
return balance;
}
// At this point, the total debt fits within `uint128`, as it is less than or equal to the balance.
return totalDebt.toUint128();
}
function _totalDebtOf(address streamId) internal view returns (uint256) {
// This function is not implemented, but it would typically return the total debt of the stream.
return 0;
}
function createStream(address streamId, uint128 balance, uint128 ratePerSecond, uint256 startTime, uint256 endTime) public {
streams[streamId] = Stream(balance, ratePerSecond, startTime, endTime);
}
function getCoveredDebt(address streamId) public view returns (uint128) {
return _coveredDebtOf(streamId);
}
}
contract Attacker {
SablierFlow public sablierFlow;
constructor(address sablierFlowAddress) public {
sablierFlow = SablierFlow(sablierFlowAddress);
}
function attack(address streamId) public {
// Create a stream with a very small balance (e.g., 1) that is effectively zero.
sablierFlow.createStream(streamId, 1, 1, block.timestamp, block.timestamp + 1 hours);
// Get the covered debt of the stream.
uint128 coveredDebt = sablierFlow.getCoveredDebt(streamId);
// If the covered debt is not zero, then the attack was successful.
if (coveredDebt != 0) {
// Do something with the covered debt, such as transferring it to the attacker's account.
}
}

This proof of concept code creates a contract called SablierFlow that has a function called _coveredDebtOf that is vulnerable to the attack. The contract also has a function called createStream that allows the attacker to create a stream with a very small balance.

The Attacker contract is used to demonstrate the attack. It creates a stream with a very small balance and then gets the covered debt of the stream using the getCoveredDebt function. If the covered debt is not zero, then the attack was successful.

Tools Used : VS code

Recommendations : Here are some recommendations..

Recommendation 1: Use a More Robust Comparison Method

  • Instead of using a strict equality check (balance == 0), use a comparison that considers the balance as effectively zero if it is less than or equal to a small threshold value (e.g., 1).

  • This will ensure that the _coveredDebtOf function returns the correct result, even when the balance is a very small value.

Recommendation 2: Update the _coveredDebtOf Function

  • Update the _coveredDebtOf function to use the more robust comparison method:

function \_coveredDebtOf(uint256 streamId) internal view returns (uint128) {
uint128 balance = \_streams\[streamId].balance;
// If the balance is effectively zero, return zero.
if (balance <= 1) {
return 0;
}
uint256 totalDebt = _totalDebtOf(streamId);
// If the stream balance is less than or equal to the total debt, return the stream balance.
if (balance < totalDebt) {
return balance;
}
// At this point, the total debt fits within `uint128`, as it is less than or equal to the balance.
return totalDebt.toUint128();
}

Recommendation 3: Review and Test the Updated Code

  • Review the updated code to ensure that it is correct and does not introduce any new vulnerabilities.

  • Test the updated code thoroughly to ensure that it works as expected and does not produce any incorrect results.

Recommendation 4: Consider Using a Library for Safe Arithmetic Operations

  • Consider using a library like OpenZeppelin's SafeMath to perform safe arithmetic operations.

  • This will help prevent overflow and underflow errors, and ensure that the code is more secure.

Recommendation 5: Review and Update Other Parts of the Code

  • Review other parts of the code that rely on the _coveredDebtOf function to ensure that they are correct and do not introduce any new vulnerabilities.

  • Update other parts of the code as necessary to ensure that they work correctly with the updated _coveredDebtOf function.

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.