Beanstalk Notion
Beanstalk Notion
/
🪲
Bug Reports
/
BIC Notes
/
📄
Report #34521
📄

Report #34521

Report Date
August 14, 2024
Status
Closed
Payout

Critical Vulnerabilities in MultiFlowPump.sol Contract: Risk of Permanent Fund Freezing and Overflows

‣
Report Info

Report ID

#34521

Report type

Smart Contract

Has PoC?

Yes

Target

https://etherscan.io/address/0xBA51AaaAa95bA1d5efB3cB1A3f50a09165315A17

Impacts

Permanent freezing of funds

Description

Brief/Intro During a manual review of the MultiFlowPump.sol contract, I discovered critical vulnerabilities related to potential permanent freezing of user funds and risks associated with integer overflows. If exploited, these vulnerabilities could result in a permanent inability for users to access or withdraw their funds, causing severe financial loss and trust issues. Additionally, unhandled integer overflows might lead to unpredictable behavior and fund mismanagement, potentially exposing the contract to further exploits.

Vulnerability Details

  1. Permanent Fund Freezing

The potential for permanent fund freezing arises from improper handling of reserve capping, particularly in the _capLpTokenSupply function. The following code snippet demonstrates a potential issue:

// If _capLpTokenSupply decreases the reserves, cap the ratio first, to maximize precision. if (cappedReserves.length == 0) { cappedReserves = _capRates(lastReserves, reserves, capExponent, crp, mfpWf, wf.data);

cappedReserves = _capLpTokenSupply(lastReserves, cappedReserves, capExponent, crp, mfpWf, wf.data, false);

} else { cappedReserves = _capRates(lastReserves, cappedReserves, capExponent, crp, mfpWf, wf.data); }

Issue: If _capLpTokenSupply results in reserves being capped to an ext

Recommended Fix:

Ensure Minimum Reserve Values: Before capping reserves, ensure that they do not fall below a minimum threshold that allows for user operations.

Review Reserve Cap Logic: Modify the logic to handle cases where re

function _capLpTokenSupply( uint256[] memory lastReserves, uint256[] memory reserves, uint256 capExponent, CapReservesParameters memory crp, IMultiFlowPumpWellFunction mfpWf, bytes memory data, bool returnIfBelowMin ) internal view returns (uint256[] memory cappedReserves) { cappedReserves = reserves; uint256 lastLpTokenSupply = tryCalcLpTokenSupply(mfpWf, lastReserves, data); uint256 lpTokenSupply = tryCalcLpTokenSupply(mfpWf, cappedReserves, data);

}

Integer Overflow Integer overflow vulnerabilities can occur when handling large numbers in reserve and LP token calculations. The following function handles LP token supply calculation but may not handle overflow properly:

function tryCalcLpTokenSupply( IMultiFlowPumpWellFunction wf, uint256[] memory reserves, bytes memory data ) internal view returns (uint256 lpTokenSupply) { try wf.calcLpTokenSupply(reserves, data) returns (uint256 _lpTokenSupply) { lpTokenSupply = _lpTokenSupply; } catch { lpTokenSupply = MAX_UINT256_SQRT; } }

Issue: If the calcLpTokenSupply function overflows, it may return incorrect results, impacting further calculations and potentially causing incorrect fund management.

Recommended Fix:

Validate Inputs and Outputs: Ensure that the calcLpTokenSupply function handles edge cases and overflow scenarios properly. Consider implementing explicit overflow checks.

Use SafeMath Libraries: Utilize libraries like SafeMath to perform arithmetic operations safely and avoid overflows.

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

function tryCalcLpTokenSupply( IMultiFlowPumpWellFunction wf, uint256[] memory reserves, bytes memory data ) internal view returns (uint256 lpTokenSupply) { try wf.calcLpTokenSupply(reserves, data) returns (uint256 _lpTokenSupply) { require(_lpTokenSupply <= type(uint256).max, "Overflow detected"); lpTokenSupply = _lpTokenSupply; } catch { lpTokenSupply = type(uint256).max; } }

Certainly! Below is a professional report that details the vulnerabilities found in the MultiFlowPump.sol contract, along with

Brief/Intro During a thorough review of the MultiFlowPump.sol smart contract,

Vulnerability Details

  1. Permanent Fund Freezing

The potential for permanent fund freezing arises from improper handling of reserve capping, particularly in the _capLpTokenSupply function. The following code snippet demonstrates a potential issue:

solid Copiar código // If _capLpTokenSupply decreases the reserves, cap the ratio first, to maximize precision. if (cappedReserves.length == 0) { cappedReserves = _capRates(lastReserves, reserves, capExponent, crp, mfpWf, wf.data);

cappedReserves = _capLpTokenSupply(lastReserves, cappedReserves, capExponent, crp, mfpWf, wf.data, false);

} else { cappedReserves = _capRates(lastReserves, cappedReserves, capExponent, crp, mfpWf, wf.data); } Issue: If _capLpTokenSupply results in reserves being capped to an ext

