The refund function can be exploited by the .sendValue external call to continue withdrawing the entranceFee until the PuppyRaffle contract's balance is drained in a reentrancy attack.
When I call the refund function, I supply my valid playerIndex number the function uses to find my address in the players array of addresses then proceeds to send me the public global entranceFee.
However, I can just deploy a smart contract to run a drain attack logic in the receive or fallback function that enables me to keep calling the PuppyRaffle::refund function as long as the balance is greater than a certain amount, e.g the entrance fee.
Manual review
The fix for this is to either implement a nonReentrant lock or opt for the CEI pattern by clearing my address from the players array before making the transfer like so:
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.