stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: high
Valid

`reSDL` token approvals not deleted after transferring to another chain, which may lead to unauthorized transfers

Summary

reSDL tokens could be transferred between different chains by the owner, But token approvals not resetted during such transfers. If a token was transferred to another chain, and then transferred back, it will preserve its initial approval, even if the owner changed (on another chain).

Issue Details

The Protocol allows users to mint reSDL tokens (ERC721) by depositing SDL token. reSDL represents a position(which holds underlying SDL token balance) and provides token holders(stakers) with rewards.

In this upgrade, the Protocol introduces a new feature which allows reSDL holders to transfer their tokens to another chains. The cross-chain transfer mechanism is based on Chainlink CCIP.

The issue with this feature is that during reSDL token transfer to another chain tokenApprovals not updated (as in regular intrachain transfers).

This behavior introduces risks for safety of users funds, because reSDL tokens can be transferred(e.g. sold) and new owners may end up having unexpected approvals on their assets.

PoC

The following scenario shows how a malicious entity can exploit the issue to steal users funds:

  1. Exploiter mints reSDL_1 token on chain_a (Exploiter is owner of reSDL_1)

  2. Exploiter approves reSDL_1 token to his another address (tokenApprovals[reSDL_1] set)

  3. Exploiter transfers reSDL_1 to chain_b (reSDL_1 on chain_a has no owner, but the approval remains)

  4. Exploiter sells reSDL_1 on chain_b to User (User is owner of reSDL_1 on chain_b, no approvals on chain_b)

  5. User transfers reSDL_1 to chain_a (User is owner of reSDL_1 on chain_a, but Exploiter's second address still has approval on reSDL_1)

  6. Exploiter now uses his approval to transfer reSDL_1 to his address

There possibility for an alternative scenario, where the initial token owner has no intent to exploit the system, but the approved entity can do so.


And to confirm that approvals left untouched, we can review two function, responsible for state updates in SDLPoolPrimary during cross-chain reSDL tokens transfers (functions from SDLPoolSecondary omitted, but have similar logic):

  1. handleOutgoingRESDL

172: function handleOutgoingRESDL(
173: address _sender,
174: uint256 _lockId,
175: address _sdlReceiver
176: )
177: external
178: onlyCCIPController
179: onlyLockOwner(_lockId, _sender)
180: updateRewards(_sender)
181: updateRewards(ccipController)
182: returns (Lock memory)
183: {
184: Lock memory lock = locks[_lockId];
185:
186: delete locks[_lockId].amount;
187: delete lockOwners[_lockId];
188: balances[_sender] -= 1;
189:
190: uint256 totalAmount = lock.amount + lock.boostAmount;
191: effectiveBalances[_sender] -= totalAmount;
192: effectiveBalances[ccipController] += totalAmount;
193:
194: sdlToken.safeTransfer(_sdlReceiver, lock.amount);
195:
196: emit OutgoingRESDL(_sender, _lockId);
197:
198: return lock;
199: }
  1. handleIncomingRESDL

207: function handleIncomingRESDL(
208: address _receiver,
209: uint256 _lockId,
210: Lock calldata _lock
211: ) external onlyCCIPController updateRewards(_receiver) updateRewards(ccipController) {
212: if (lockOwners[_lockId] != address(0)) revert InvalidLockId();
213:
214: locks[_lockId] = Lock(_lock.amount, _lock.boostAmount, _lock.startTime, _lock.duration, _lock.expiry);
215: lockOwners[_lockId] = _receiver;
216: balances[_receiver] += 1;
217:
218: uint256 totalAmount = _lock.amount + _lock.boostAmount;
219: effectiveBalances[_receiver] += totalAmount;
220: effectiveBalances[ccipController] -= totalAmount;
221:
222: emit IncomingRESDL(_receiver, _lockId);
223: }

As we can see, both functions do not update tokenApprovals.

Impact

Affected users will loose their reSDLtokens (with all underlying SDL), after transfer to another chain.
Potential exploiter will not incur substantial costs, since he can sell his tokens for average price, and double his investments after tokens transferred cross-chain by users.

The issue impact might be influenced by the following conditions:

  1. Exploited reSDL tokens may hold arbitrary large amount of SDL

  2. Exploiter can mint a bunch of reSDL tokens to lure more users (the costs of mining will be low, since Exploiter sells tokens for average price)

  3. Exploiter can transfer/approve reSDL token between all available chains, prior to selling it to User. So, if there are 3 supported chains, Exploiter will posses approvals on 2 of them. Which effectively increases his chances to steal the token

  4. Exploiter can lure more users by analyzing conditions on different chains, and proposing attractive strategies for users (e.g. buy this token on chain A, and sell it for profit on chain B)

Recommendation

Consider deleting token approvals when transferring cross-chain(as with regular transfers):

diff --git a/contracts/core/sdlPool/SDLPoolPrimary.sol b/contracts/core/sdlPool/SDLPoolPrimary.sol
--- a/contracts/core/sdlPool/SDLPoolPrimary.sol (revision 549b2b8c4a5b841686fceb9c311dca9ac58225df)
+++ b/contracts/core/sdlPool/SDLPoolPrimary.sol (date 1704900111161)
@@ -185,6 +185,7 @@
delete locks[_lockId].amount;
delete lockOwners[_lockId];
+ delete tokenApprovals[_lockId];
balances[_sender] -= 1;
uint256 totalAmount = lock.amount + lock.boostAmount;
diff --git a/contracts/core/sdlPool/SDLPoolSecondary.sol b/contracts/core/sdlPool/SDLPoolSecondary.sol
--- a/contracts/core/sdlPool/SDLPoolSecondary.sol (revision 549b2b8c4a5b841686fceb9c311dca9ac58225df)
+++ b/contracts/core/sdlPool/SDLPoolSecondary.sol (date 1704900156217)
@@ -267,6 +267,7 @@
delete locks[_lockId].amount;
delete lockOwners[_lockId];
+ delete tokenApprovals[_lockId];
balances[_sender] -= 1;
uint256 totalAmount = lock.amount + lock.boostAmount;
Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

stale-approval

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.