The Vault.balance function uses the balanceOfThis function which scales ("normalizes") all balances to 18 decimals.
for (uint8 i; i < _tokens.length; i++) {
address _token = _tokens[i];
// everything is padded to 18 decimals
_balance = _balance.add(_normalizeDecimals(_token, IERC20(_token).balanceOf(address(this))));
}
Note that balance()'s second term IController(manager.controllers(address(this))).balanceOf() is not normalized, but it must be.
This leads to many issues through the contracts that use balance but don't treat these values as normalized values.
For example, in Vault.withdraw, the computed _amount value is normalized (in 18 decimals).
But the uint256 _balance = IERC20(_output).balanceOf(address(this)); value is not normalized but compared to the normalized _amount and even subtracted:
Imagine in withdraw, the output is USDC with 6 decimals, then the normalized _toWithdraw with 18 decimals (due to using _amount) will be a huge number and attempt to withdraw an inflated amount.
An attacker can steal tokens this way by withdrawing a tiny amount of shares and receive an inflated USDC or USDT amount (or any _output token with less than 18 decimals).
Recommended Mitigation Steps
Whenever using anything involving vault.balanceOfThis() or vault.balance() one needs to be sure that any derived token amount needs to be denormalized again before using them.
Handle
cmichel
Vulnerability details
The
Vault.balance
function uses thebalanceOfThis
function which scales ("normalizes") all balances to 18 decimals.Note that
balance()
's second termIController(manager.controllers(address(this))).balanceOf()
is not normalized, but it must be.This leads to many issues through the contracts that use
balance
but don't treat these values as normalized values. For example, inVault.withdraw
, the computed_amount
value is normalized (in 18 decimals). But theuint256 _balance = IERC20(_output).balanceOf(address(this));
value is not normalized but compared to the normalized_amount
and even subtracted:Impact
Imagine in
withdraw
, theoutput
is USDC with 6 decimals, then the normalized_toWithdraw
with 18 decimals (due to using_amount
) will be a huge number and attempt to withdraw an inflated amount. An attacker can steal tokens this way by withdrawing a tiny amount of shares and receive an inflated USDC or USDT amount (or any_output
token with less than 18 decimals).Recommended Mitigation Steps
Whenever using anything involving
vault.balanceOfThis()
orvault.balance()
one needs to be sure that any derived token amount needs to be denormalized again before using them.