Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

[H-8] Gas Grief possibility in `InheritanceManager::withdrawInheritedFunds`

Summary

Gas Grief is the act of spending the forwarded gas (63/64 max) in order to create DoS attack in the contract.

Vulnerability Details

The withdrawInheritedFunds function in the InheritanceManager contract allows beneficiaries to withdraw inherited funds, including Ether (ETH) when _asset == address(0). ETH is dispersed to each beneficiary using a low-level .call within a loop:

for (uint256 i = 0; i < divisor; i++) {
address payable beneficiary = payable(beneficiaries[i]);
(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
require(success, "something went wrong");
}

When ETH is sent via .call, it triggers the recipient’s receive() or fallback() function (if implemented). A malicious beneficiary contract can implement a gas-intensive receive() or fallback() function that consumes a significant portion of the available gas (up to 63/64ths of the caller’s remaining gas per EIP-150) without reverting. This can cause the total gas cost of withdrawInheritedFunds to exceed the block gas limit, resulting in a transaction failure.

contract MaliciousBeneficiary {
receive() external payable {
uint256 gasToBurn = gasleft() / 2; // Consume half the available gas or we can do even more
while (gasleft() > gasToBurn) {
keccak256(abi.encodePacked(block.timestamp)); // Gas-intensive operation
}
}
}

Impact

High. The beneficiaries will not be able to withdraw ETH from the contract, leaving the funds locked

Tools Used

  • Manual Review

Recommendations

Implement a pull-payment pattern to mitigate gas griefing by allowing beneficiaries to withdraw their ETH shares individually, rather than distributing funds in a single transaction. This isolates the impact of a malicious beneficiary to their withdrawal. Suggested fix:

// Add mapping to track withdrawn amounts
mapping(address => uint256) public ethWithdrawn;
function withdrawInheritedFunds(address _asset) external {
if (!isInherited) {
revert NotYetInherited();
}
uint256 divisor = beneficiaries.length;
if (_asset == address(0)) {
uint256 ethAmountAvailable = address(this).balance;
uint256 amountPerBeneficiary = ethAmountAvailable / divisor;
bool isBeneficiary = false;
for (uint256 i = 0; i < divisor; i++) {
if (msg.sender == beneficiaries[i]) {
isBeneficiary = true;
break;
}
}
if (!isBeneficiary) revert("Not a beneficiary");
if (ethWithdrawn[msg.sender] >= amountPerBeneficiary) revert("Already withdrawn");
ethWithdrawn[msg.sender] = amountPerBeneficiary;
(bool success,) = msg.sender.call{value: amountPerBeneficiary}("");
require(success, "ETH transfer failed");
} else {
// Existing ERC-20 logic remains unchanged
uint256 assetAmountAvailable = IERC20(_asset).balanceOf(address(this));
uint256 amountPerBeneficiary = assetAmountAvailable / divisor;
for (uint256 i = 0; i < divisor; i++) {
IERC20(_asset).safeTransfer(beneficiaries[i], amountPerBeneficiary);
}
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.