reSDL token approvals are not deleted when transferring reSDL cross-chain, allowing previously approved users to steal a reSDL lock once it's been transferred back to the original chain.
Transferring reSDL locks between users is supposed to delete the approvals for that lockId, as new owners likely do not want the previously approved people to still be approved to their lock. This is evident in the NATSPEC of the function SDLPool::approve
The above requirement is properly enforced in the SDLPool::_transfer()
internal function, due to the line:
delete tokenApprovals[_lockId]
(Line 464 of SDLPool.sol)
However, the requirement is not held when transferring reSDL tokens cross-chain.
When a user calls RESDLTokenBridge::transferRESDL
to send a reSDL lock to the secondary chain,
In SDLPoolPrimary::handleOutgoingRESDL
, the token approvals for the lock _lockId
are not deleted.
Similarly in SDLPoolSecondary::handleOutgoingRESDL
, tokenApprovals are not deleted on sending reSDL back to the primary chain.
Note that how much ever the lock is transferred in the other chain, the approvals in that chain will constantly be deleted, but the approvals in the original chain remain intact.
Since the approvals are not deleted whenever the reSDL token is transferred over to a different chain, the approval will still exist when the token is transferred back to the original chain. Once this happens, the approved user can transfer the lock to themselves, since they have approval to that lockId.
Summary:
The following is one example of how the vulnerability can be exploited.
Initial State: initialOwner owns the lock with lockId=1 (the lockId is arbitrary, the vulnerability exists for every lockId possible).
Steps:
A user (initialOwner) approves their alt account (initialOwner_Alt), before transferring their reSDL lock to another user (receiverOfLock) on secondary chain
The user who just received it on the secondary chain transfers the lock to someone else (otherReceiverOfLock), this only deletes approvals on the secondary chain
The otherReceiverOfLock transfers their lock back to the primary chain, with the receiver being their own address.
initialOwner_Alt who is still approved calls primarySDL.transferFrom(otherReceiverOfLock, initialOwner_Alt, lockIds[0]);
to transfer the lock back to themselves.
Note that the following foundry test may not be immediately runnable on any machine, as it uses a few custom mocks that bypass the need for CCIP but mimic the functionality of the protocol.
Manual Review
Delete the token approvals whenever reSDL tokens are transferred to another chain.
This will ensure that approved users dont have the ability to transfer reSDL tokens once the tokens have been transferred cross-chain.
In SDLPoolPrimary::handleOutgoingRESDL
:
Similarly, in SDLPoolSecondary::handleOutgoingRESDL
:
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.