L2MessageReceiver.sol::retryMessage()
checks the transaction has a failed message as follow:
Then, goes ahead to call the _nonblockingLzReceive
before deleting the payload from the failed message:
However, in the _nonblockingLzReceive()
there is an external call to the token contract to mint to the user
If this token is changed in the future to a token that uses a _safeMint
or implements its before/afterTransfer callbacks, then an attacker would be able to receive an infinite amount of reward by reentering the retryMessage before the failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_].
Attack Scenario:
Users deposit into the protocol and are now eligible for rewards
Time passes and protocol makes changes to the rewardToken address to a contract that uses _safeMint
in the mint function call
An attacker notices this and deploys a contract that deposits into the protocol and later calls claim()
which sends the mint message to the l2
lzReceive
gets triggered on l2 and call to IL2MessageReceiver(address(this)).nonblockingLzReceive()
fails
The attacker then calls retryMessage
which mints to the attacker contract, however, the payload isn't yet before making the external call.
So the attacker contract makes call into the retryMessage
and performs the same thing again.
If this token is changed in the future to a token that uses a _safeMint
or implements its before/afterTransfer callbacks, then an attacker would be able to receive an infinite amount of reward by reentering the retryMessage before the failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_].
Make this change
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.