Christmas Dinner

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

Report on the Reentrancy Vulnerability in the refund()

Summary

The ChristmasDinner contract has a reentrancy vulnerability in the refund() function. This vulnerability arises because the contract transfers Ether to the user before applying the reentrancy lock, which can lead to a recursive call of the refund() function. This can result in an attacker draining the contract's funds by repeatedly calling the refund() function via their receive() function.

Vulnerability Details

The refund() function in the contract calls two functions to refund the user: _refundERC20() and _refundETH(). The nonReentrant modifier is applied to refund(), but the issue lies in how the Ether refund is executed

function refund() external nonReentrant beforeDeadline {
address payable _to = payable(msg.sender);
_refundERC20(_to);
_refundETH(_to);
emit Refunded(msg.sender);
}

The vulnerability occurs in the _refundETH() function, where Ether is sent to the user before the reentrancy lock is applied. This enables the attacker to call refund() again via their receive() function and re-enter the contract, bypassing the reentrancy guard.

The nonReentrant modifier is intended to prevent reentrancy attacks by ensuring that no other external function can be called before the current function finishes executing. However, in the refund() function, the state is updated (such as sending Ether or ERC20 tokens to the user) before the modifier fully locks the contract.

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

Here is the problematic code in the _refundETH() function:

function _refundETH(address payable _to) internal {
uint256 refundValue = etherBalance[_to];
_to.transfer(refundValue); // This line triggers the user's receive() function
etherBalance[_to] = 0;
}


Impact

The reentrancy vulnerability allows an attacker to repeatedly call the refund() function, draining the contract of its funds.

Tools Used

Manual

Recommendations

The modifier should immediately lock the contract before any other logic is executed. Update the nonReentrant modifier as follows:

modifier nonReentrant() {
require(!locked, "No re-entrancy");
locked = true; // Lock before executing function
_;
locked = false; // Unlock after execution
}
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!