solidity.security.balancer-readonly-reentrancy-getpooltokens.balancer-readonly-reentrancy-getpooltokens
semgrep
Author
unknown
Download Count*
License
$VAULT.getPoolTokens() call on a Balancer pool is not protected from the read-only reentrancy.
Run Locally
Run in CI
Defintion
rules:
- id: balancer-readonly-reentrancy-getpooltokens
message: $VAULT.getPoolTokens() 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://quillaudits.medium.com/decoding-sentiment-protocols-1-million-exploit-quillaudits-f36bee77d376
- https://hackmd.io/@sentimentxyz/SJCySo1z2
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
vulnerability_class:
- Other
patterns:
- pattern-either:
- pattern: |
function $F(...) {
...
$RETURN = $VAULT.getPoolTokens(...);
...
}
- metavariable-pattern:
metavariable: $RETURN
pattern-regex: .*uint256\[].*
- pattern-not-inside: |
contract $C {
...
function $CHECKFUNC(...) {
...
VaultReentrancyLib.ensureNotInVaultContext(...);
...
}
...
function $F(...) {
...
$CHECKFUNC(...);
...
$RETURN = $VAULT.getPoolTokens(...);
...
}
...
}
- pattern-not-inside: |
contract $C {
...
function $CHECKFUNC(...) {
...
VaultReentrancyLib.ensureNotInVaultContext(...);
...
}
...
function $F(...) {
...
$RETURN = $VAULT.getPoolTokens(...);
...
$CHECKFUNC(...);
...
}
...
}
- pattern-not-inside: |
contract $C {
...
function $CHECKFUNC(...) {
...
$VAULT.manageUserBalance(...);
...
}
...
function $F(...) {
...
$RETURN = $VAULT.getPoolTokens(...);
...
$CHECKFUNC(...);
...
}
...
}
- pattern-not-inside: |
contract $C {
...
function $CHECKFUNC(...) {
...
$VAULT.manageUserBalance(...);
...
}
...
function $F(...) {
...
$CHECKFUNC(...);
...
$RETURN = $VAULT.getPoolTokens(...);
...
}
...
}
- pattern-not: |
function $F(...) {
...
VaultReentrancyLib.ensureNotInVaultContext(...);
...
}
- pattern-not: |
function $F(...) {
...
$VAULT.manageUserBalance(...);
...
}
- pattern-not-inside: |
contract LinearPool {
...
}
- pattern-not-inside: |
contract ComposableStablePool {
...
}
- pattern-not-inside: |
contract BalancerQueries {
...
}
- pattern-not-inside: |
contract ManagedPool {
...
}
- pattern-not-inside: |
contract BaseWeightedPool {
...
}
- pattern-not-inside: |
contract ComposableStablePoolStorage {
...
}
- pattern-not-inside: |
contract RecoveryModeHelper {
...
}
- focus-metavariable:
- $VAULT
languages:
- solidity
severity: ERROR
Examples
balancer-readonly-reentrancy-getpooltokens.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract BALBBA3USDOracle is IOracle, IOracleValidate {
function check() internal view {
// ok: balancer-readonly-reentrancy-getpooltokens
(address[] memory poolTokens, , ) = getVault().getPoolTokens(getPoolId());
}
function test() view {
// ruleid: balancer-readonly-reentrancy-getpooltokens
(, uint256[] memory balances, ) = IVault(VAULT_ADDRESS).getPoolTokens(poolId);
}
function getPrice(address token) external view returns (uint) {
(
address[] memory poolTokens,
uint256[] memory balances,
// ruleid: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
uint256[] memory weights = IPool(token).getNormalizedWeights();
uint length = weights.length;
uint temp = 1e18;
uint invariant = 1e18;
for(uint i; i < length; i++) {
temp = temp.mulDown(
(oracleFacade.getPrice(poolTokens[i]).divDown(weights[i]))
.powDown(weights[i])
);
invariant = invariant.mulDown(
(balances[i] * 10 ** (18 - IERC20(poolTokens[i]).decimals()))
.powDown(weights[i])
);
}
return invariant
.mulDown(temp)
.divDown(IPool(token).totalSupply());
}
}
abstract contract LinearPool {
function check() internal view {
// ok: balancer-readonly-reentrancy-getpooltokens
(, uint256[] memory registeredBalances, ) = getVault().getPoolTokens(getPoolId());
}
}
contract Sentiment {
function checkReentrancy() internal {
vault.manageUserBalance(new IVault.UserBalanceOp[](0));
}
function getPrice(address token) external returns (uint) {
checkReentrancy();
(
address[] memory poolTokens,
uint256[] memory balances,
// ok: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
//...
}
}
contract Testing {
function getPrice(address token) external returns (uint) {
(
address[] memory poolTokens,
uint256[] memory balances,
// ok: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
vault.manageUserBalance(new IVault.UserBalanceOp[](0));
//...
}
}
contract TestingSecondCase {
function checkReentrancy() internal {
VaultReentrancyLib.ensureNotInVaultContext(getVault());
}
function getPrice(address token) external returns (uint) {
checkReentrancy();
(
address[] memory poolTokens,
uint256[] memory balances,
// ok: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
//...
}
function getPrice2(address token) external returns (uint) {
(
address[] memory poolTokens,
uint256[] memory balances,
// ruleid: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
//...
}
function getPrice3(address token) external returns (uint) {
VaultReentrancyLib.ensureNotInVaultContext(getVault());
(
address[] memory poolTokens,
uint256[] memory balances,
// ok: balancer-readonly-reentrancy-getpooltokens
) = vault.getPoolTokens(IPool(token).getPoolId());
//...
}
}
Short Link: https://sg.run/803Q