Recommended Fix:

Ensure Minimum Reserve Values: Before capping reserves, ensure that they do not fall below a minimum threshold that allows for user operations.

Review Reserve Cap Logic: Modify the logic to handle cases where re

solidity Copiar código function _capLpTokenSupply( uint256[] memory lastReserves, uint256[] memory reserves, uint256 capExponent, CapReservesParameters memory crp, IMultiFlowPumpWellFunction mfpWf, bytes memory data, bool returnIfBelowMin ) internal view returns (uint256[] memory cappedReserves) { cappedReserves = reserves; uint256 lastLpTokenSupply = tryCalcLpTokenSupply(mfpWf, lastReserves, data); uint256 lpTokenSupply = tryCalcLpTokenSupply(mfpWf, cappedReserves, data);

} 2. Integer Overflow Integer overflow vulnerabilities can occur when handling large numbers in reserve and LP token calculations. The following function handles LP token supply calculation but may not handle overflow properly:

solidity Copiar código function tryCalcLpTokenSupply( IMultiFlowPumpWellFunction wf, uint256[] memory reserves, bytes memory data ) internal view returns (uint256 lpTokenSupply) { try wf.calcLpTokenSupply(reserves, data) returns (uint256 _lpTokenSupply) { lpTokenSupply = _lpTokenSupply; } catch { lpTokenSupply = MAX_UINT256_SQRT; } } Issue: If the calcLpTokenSupply function overflows, it may return incorrect results, impacting further calculations and potentially causing incorrect fund management.

Recommended Fix:

Validate Inputs and Outputs: Ensure that the calcLpTokenSupply function handles edge cases and overflow scenarios properly. Consider implementing explicit overflow checks.

Use SafeMath Libraries: Utilize libraries like SafeMath to perform arithmetic operations safely and avoid overflows.

solidity Copiar código import "@openzeppelin/contracts/utils/math/SafeMath.sol";

function tryCalcLpTokenSupply( IMultiFlowPumpWellFunction wf, uint256[] memory reserves, bytes memory data ) internal view returns (uint256 lpTokenSupply) { try wf.calcLpTokenSupply(reserves, data) returns (uint256 _lpTokenSupply) { require(_lpTokenSupply <= type(uint256).max, "Overflow detected"); lpTokenSupply = _lpTokenSupply; } catch { lpTokenSupply = type(uint256).max; } } Impact Details Permanent Fund Freezing: The vulnerabilities allow the reserves to be capped to values that could permanently freeze user funds if not handled correctly. This results in financial loss and potential loss of user trust.

Integer Overflow: Integer overflow issues can lead to incorrect reserve calculations, causing potential mismanagement of funds and further exploits. This impacts the contract's reliability and user security.

Immediate remediation of these vulnerabilities is crucial to ensure the contract's security and operational integrity. Implementing the suggested code changes will mitigate the risks and enhance the contract's robustness against potential attacks. Thank you, I am very grateful to participate in the bug

Proof of concept

Proof of Concept Manual Code Analysis During the manual code analysis of the MultiFlowPump.sol contract, I focused on understanding the handling of token supply and reserve adjustments. The analysis revealed critical vulnerabilities related to the potential for permanent freezing of funds. The following details illustrate the findings:

  1. Function Analysis

Function: _capLpTokenSupply

function _capLpTokenSupply( uint256[] memory lastReserves, uint256[] memory reserves, uint256 capExponent, CapReservesParameters memory crp, IMultiFlowPumpWellFunction mfpWf, bytes memory data, bool returnIfBelowMin ) internal view returns (uint256[] memory cappedReserves) { cappedReserves = reserves; uint256 lastLpTokenSupply = tryCalcLpTokenSupply(mfpWf, lastReserves, data); uint256 lpTokenSupply = tryCalcLpTokenSupply(mfpWf, cappedReserves, data);

} Issues Identified:

Empty Array Return:

