Summary
AaveDIVAWrapper::batchApproveCollateralTokenForAave
& AaveDIVAWrapper::approveCollateralTokenForAave
Lacks Access Controls Allowing Unauthorized Approvals
Vulnerability Details
The AaveDIVAWrapper::batchApproveCollateralTokenForAave
& AaveDIVAWrapper::approveCollateralTokenForAave
functions have no access controls, allowing any user to approve registered collateral tokens for Aave. These functions can be called repeatedly by malicious actors to manipulate approval states or cause unnecessary gas costs.
function batchApproveCollateralTokenForAave(address[] calldata _collateralTokens) external override {
uint256 _length = _collateralTokens.length;
for (uint256 i = 0; i < _length; i++) {
_approveCollateralTokenForAave(_collateralTokens[i]);
}
}
function _approveCollateralTokenForAave(address _collateralToken) internal {
if (AaveDIVAWrapper::_collateralTokenToWToken[_collateralToken] == address(0)) {
revert CollateralTokenNotRegistered();
}
uint256 currentAllowance = IERC20Metadata(_collateralToken).allowance(address(this), AaveDIVAWrapper::_aaveV3Pool);
IERC20Metadata(_collateralToken).safeIncreaseAllowance(AaveDIVAWrapper::_aaveV3Pool, type(uint256).max - currentAllowance);
}
Proof of Concept
describe("Unauthorized Approval", function() {
it("Should allow any address to call approval function", async function() {
const { s } = await loadFixture(setup);
const maliciousActor = s.acc2;
await s.aaveDIVAWrapper
.connect(s.owner)
.registerCollateralToken(collateralToken);
await s.aaveDIVAWrapper
.connect(maliciousActor)
.batchApproveCollateralTokenForAave([collateralToken]);
await s.aaveDIVAWrapper
.connect(maliciousActor)
.batchApproveCollateralTokenForAave([collateralToken]);
});
});
Impact
Malicious actors can repeatedly call approvals causing unnecessary gas costs for protocol
Potential for approval manipulation if tokens require approval reset
External control over protocol approval states could interfere with protocol operations
Tools Used
Hardhat
Recommendations
Two possible approaches:
Restrictive approach - Add onlyOwner modifier:
- function batchApproveCollateralTokenForAave(address[] calldata _collateralTokens) external override {
+ function batchApproveCollateralTokenForAave(address[] calldata _collateralTokens) external override onlyOwner {
uint256 _length = _collateralTokens.length;
for (uint256 i = 0; i < _length; i++) {
_approveCollateralTokenForAave(_collateralTokens[i]);
}
}
Rate-limited approach - Keep permissionless but add controls:
+ mapping(address => uint256) public lastApprovalTimestamp;
function batchApproveCollateralTokenForAave(address[] calldata _collateralTokens) external override {
+ require(block.timestamp >= lastApprovalTimestamp[msg.sender] + 1 days, "Rate limit: wait 24h");
+ lastApprovalTimestamp[msg.sender] = block.timestamp;
+ emit CollateralTokenApprovalUpdated(msg.sender, _collateralTokens);
uint256 _length = _collateralTokens.length;
for (uint256 i = 0; i < _length; i++) {
_approveCollateralTokenForAave(_collateralTokens[i]);
}
}
+ event CollateralTokenApprovalUpdated(address indexed caller, address[] tokens);
The rate-limited approach is recommended as it preserves needed functionality while preventing abuse. This keeps the system permissionless but controlled.