DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Not checking the return results of functions can amplify vulnerabilities in critical system modules.

Summary:

Likelihood:Low
Severity:High

Low risk
The protocol uses various mechanisms to ensure stability and growth, including the issuance of debt through "Fertilizer".

Fertilizer is a critical component in Beanstalk Farm's recapitalization process. Here’s a simplified explanation of how it works:

  • Debt Issuance: Fertilizer represents debt issued by Beanstalk Farm. When users purchase Fertilizer, they provide liquidity to the protocol.

  • Interest Payment: The protocol promises to pay back Fertilizer holders with interest, incentivizing them to buy and hold Fertilizer.

  • **Recapitalization:**The liquidity from Fertilizer sales is used to stabilize the BEAN token, particularly after a significant drop in value or a market downturn. This recapitalization helps maintain the peg of the stablecoin.

Fertilizer tokens in Beanstalk Farm play a crucial role in the protocol's recapitalization process. When users purchase fertilizer tokens, funds are injected into the system, helping to stabilize and grow the ecosystem. If the mintFertilizer function fails, this continuous inflow of capital halts, leading to stagnation in recapitalization efforts. This can slow down or completely stop the recovery of any losses or financial imbalances within the system.Without the ability to mint new Fertilizer, the protocol cannot attract fresh liquidity, which is essential during market downturns to stabilize the BEAN token.
Fertilizer represents debt that the protocol promises to pay back with interest. If the protocol cannot issue new Fertilizer, it cannot raise funds to meet these obligations.

Vulnerability Details

If such an accident occurs even once, and cannot be repaired in a timely manner, a vicious cycle would easily arise.

  1. Recapitalization Stagnation:
    Fertilizer tokens in Beanstalk Farm play a crucial role in the protocol's recapitalization process. When users purchase fertilizer tokens, funds are injected into the system, helping to stabilize and grow the ecosystem. If the mintFertilizer function fails, this continuous inflow of capital halts,leading to stagnation in recapitalization efforts. This can slow down or completely stop the recovery of any losses or financial imbalances within the system.Without the ability to mint new Fertilizer, the protocol cannot attract fresh liquidity, which is essential during market downturns to stabilize the BEAN token.
    Fertilizer represents debt that the protocol promises to pay back with interest. If the protocol cannot issue new Fertilizer, it cannot raise funds to meet these obligations.

  2. Loss of User Confidence:
    The failure of such a fundamental function could lead to a significant loss of confidence among users. In DeFi ecosystems, user trust is paramount. Without the ability to mint fertilizer tokens, users may perceive the system as unreliable or insecure, prompting them to withdraw their funds(Just like traditional financial models, when investors (users) lose confidence in the institution, it can extremely easily lead to massive redemptions.). This mass exodus can further destabilize the protocol, as the remaining assets might not be sufficient to cover outstanding obligations.

  3. Price Volatility and Arbitrage Opportunities:
    Fertilizer tokens are linked to the broader Beanstalk ecosystem, including price feeds and liquidity pools. A disruption in the minting process can create price discrepancies, leading to arbitrage opportunities. Malicious actors might exploit these discrepancies through flash loan attacks further destabilizing the protocol.

  4. User side DoS Vulnerability: As discussed, if the function relies on external oracles (such as Chainlink, or other external platform) and these oracles return invalid, unchecked values(Here we assume that the price is not manipulated), the entire Fertilizer minting process could be halted(Because we don't cache any price information, we don't have a mechanism to prevent(handle) unexpected events.). This could create a denial-of-service (DoS) situation where users are unable to mint Fertilizer, leading to a significant operational disruption.

Even though Chainlink is widely recognized as very secure, it is not 100% safe. We cannot completely entrust the system's security to external sources. We should conduct additional reviews of the returned values and implement corresponding security measures. After all, our system will need to safeguard at least $77 million in assets in the future!

Proof of Concept

By simulating an external oracle returning an exception value, and we did not validate at all for the case where the return value is non-zero, what exactly would happen?

function getRoundData(uint80) external pure override returns (uint80, int256, uint256, uint256, uint80) {
return (0, 0, 0, 0, 0); // return 0
}
function latestRoundData() external pure override returns (uint80, int256, uint256, uint256, uint80) {
return (0, 0, 0, 0, 0); // return 0
}

Log:

├─ [0] console::log("ready to fertilizerAmountOut") [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] console::log("barn raise token is:", wstETH: [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0]) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] console::log("this is input token, ", wstETH: [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0]) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [210] CL ETH/USD::decimals() [staticcall]
│ │ │ └─ ← [Return] 8
│ │ ├─ [398] CL ETH/USD::latestRoundData() [staticcall]
│ │ │ └─ ← [Return] 0, 0, 0, 0, 0
│ │ ├─ [210] CL WstETH/ETH::decimals() [staticcall]
│ │ │ └─ ← [Return] 8
│ │ ├─ [398] CL WstETH/ETH::latestRoundData() [staticcall]
│ │ │ └─ ← [Return] 0, 0, 0, 0, 0
│ │ ├─ [0] console::log("chainlink price is: ", 0) [staticcall]
│ │ │ └─ ← [Stop]
│ │ ├─ [0] console::log("wstethUsdPrice: ", 0) [staticcall]
│ │ │ └─ ← [Stop]
│ │ └─ ← [Revert] panic: division or modulo by zero (0x12)

As we can see, user can't mint any fertilizer.

Tools Used

Manual Review

Mitigation Strategies

Follow CEI

  • Implement Caching Mechanism: To mitigate the risk of relying entirely on external oracles, implement a caching mechanism that stores the last known valid price. This cached price can be used as a fallback during oracle failures.
    For example:

// protocol\contracts\libraries\Oracle\LibUsdOracle.sol
+uint256 constant PRICE_UPDATE_INTERVAL = 1 hours;// Here I use 1 hour to match season change.
function getUsdPrice(address token, uint256 lookback) internal view returns (uint256) {
AppStorage storage s = LibAppStorage.diamondStorage();
+ uint256 currentTime = block.timestamp;
+ uint256 cachedPrice = s.usdTokenPrice[token]; // cached the price to prevent unexpected vulnerable event happened
+ uint256 ethUsdPrice = LibEthUsdOracle.getEthUsdPrice(lookback);
// The price is saved every hour, but since the probability of the event happening is ""Low"", we can set the duration higher to save too much gas.
+ if (ethUsdPrice > 0) { // If it is normal
+ // Cached price
+ if (currentTime >= s.usdTokenPriceLastUpdate[token] + PRICE_UPDATE_INTERVAL) {
+ s.usdTokenPrice[token] = ethUsdPrice; // cached price
+ s.usdTokenPriceLastUpdate[token] = currentTime; // refresh update time
+ }
+ return ethUsdPrice;
+ }
+ // If error happend, use cachedPrice
+ require(cachedPrice > 0, "Fallback price is also zero, get price error alert ! ! !");
+ return cachedPrice; // If the interval is still within one hour
+}

Because this module is the most core module newly established after beanstalk was attacked in April 17, 2022, in order to ensure the core functions and reduce the vulnerability as much as possible, we should perform due checks on the return value obtained by the external call (in fact, we should check all uncertain return values ​​that cannot be guaranteed to be 100% what we need), which is why we should always follow CEI.So such issues should not be classified as design pattern choice.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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