Glamorous Plum Baboon
High
The ValidationLogic:validateBorrow
function contains an unchecked arithmetic operation when calculating vars.totalDebt
. This can lead to potential overflows, allowing malicious actors to bypass borrow cap validations. Utilizing unchecked arithmetic without proper safeguards introduces risks of unexpected behavior in the smart contract.
In the ValidationLogic:validateBorrow
function, the following operation is performed:
vars.totalDebt = vars.totalSupplyVariableDebt + params.amount;
unchecked {
require(vars.totalDebt <= vars.borrowCap * vars.assetUnit, Errors.BORROW_CAP_EXCEEDED);
}
The addition of vars.totalSupplyVariableDebt
and params.amount
is done outside of a checked arithmetic block, meaning it is susceptible to overflow. If vars.totalSupplyVariableDebt
or params.amount
is maliciously manipulated to near-maximum values, an overflow could occur, resulting in a calculated vars.totalDebt
that is much smaller than the actual value. This would allow malicious borrowers to bypass the borrow cap check (vars.totalDebt <= vars.borrowCap * vars.assetUnit
).
The unchecked arithmetic operation occurs at this line:
vars.totalDebt = vars.totalSupplyVariableDebt + params.amount;
https://github.com/sherlock-audit/2025-01-aave-v3-3/blob/main/aave-v3-origin/src/contracts/protocol/libraries/logic/ValidationLogic.sol#L184-L190
- Severity: High
- Risk: Exploitable overflow in the calculation of
vars.totalDebt
could lead to a failure in enforcing the borrow cap, compromising the protocol’s risk management mechanisms. - Consequences: An attacker can borrow beyond the set borrow cap, which can destabilize the protocol, drain liquidity pools, and harm users’ funds.
Assume:
vars.totalSupplyVariableDebt = 2^256 - 1
(maximumuint256
value)params.amount = 1
Calculation without overflow check:
vars.totalDebt = vars.totalSupplyVariableDebt + params.amount;
// vars.totalDebt overflows to 0
The borrow cap validation:
require(vars.totalDebt <= vars.borrowCap * vars.assetUnit, Errors.BORROW_CAP_EXCEEDED);
// This validation passes since vars.totalDebt is now 0 due to overflow
This enables an attacker to bypass the borrow cap and borrow unrestricted amounts.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
contract ValidateBorrowTest is Test {
// Mock of Errors used in the contract
string constant BORROW_CAP_EXCEEDED = "BORROW_CAP_EXCEEDED";
// Struct to simulate the function's variables
struct Vars {
uint256 totalSupplyVariableDebt;
uint256 totalDebt;
uint256 borrowCap;
uint256 assetUnit;
}
// Function simulating validateBorrow
function validateBorrow(Vars memory vars, uint256 amount) public pure {
vars.totalDebt = vars.totalSupplyVariableDebt + amount;
unchecked {
require(
vars.totalDebt <= vars.borrowCap * vars.assetUnit,
BORROW_CAP_EXCEEDED
);
}
}
function testUncheckedArithmeticOverflow() public {
// Arrange
Vars memory vars;
vars.totalSupplyVariableDebt = type(uint256).max; // Max uint256 value
vars.borrowCap = 10; // Example borrow cap
vars.assetUnit = 1 ether; // Example asset unit
uint256 paramsAmount = 1; // Value to cause overflow
// Act & Assert
vm.expectRevert(bytes(BORROW_CAP_EXCEEDED)); // Expect the borrow cap to be bypassed
validateBorrow(vars, paramsAmount); // Trigger the vulnerability
}
}
-
Setup the Variables:
vars.totalSupplyVariableDebt
is set totype(uint256).max
, which is the maximum value auint256
can hold (2^256 - 1
).params.amount
is set to1
, which will cause an overflow when added tovars.totalSupplyVariableDebt
.
-
Simulate the Vulnerability:
- The function
validateBorrow
performs unchecked arithmetic usingunchecked
block, causing the overflow. - When the overflow occurs,
vars.totalDebt
wraps around to0
.
- The function
-
Check the Behavior:
- The test expects a revert with the error
BORROW_CAP_EXCEEDED
. However, due to the overflow, therequire
condition incorrectly evaluates astrue
, and no revert occurs. - This demonstrates how the overflow bypasses the borrow cap validation.
- The test expects a revert with the error
-
Use
vm.expectRevert
:- This ensures that the test verifies if the borrow cap validation is bypassed.
forge test --match-path test/ValidateBorrowTest.sol
Observe the results. If the vulnerability exists, the test will fail to revert, proving the exploit.
After applying the recommended mitigation (using checked arithmetic), rerun the test. The test should pass, as the borrow cap validation will no longer be bypassed due to overflow.
To prevent overflow, the arithmetic operation should use checked arithmetic provided by libraries such as OpenZeppelin’s SafeMath
or the built-in overflow checks introduced in Solidity 0.8. Specifically:
-
Use Checked Arithmetic: Replace the arithmetic operation with checked arithmetic:
vars.totalDebt = vars.totalSupplyVariableDebt + params.amount; require(vars.totalDebt <= vars.borrowCap * vars.assetUnit, Errors.BORROW_CAP_EXCEEDED);
-
Explicit Safeguards: Alternatively, if unchecked arithmetic is required for gas optimization, ensure that inputs (
vars.totalSupplyVariableDebt
andparams.amount
) are validated beforehand to ensure they cannot approach values that would cause an overflow:require(vars.totalSupplyVariableDebt + params.amount >= vars.totalSupplyVariableDebt, "Arithmetic overflow detected"); vars.totalDebt = vars.totalSupplyVariableDebt + params.amount;
By incorporating these measures, the borrow cap validation will reliably enforce the intended constraints and prevent overflow vulnerabilities.