Reentrancy attack is possible if an ERC777 token is added to a basket or RToken is upgraded to ERC777 token #95
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
duplicate-347
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L268-L276
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L791
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L498-L511
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BackingManager.sol#L105
Vulnerability details
Impact
An attacker can manipulate the issue price of RToken.
An attacker can make BackingManager to sell assets, mint rToken, or execute other operations based on a wrong intermediate state.
Proof of Concept
A similar issue is reported in Solidified - Oct 16 2022.pdf:
But in reality, the problem is more complex than the report describes.
At least the following two points have not been covered or fixed:
The main logic of a cross-contract reentrancy attack is:
Call
RToken.issue()
orRToken.vest()
orRToken.redeem()
to trigger an ERC777 callback(tokensToSend()
ortokensReceived()
) throughissue - _mint()
orissue - safeTransferFrom()
orvest - _mint()
orredeem - safeTransferFrom()
orredeem - _burn()
Then call
BackingManager - manageTokens()
within the ERC777 callback.The
manageTokens()
will cause errors becase it is called in an intermediate state, when RToken's state has been updated but not yet finished transferring all assets to BackingManager.For example, the following test code shows one of the attack scenarios:
RToken.issue()
tokensReceived()
will be triggered byissue - _mint()
tokensReceived()
, callBackingManager - manageTokens()
. Becase it is not fully collateralized now(assets has not been transferred to BackingManager), it will make BackingManager callsetBasketsNeeded()
to set a smaller RToken.basketsNeeded.RToken.issue()
return.RToken.basketsNeeded
is wrong, the attacker (or any user) can issue cheaper RToken (with less assets) and sell RToken to earn profit.Test code:
Tools Used
VS Code
Recommended Mitigation Steps
Since it is hard to ensure that ERC777 token will never be added to a basket, it is recommended to improve the code to completely avoid this attack risk:
issue
,vest
,redeem
,mint
,setBasketsNeeded
) nonReentrant.BasketHandler.manageTokens()
) when RToken is executing issue/vest/redeem.The text was updated successfully, but these errors were encountered: