Raspy Metal Rattlesnake
Medium
In the Unitroller.sol
contract, the fallback function
is defined as payable
, meaning it can receive Ether. However, the contract does not seem to have a clear intention or need to accept Ether. This could result in unwanted behavior, such as receiving Ether from an untrusted source, which could cause issues in the system, such as locking funds or creating operational inconsistencies.
I have identified that the contract's fallback function is unnecessarily payable, and Ether could be sent without any validation. This might cause an attack vector if an attacker or user mistakenly sends funds to the contract.
-
Contract Issue
Thepayable fallback function
is defined in the contract as follows: https://github.com/sherlock-audit/2024-12-mach-finance/blob/main/contracts/src/Unitroller.sol#L136fallback() external payable { // delegate all other functions to current implementation (bool success,) = comptrollerImplementation.delegatecall(msg.data); assembly { let free_mem_ptr := mload(0x40) returndatacopy(free_mem_ptr, 0, returndatasize()) switch success case 0 { revert(free_mem_ptr, returndatasize()) } default { return(free_mem_ptr, returndatasize()) } } }
Problem:
- The contract is accepting Ether, but there’s no handling logic for it. This could lead to unexpected results, such as mistakenly locking Ether in the contract or causing unnecessary reverts.
-
Test Case (Simulation): If a user or attacker sends Ether to the contract without any function call data (using
.send()
or.transfer()
), the fallback function is triggered. This transaction could fail or cause unexpected outcomes.To simulate a test:
function testEtherAcceptance() public { uint256 balanceBefore = address(unitroller).balance; try unitroller.send(1 ether) { assert(false, "Ether should not be accepted"); } catch { // Expected revert, as the contract should not accept Ether } }
Expected Outcome:
The contract should reject the Ether transfer or trigger a revert due to thepayable
fallback function, which isn't explicitly needed.
-
Remove the
payable
modifier from thefallback
function if the contract is not intended to receive Ether.Proposed Code Change:
fallback() external { // delegate all other functions to current implementation (bool success,) = comptrollerImplementation.delegatecall(msg.data); assembly { let free_mem_ptr := mload(0x40) returndatacopy(free_mem_ptr, 0, returndatasize()) switch success case 0 { revert(free_mem_ptr, returndatasize()) } default { return(free_mem_ptr, returndatasize()) } } }
-
If Ether must be received, add a
receive()
function to control how Ether is handled in the contract, ensuring clarity and preventing unexpected issues.Proposed
receive()
function:receive() external payable { // Ether is accepted, but no action is taken }
- Reward Manipulation: An attacker could send Ether to the contract without validation, causing issues in how funds are handled.
- State Corruption: The fallback function could inadvertently lock Ether or disrupt contract execution.
- Protocol Losses: If the contract is unexpectedly receiving Ether, funds could be locked or cause operational inconsistencies.