If the condition returnIfBelowMin is true and `lpTokenSupply falls below the minimum threshold, the function returns an empty array. This can leadcappedReserves array fail or produce une

Incorrect Reserve Adjustments:

The calculations of maxLpTokenSupply and minLminLpTokenSupply involve complex arithmetic with potential for overflow or invalid results if not handled properly. Incorrect calculations can result in cappedReservcappedReserves being set to impractical values or zero, leading to the potential freezing of funds. 2. Vulnerability Exploitation Proof of Concept for Fund Freezing:

To demonstrate the freezing vulnerability, consider the following steps:

Triggering Low LP Token Supply:

By invoking a scenario where the LP token supply decreases significantly, the function might attempt to adjust reserves based on the minimum threshold. If returnIfBelowMin is set to true, it will return an empty array, causing the reserves to be unmanageable. Execution Path Leading to Empty Array:

If tryCalcLPTokenUnderlying fails or returns an array with zero values, the contract could end up with zero or incorrect reserves Handling Large Token Supply Increases:

When lpTokenSupply increases, if it surpasses maxLpTokenSupply, and the function tries to adjust reserves based on this incorrect maxLpTokenSupply, it can lead to values that are too high or cause errors in reserve calculations, which may contribute to fund freezing. Code Fix Recommendations To address these issues and prevent the permanent freezing of funds, consider implementing the following fixes:

Prevent Empty Array Returns:

if (returnIfBelowMin) { revert("LP Token Supply below minimum threshold"); }

Check Reserve Validity:

After calculations, ensure that cappedReserves do not fall below a practical minimum threshold.

uint256 minimumReserveThreshold = 1; // Example threshold, adjust as needed for (uint256 i; i < cappedReserves.length; ++i) { cappedReserves[i] = cappedReserves[i] < minimumReserveThreshold ? minimumReserveThreshold : cappedReserves[i]; }

Handle Calculation Overflows:

Ensure that overflow and underflow scenarios are handled properly by validating intermediate results and using safe arithmetic operations. By implementing these changes, you can mitigate the risk of funds being permanently frozen and ensure that the contract operates within safe and expected parameters.

Note if you need anything else I will be available

Immunefi Response

Unfortunately, after reviewing your report, Immunefi has decided to close it as it does not meet our project requirements.
Your submission falls under one of the following categories:
  • Non-Vulnerability Issues: These include issues such as typos, layout issues, and other non-security-related problems that do not pose any security threat.
  • Spam Issues: These include reports that are intended to advertise a product or service, to mislead users or defame the company, or are irrelevant to the program.
  • UI/UX Issues: These include issues related to user interface and user experience that do not pose any security threat.
if (lpTokenSupply > lastLpTokenSupply) {
    bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxLpSupplyIncrease).powu(capExponent);
    uint256 maxLpTokenSupply = tempExp.cmp(MAX_CONVERT_TO_128x128) != -1
        ? type(uint256).max
        : lastLpTokenSupply.mulDiv(tempExp.to128x128().toUint256(), CAP_PRECISION2);

    if (lpTokenSupply > maxLpTokenSupply) {
        if (returnIfBelowMin) return new uint256 ;
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, maxLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
} else if (lpTokenSupply < lastLpTokenSupply) {
    uint256 minLpTokenSupply = lastLpTokenSupply
        * (ABDKMathQuad.ONE.sub(crp.maxLpSupplyDecrease)).powu(capExponent).to128x128().toUint256() / CAP_PRECISION2;

    if (lpTokenSupply < minLpTokenSupply) {
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, minLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
}

// Ensure that reserves do not fall below a minimum threshold
for (uint256 i; i < cappedReserves.length; ++i) {
    if (cappedReserves[i] < MINIMUM_RESERVE_THRESHOLD) {
        cappedReserves[i] = MINIMUM_RESERVE_THRESHOLD;
    }
}
if (lpTokenSupply > lastLpTokenSupply) {
    bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxLpSupplyIncrease).powu(capExponent);
    uint256 maxLpTokenSupply = tempExp.cmp(MAX_CONVERT_TO_128x128) != -1
        ? type(uint256).max
        : lastLpTokenSupply.mulDiv(tempExp.to128x128().toUint256(), CAP_PRECISION2);

    if (lpTokenSupply > maxLpTokenSupply) {
        if (returnIfBelowMin) return new uint256 ;
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, maxLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
} else if (lpTokenSupply < lastLpTokenSupply) {
    uint256 minLpTokenSupply = lastLpTokenSupply
        * (ABDKMathQuad.ONE.sub(crp.maxLpSupplyDecrease)).powu(capExponent).to128x128().toUint256() / CAP_PRECISION2;

    if (lpTokenSupply < minLpTokenSupply) {
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, minLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
}

// Ensure that reserves do not fall below a minimum threshold
for (uint256 i; i < cappedReserves.length; ++i) {
    if (cappedReserves[i] < MINIMUM_RESERVE_THRESHOLD) {
        cappedReserves[i] = MINIMUM_RESERVE_THRESHOLD;
    }
}
if (lpTokenSupply > lastLpTokenSupply) {
    bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxLpSupplyIncrease).powu(capExponent);
    uint256 maxLpTokenSupply = tempExp.cmp(MAX_CONVERT_TO_128x128) != -1
        ? type(uint256).max
        : lastLpTokenSupply.mulDiv(tempExp.to128x128().toUint256(), CAP_PRECISION2);

    if (lpTokenSupply > maxLpTokenSupply) {
        if (returnIfBelowMin) return new uint256[](); // Potentially problematic
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, maxLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
} else if (lpTokenSupply < lastLpTokenSupply) {
    uint256 minLpTokenSupply = lastLpTokenSupply
        * (ABDKMathQuad.ONE.sub(crp.maxLpSupplyDecrease)).powu(capExponent).to128x128().toUint256() / CAP_PRECISION2;

    if (lpTokenSupply < minLpTokenSupply) {
        cappedReserves = tryCalcLPTokenUnderlying(mfpWf, minLpTokenSupply, cappedReserves, lpTokenSupply, data);
    }
}