The modifier ChristamsDinner::nonReentrant does not set the variable locked to true before function execution. While the modifier is intended to prevent reentrancy attacks, failing to set the variable locked to true does not provide the intended protection.
Modifiers to protect against reentrancy attacks typically use a state variable that flags whether the function is currently being executed to prevent a function from being called again before the current execution is complete. The ChristmasDinner.sol contract uses the flag locked which is initially set to false. The nonReentrant modifier first checks the value of the variable locked - if it is set to false the function can be executed. However the implementation of the nonReentrant modifier fails to set locked to true after the check to prevent future reentrancy of the function. This allows an attacker to call the function multiple times before the current execution is complete, potentially leading to reentrancy attacks.
The following scenario can lead to an exploit:
ChristmasDinner.sol contract is used as a base contract for another contract.
The inheriting contract extends the existing functionality and introduces a new function withdrawEth that allows the caller to withdraw ETH from the contract similar to the refund function. Function uses the nonReentrant modifier but then sends the ETH with call instead of transferassuming the modifier protects against reentrancy attacks.
An attacker creates a contract that calls the withdrawEth function multiple times via the fallback function before the execution of withdrawEth is complete, draining all funds from the inheriting contract.
Code:
Place following code into ChristmasDinnerTest.t.sol:
The impact of this vulnerability is LOW because the modifier is only used once in the ChristmasDinner::refund function. Because the function uses transfer to send ETH to the caller, the reentrancy vulnerability is not exploitable since the transfer function has a gas limit of 2300 gas to prevent reentrancy attacks. However, this issue should be addressed to prevent reentrancy vulnerabilities in inheriting contracts or possible future changes to the contract.
Manual review, custom test
To prevent reentrancy attacks, the locked variable should be set to true before the function is executed. This will prevent the function from being called again before the current execution is complete. Alternatively, consider to use the ReentrancyGuard contract from the OpenZeppelin library which provides a battle-tested implementation of a reentrancy lock.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.