PuppyRaffle::refund()
does not conform to Checks-Effects-Interactions (CEI) pattern.refund()
sets the player address to address(0)
, effectively removing it from the raffle, after sending the refunded entranceFee
. This allows an attacker to re-enter the contract and call refund()
recursively until all funds from the contract have been transferred to the attacker's address.
In PuppyRaffle::refund()
on line 101 the refund amount equal to the entranceFee
is sent to the refunding address. The player address in the players
array is set to address(0)
on line 103 effectively removing the refunding address from the raffle. This means that the function is performing the effect after the interaction and therefore does not conform to Checks-Effects-Interactions pattern:
An attacking address can re-enter the contract when the refund is sent and recursively call refund()
as their address still passes the checks and their address exists in the players
array.
An attacker can re-enter the contract recessively and steal the entire contract balance. This is a high-severity vulnerability as all funds would be lost including the totalFees
and other players' entranceFee
, removing the prizePool
from the raffle.
The following contract takes a raffle address as a constructor parameter. When funds are sent to the contract, the receive()
fallback function is called which calls refund()
recursively on the raffle contract, stealing the contract's balance:
This test enters some players into the raffle to provide some funds to steal, funds the attacking contract, and enters it into the raffle. The attacking contract then calls refund()
. To see if the attacking contract has stolen the entire puppyRaffle
contract balance, there is a check that the raffle contract balance is equal to zero.
The test passes meaning that the attacking contract successfully stole the entire raffle contract balance by transferring the balance to itself, leading to a zero balance left on the puppyRaffle
contract.
Conform to CEI by performing the effect before the impact:
Now, when a contract attempts to re-enter, the player address will no longer exist in the players
array, so the attacker will no longer be calling refund()
with the address at their previous playerIndex
and the function will revert with "PuppyRaffle: Only the player can refund"
.
Forge
reentrancy in refund() function
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.