solidity.security.balancer-readonly-reentrancy-getrate.balancer-readonly-reentrancy-getrate

profile photo of semgrepsemgrep
Author
unknown
Download Count*

$VAR.getRate() call on a Balancer pool is not protected from the read-only reentrancy.

Run Locally

Run in CI

Defintion

rules:
  - id: balancer-readonly-reentrancy-getrate
    message: $VAR.getRate() call on a Balancer pool is not protected from the
      read-only reentrancy.
    metadata:
      category: security
      technology:
        - solidity
      cwe: "CWE-841: Improper Enforcement of Behavioral Workflow"
      confidence: HIGH
      likelihood: MEDIUM
      impact: HIGH
      subcategory:
        - vuln
      references:
        - https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Other
    patterns:
      - pattern: |
          function $F(...) {
            ...
            $VAR.getRate();
            ...
          }
      - pattern-not-inside: |
          function $F(...) {
            ...
            VaultReentrancyLib.ensureNotInVaultContext(...);
            ...
          }
      - pattern-not-inside: |
          function $F(...) {
            ...
            $VAULT.manageUserBalance(...);
            ...
          }
      - pattern-not-inside: |
          function _updateTokenRateCache(...) {
            ...
          }
      - pattern-not-inside: |
          contract PoolRecoveryHelper {
            ...
          }
      - pattern-not-inside: |
          contract ComposableStablePoolRates {
            ...
          }
      - pattern-not-inside: |
          contract WeightedPoolProtocolFees {
            ...
          }
      - pattern-not-inside: |
          contract $C {
            ...
            function $CHECKFUNC(...) {
              ...
              VaultReentrancyLib.ensureNotInVaultContext(...);
              ...
            }
            ...
            function $F(...) {
              ...
              $CHECKFUNC(...);
              ...
              $VAR.getRate();
              ...
            }
            ...
          }
      - pattern-not-inside: |
          contract $C {
            ...
            function $CHECKFUNC(...) {
              ...
              VaultReentrancyLib.ensureNotInVaultContext(...);
              ...
            }
            ...
            function $F(...) {
              ...
              $VAR.getRate();
              ...
              $CHECKFUNC(...);
              ...
            }
            ...
          }
      - pattern-not-inside: |
          contract $C {
            ...
            function $CHECKFUNC(...) {
              ...
              $VAULT.manageUserBalance(...);
              ...
            }
            ...
            function $F(...) {
              ...
              $VAR.getRate();
              ...
              $CHECKFUNC(...);
              ...
            }
            ...
          }
      - pattern-not-inside: |
          contract $C {
            ...
            function $CHECKFUNC(...) {
              ...
              $VAULT.manageUserBalance(...);
              ...
            }
            ...
            function $F(...) {
              ...
              $CHECKFUNC(...);
              ...
              $VAR.getRate();
              ...
            }
            ...
          }
      - focus-metavariable: $VAR
    languages:
      - solidity
    severity: ERROR

Examples

balancer-readonly-reentrancy-getrate.sol

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

contract BALBBA3USDOracle is IOracle, IOracleValidate {

    function _get() internal view returns (uint256) {
        uint256 usdcLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDC);
        uint256 usdtLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDT);
        uint256 daiLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_DAI);

        uint256 minValue = Math.min(
        Math.min(usdcLinearPoolPrice, usdtLinearPoolPrice),
        daiLinearPoolPrice
        );  
        // ruleid: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }

    function check() internal view returns (uint256) {
        
        VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT));
        // ok: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }

}

contract PoolRecoveryHelper is SingletonAuthentication {

    function _updateTokenRateCache(
        uint256 index,
        IRateProvider provider,
        uint256 duration
    ) internal virtual {
        // ok: balancer-readonly-reentrancy-getrate
        uint256 rate = provider.getRate();
        bytes32 cache = _tokenRateCaches[index];

        _tokenRateCaches[index] = cache.updateRateAndDuration(rate, duration);

        emit TokenRateCacheUpdated(index, rate);
    }
}


contract TestA {
    function checkReentrancy() {
        VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT));
    }

    function test() internal view returns (uint256) {
        checkReentrancy();
        // ok: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }

    function test2() internal view returns (uint256) {
        
        // ruleid: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }
}

contract TestB {
    function checkReentrancy() {
        vault.manageUserBalance(new IVault.UserBalanceOp[](0));
    }

    function test() internal view returns (uint256) {
        checkReentrancy();
        // ok: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }

    function test2() internal view returns (uint256) {
        vault.manageUserBalance(new IVault.UserBalanceOp[](0));        
        // ok: balancer-readonly-reentrancy-getrate
        return (BAL_BB_A3_USD.getRate() * minValue) / 1e18;
    }
}