Open code423n4 opened 1 year ago
trust1995 changed the severity to 2 (Med Risk)
trust1995 marked the issue as primary issue
trust1995 marked the issue as satisfactory
0xBugsy marked the issue as sponsor acknowledged
0xBugsy marked the issue as disagree with severity
Unsigned actions / actions that do not make use of the Virtual Account are unadvised for token deposits and thus are left unimplemented in the MulticallRootRoute
but if someone wants to the reverse and create a settlement despite not having the settlement attached to your account that is up to the person developing infrastructure around that to manage as we won't be supporting those actions in our systems / frontend integrations. Although I do agree documentation around this should be much much clearer
Med seems appropriate as a scenario that leads to loss of funds is not explicitly documented as erroneous behavior.
trust1995 marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2023-05-maia/blob/78e49c651fd119b85bb79296620d5cb39efa7cdd/src/ulysses-omnichain/MulticallRootRouter.sol#L175-L236
Vulnerability details
Impact
ulyssses omnichain provides the user with the ability to do what is known as multicall transactions. this is possible via the multicallrouter which from my review of code base, is the protocols primarily method for enabling omnichain transactions between source and destination chains. the contract enables the user who is in the source chain, lets say avax, to do multicalls in the root chain via their virtual account, withdraw funds from their virtual account and then use these funds to do multiple settlements(or a single settlement) in destination chain which could be FTM for instance,and lets them retrieve their desired output tokens based on amounts they deposited, all of this within a single transaction.
There are multiple endpoints exposed to enable these multicall functionalities, one of them enables what is known as a multicallmultioutputnodeposit or multicallsingleoutputnodeposit. they are exposed to branch bridge agents via calling 0x01 flag to signal a transaction without a deposit. this in turn reaches the root, and triggers the executeNoDeposit function in the rootbranchbridgeexecutor contract. that function then fires the anyExecute function in the multicallrouter contract.
based on the payload set by user from the source chain, the function will do a number of things, first it determines the type of transaction via the flag, lets assume the user chose the multicallMultipleOutput, which signals he wants to do multiple calls within root environment, and then finally he wants to do multiple settlements in destination chain. this would trigger the code block below:
the problem manifests in the code block above, essentially because the owner of the settlement that needs to be cleared in destination branch will be the zero address. depending on whether user requested a multicallSingleOutput or a multicallMultiOutput action, this code block will do a number of things, first it will allow the user to make multi calls via payload he specified, second it will approve the Root Port to spend output hTokens on behalf of user. it will then move output hTokens from Root to destination Branch and call 'clearTokens'. this process updates the state of the root bridge via the _updateStateOnBridgeOut. the tokens are then 'cleared' in destination chain, and user should receive his desired output tokens.
However, because the settlement has no linked owner, if the transaction to destination chain fails for whatever reason, the user will be unable to retry the settlement via the root bridge, and they also cannot redeem the settlement. this effectively means the user funds transfered to ulysses root environment are essentialy locked.
as you can see above, a settlement with owner set to address zero is not redeemable. the logic behind that was a redeemed settlement will be deleted and hence getSettlement would retrieve an empty settlement struct with an owner of address zero.
as you can see above, settlement retries will also fail, because once again, the settlement was set with owner of address zero initially.
the impact of this is very high in my opinion because not only will user funds be permanently locked, but system invariants will be broken since the token accounting in system will not be in balance. proof of concept will help clarify this issue further.
Proof of Concept
The POC above demonstrates in detail how this problem develops. in this single transaction it is possible for a user to leverage the multicall feature to transfer their own funds to the rootMulticallRouter, which would then proceed to attempt to settle transactions in the destination chain the user chose.
The following discussion is based on POC inputs:
For FTM , user is requesting an amount of 100 and deposit of 50, this will cause the root bridge agent to update its state, which will effectively increase ts port balance of FTM global by 50, it will also effectively burn the remaining 50 that is in the multi router.
For Avax Global , user is requesting an amount of 100 and deposit of 0, this will cause the root bridge agent to update its state, which will effectively increase its port balance of AVAX global by 100, the full amount.
If the transfer to FTM bridge agent is successful, it will settle the transaction for the user in the destination chain, which will do the following:
For FTM global, it will bridge in(mint) 50 local FTM tokens for the user. it will also withdraw 50 global tokens and give them to the user. so the user ends up with the same amount of tokens he started with, except he now has 50 global FTM and 50 local FTM.
For AVAX global, it will bridge in or mint 100 local AVAX tokens for the user. so the user ends up with the same amount of tokens he started with, except he now has 100 local AVAX tokens.
But what if the calloutandbridge from root to branch bridge agent fails, maybe to low gas. The anyfallback if fired successfully in root bridge, will enable a user to either retry or redeem the settlement. The problem is the _approveAndCallOut function the multicallrootrouter set the owner to the zero address. which means the owner of the settlement will be the zero address. This means the user will have no way to retry or redeem his settlement in the destination branch branch. This effectively means the user has lost the entire amount of global AVAX and FTM he initially deposited with the router to process his request. Not only that but the token accounting in the system will not be in balance.for example the total amount of global AVAX in the system will not equal the total amount of local AVAX, hence the invariant of 1:1 supply is broken. The broken invariant applies to FTM as well. specifically ftm global > ftm local by 50, and AVAX global > AVAX local by 100.
Tools Used
Manual Review
Recommended Mitigation Steps
run the poc again with this modification, it should pass.
Assessed type
Other