TokenManager
's flow limit logic is broken for ERC777
tokens
#332
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
M-02
primary issue
Highest quality submission among a set of duplicates
selected for report
This submission will be included/highlighted in the audit report
sponsor confirmed
Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Lines of code
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/its/token-manager/implementations/TokenManagerLockUnlock.sol#L60-L67
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/its/token-manager/implementations/TokenManagerLiquidityPool.sol#L94-L101
Vulnerability details
TokenManager
implementations inherit from theFlowLimit
contract that keeps track of flow in and flow out, and if these two values are too far away from each other, it reverts:Flow in and flow out are increased when some tokens are transferred from one blockchain to another.
There are 3 different kinds of
TokenMaganer
:Let's see how Lock/Unlock and Liquidity Pool implementations handle cases when they have to transfer tokens to users:
As can be seen, they return a value equal to a balance difference before and after token transfer. This returned value is subsequently used by the
giveToken
function in order to call_addFlowIn
:The problem arises when
token
isERC777
token. In that case, token receiver can manipulate its balance in order to increase flow in more than it should be - see POC section for more details.There is no mechanism that will disallow someone from creating
TokenManager
withERC777
token as an underlying token, so it's definitely a possible scenario and the protocol would malfunction if it happens.Note that it's not an issue like "users may deploy
TokenManager
for their malicious tokens that could even lie aboutbalanceOf
" - users may simply want to deployTokenManager
for someERC777
token and bridge theirERC777
tokens and there is nothing unusual about it.Impact
It is possible to manipulate flow in when there is
TokenManager
of type Lock/Unlock or Liquidity Pool and the underlying token isERC777
token. It could be used to create DoS attacks as it won't be possible to transfer tokens from one blockchain to another when the flow limit is reached (it may be possible to send them from one blockchain, but it will be impossible to receive them on another one due to therevert
in the_addFlow
function).In order to recover,
flowLimit
could be set to0
, but the feature was introduced in order to control flow in and flow out and settingflowLimit
to0
means that the protocol won't be able to control it anymore.Here, the availability of the protocol is impacted, but an extra requirement is that there has to be
TokenManager
of Lock/Unlock or Liquidity Pool kind withERC777
underlying token, so I'm submitting this issue as Medium.Proof of Concept
Consider the following scenario:
TokenManager
of type Lock/Unlock was deployed on blockchainX
with underlyingERC777
token (like FLUX, for example) - let's call this tokenT
.X
has low gas price (not strictly necessary, but will be helpful to visualise the issue).T
) from blockchainY
toX
, so she callssendToken
fromTokenManagerLockUnlock
in order to start the process.sendToken
but with some dust amount of tokenT
, but he schedules that transaction from his smart contract calledMaliciousContract
.TokenManagerLockUnlock::_giveToken
is called in order to give that dust amount ofT
to Bob's contract (MaliciousContract
)._giveToken
:first records current balance of
T
ofMaliciousContract
, which happens to be0
and callstransfer
, which finally callsMaliciousContract::tokensReceived
hook.MaliciousContract::tokensReceived
hook looks as follows:, where
<TOKEN_MANAGER_ADDRESS>
is the address of the relevantTokenManager
andmaliciousContractHelper
is an instance ofMaliciousContractHelper
, that exposessendMeT
function which will send all tokens that it has to theMaliciousContract
instance that called it.maliciousContractHelper
has a lot of tokensT
, so whentokensReceived
returns,T.balanceOf(MaliciousContract)
will increase a lot despite the fact that only a dust amount ofT
was sent fromTokenManager
._giveToken
and returned value will be huge, sinceIERC20(token).balanceOf(to) - balance
will now be a big value, despite the fact thatamount
was close to0
.flowLimit
is reached and Alice's transaction will not be processed.In short, Bob has increased the flow in amount for
TokenManager
by sending to his contract a lot of money inERC777
tokensReceived
hook from his another contract. It didn't cost him much since he sent only tiny amount ofT
between blockchains - hence he could use almost all of hisT
tokens for the attack.Of course Bob could perform this attack without waiting for Alice to submit her transaction - scenario presented above was just an example. In reality, Bob can do this at any moment.
It also seems possible to transfer tokens from the same blockchain to itself (by specifying wrong
destinationChain
insendToken
or just by specifyingdestinationChain = <CURRENT_CHAIN>
), so Bob can have his tokensT
only on one blockchain.If gas price on that blockchain is low, Bob can perform that attack a lot of times - all he needs to do is to send tokens back to
MaliciousContractHelper
after each attack (so that it can send it toMaliciousContract
as described above). Finally, he will reachflowLimit
forTokenManager
and will cause denial of service.Tools Used
VS Code
Recommended Mitigation Steps
I would recommend doing one of the following:
ERC777
tokens (possibly even check and ifTokenManager
withERC777
underlying token is to be deployed - just revert)_giveTokens
to ensure that it doesn't exceedamount
, as follows:Assessed type
Other
The text was updated successfully, but these errors were encountered: