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

Report #43892

Report Date
April 13, 2025
Status
Closed
Payout

Partial Initialization via initNoWellToken Allows Temporary Freezing and Griefing

‣
Report Info

Report ID

#43892

Report Type

Smart Contract

Has PoC

Yes

Target

https://arbiscan.io/address/0xBA510995783111be5301d93CCfD5dE4e3B28e50B

Impacts

  • Temporary freezing of funds for at least 1 hour
  • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
  • Invariant is missing on a function where it should be implemented

Details

The initNoWellToken function in the Upgradable Well Implementation contract allows attackers to partially initialize the contract, bypassing critical security checks and leaving it in an inconsistent state. This enables temporary freezing of protocol operations and griefing attacks, requiring manual intervention to resolve.

Vulnerability Details

Affected Code:

// WellUpgradeable.sol (Lines 50-52)
function initNoWellToken() external initializer {}

Empty Initializer:

  • The function uses OpenZeppelin's initializer modifier but contains no logic to configure tokens, access controls, or security mechanisms.
  • Once called, it marks the contract as initialized (_initialized = 1), blocking legitimate initialization via init().

Lack of Access Control:

  • No onlyProxy or onlyOwner modifier exists to restrict calls to authorized deployers.
  • Attackers can front-run deployment transactions to execute this function first.

Protocol Impact:

  • Core functions (e.g., swaps, liquidity provisioning) become unusable until reinitialized.
  • Token validation checks in init() are skipped, potentially allowing duplicate tokens in pools.

In-Scope Impacts:

  1. Temporary Freezing of Funds (High Severity):
    • Users cannot interact with the Well's core functions (e.g., swapFrom(), addLiquidity()) until reinitialized.
  2. Griefing (Medium Severity):
    • Attackers can disrupt protocol operations without financial gain, damaging user trust.
  3. Missing Invariant (Medium Severity):
    • No check ensures complete initialization before operational use.

Funds at Risk:

  • All liquidity in Wells deployed via vulnerable implementations becomes temporarily frozen.

References

  • Upgradable Well Implementation Code
  • OpenZeppelin Initializable Contract Guidelines

Proof of Concept

Execution Instructions

Run the following command using Foundry tools:

forge test --match-test test_initNoWellTokenBypass -vvv

Expected Output

BIC Response

The issue you describe will only arise if a contract deployer uses an incorrect/malicious configuration. Since Wells are immutable, all existing Wells are unaffected by this - there is no exploit that can be executed by an attacker. Therefore we are closing this report and no reward will be issued.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/WellUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract ExploitTest is Test {
    WellUpgradeable impl;
    ERC1967Proxy proxy;
    WellUpgradeable well;

    function setUp() public {
        impl = new WellUpgradeable();
        proxy = new ERC1967Proxy(address(impl), "");
        well = WellUpgradeable(address(proxy));
    }

    function test_initNoWellTokenBypass() public {
        // Attacker calls initNoWellToken first
        well.initNoWellToken();

        // Verify initialization state
        assertEq(well.getInitializerVersion(), 1, "Partial initialization failed");

        // Legitimate init() call reverts
        vm.expectRevert("Initializable: contract is already initialized");
        well.init("Test", "TST");
    }
}
[FAIL: Error != expected error: panic: arithmetic underflow or overflow (0x11) != Initializable: contract is already initialized] test_initNoWellTokenBypass() (gas: 189235)
Traces:
  [189235] ExploitTest::test_initNoWellTokenBypass()
    ├─ [29823] ERC1967Proxy::fallback()
    │   ├─ [24815] WellUpgradeable::initNoWellToken() [delegatecall]
    │   │   ├─ emit Initialized(version: 1)
    │   │   └─ ← [Stop]
    │   └─ ← [Return]
    ├─ [1107] ERC1967Proxy::fallback() [staticcall]
    │   ├─ [596] WellUpgradeable::getInitializerVersion() [delegatecall]
    │   │   └─ ← [Return] 1
    │   └─ ← [Return] 1
    ├─ [0] VM::assertEq(1, 1, "Partial initialization failed") [staticcall]
    │   └─ ← [Return]
    ├─ [0] VM::expectRevert(custom error 0xf28dceb3:  .Initializable: contract is already initialized)
    │   └─ ← [Return]
    ├─ [147467] ERC1967Proxy::fallback("Test", "TST")
    │   ├─ [146922] WellUpgradeable::init("Test", "TST") [delegatecall]
    │   │   ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: ExploitTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
    │   │   └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
    │   └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
    └─ ← [Revert] Error != expected error: panic: arithmetic underflow or overflow (0x11) != Initializable: contract is already initialized