Open code423n4 opened 2 years ago
I think there is a misunderstanding here -- the user takes on the slippage risk both into and out of the local assets, and the router has consistent returns on what was bridged.
On xcall
, the user swaps the amount put in for the local asset. This incurs some slippage, and only the amount of the local asset is bridged directly. It is the bridged amount that the router should supply liquidity for, and take fees on. Once the router supplies liquidity in execute
(bridged amount minus the fees), then it is swapped for the local asset and sent to the user. The user may get some different amount here, but it is the user who is impacted by this slippage. On handle, the router is credited the bridged amount.
However, there was a separate bug where the transferId
was generated with the wrong amount
on execute, so that could be where the confusion is coming from.
I actually agree with the warden here, it seems that they're right about the issue but they just failed to mention the main reason why its an issue is because transferId
is calculated using _args.amount
which does not necessarily equal bridgedAmt
due to slippage. Therefore, routers may end up fronting too much liquidity and receive considerably less when the bridge transfer is eventually reconciled. This seems rather severe as the user will receive the full transfer amount without slippage. This could be abused to drain routers on low liquidity tokens.
Right -- I agree that the problems outlined here would be the true consequences for a mismatched transferId
. If the question is to take the action outlined here -- specifically to keep this open and downgrade #227 as a QA -- that would work with me.
Per conversation with @LayneHaber - related mitigation link here: https://github.com/connext/nxtp/commit/f41a156b55a01837c8f57a77e52781f74406e1cd
Lines of code
https://github.com/code-423n4/2022-06-connext/blob/b4532655071566b33c41eac46e75be29b4a381ed/contracts/contracts/core/connext/facets/BridgeFacet.sol#L526-L616 https://github.com/code-423n4/2022-06-connext/blob/b4532655071566b33c41eac46e75be29b4a381ed/contracts/contracts/core/connext/facets/BridgeFacet.sol#L753-L803 https://github.com/code-423n4/2022-06-connext/blob/b4532655071566b33c41eac46e75be29b4a381ed/contracts/contracts/core/connext/facets/BridgeFacet.sol#L398-L428 https://github.com/code-423n4/2022-06-connext/blob/b4532655071566b33c41eac46e75be29b4a381ed/contracts/contracts/core/connext/facets/BridgeFacet.sol#L345-L351
Vulnerability details
Impact
routers pay for transaction in destination domain then nomad messages come and routers get paid again. but the amount routers pay in
execute()
are what transaction sender signed and the amount routers receive is what nomad sends and handles in_reconcile()
but this two amount can be different because of slippage and swap that happens inxcall()
because the amount sent in nomad message is the result ofswapToLocalAssetIfNeeded()
. So it's possible for routers to lose funds if some slippage happens in that swap.Proof of Concept
This is
xcall()
code:As you can see it swaps what user sent to
LoccalAsset
which the amount isbridgedAmt
and then send value ofbridgedAmt
to nomad bridgemessage = _formatMessage(_args, bridged, transferId, bridgedAmt)
. but the amount user signed in_args.amount
is different and that what user sends to contract. the reasons thatbridgedAmt
could be different than_args.amount
is: 1- deflationary tokens in transferring from user. 2- slippage in swap to local token. This is_reconcile()
code:As you can see it uses amount in message to calculate what router should receive. This is
_handleExecuteLiquidity()
code which is used inexecute()
:As you can see it uses the amount defined in
ExecuteArgs
to see how much routers should pay.because of this two issue (deflationary tokens and swap slippage) attacker could fool protocol to spend more than what he transferred to protocol. this could be seen as two bug so.
Tools Used
VIM
Recommended Mitigation Steps
update spending amount based on (deflationary tokens and swap slippage)