Closed code423n4 closed 1 year ago
trust1995 marked the issue as primary issue
trust1995 marked the issue as satisfactory
0xBugsy marked the issue as sponsor disputed
It is intended, after reverting and catching success is left as false to trigger fallback and reopen settlement for redemption on root (otherwise routers could return false and double spend assets)
0xBugsy marked the issue as sponsor confirmed
0xBugsy marked the issue as disagree with severity
To give further context on why this is intended if anyFallback is triggered (success set to false) we must set the nonce as executed so as to prevent allowing double spending of assets via calling retrySettlement
before fallback arrives to the root environment and afterwards calling redeemSettlement
.
The function retrySettlement
only allows for bumping gas in case of outofgas and fallback not triggered
0xBugsy marked the issue as sponsor disputed
trust1995 marked the issue as unsatisfactory: Invalid
Hi @0xBugsy ,
If a settlement fails on a Branch chain, a "false" success value is returned, which allows Multichain to call anyFallback
in RootBridgeAgent
to reopen that settlement.
Once that settlement is reopened on RootBridgeAgent, user can call RootBridgeAgent#retrySettlement
on that failed settlementNonce to try that settlement again.
But the PROBLEM is, an attempt to retry that failed settlement will fail on BranchBridgeAgent because the executionHistory for the failed settlement has been set to true. Please take a look and read the //audit comment:
//Try to execute remote request
try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(
recipient, localRouterAddress, data
) returns (bool, bytes memory res) {
(success, result) = (true, res);
} catch (bytes memory reason) {
result = reason;//@audit function call should `return` here to avoid setting executionHistory to true, which would prevent retrying of the failed settlement.
}
//Update tx state as executed
executionHistory[nonce] = true;
But with this fix:
try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(
recipient, localRouterAddress, data
) returns (bool, bytes memory res) {
(success, result) = (true, res);
} catch (bytes memory reason) {
@> return (false, reason);
}
//Update tx state as executed
executionHistory[nonce] = true;
If an error is caught, anyFallback will be triggered in RootBridgeAgent
which would reopen settlement, and retrying of that settlement will work as expected on BranchBridgeAgent.
Also have a look at issue 686
Hi @0xBugsy , If a settlement fails on a Branch chain, a "false" success value is returned, which allows Multichain to call
anyFallback
inRootBridgeAgent
to reopen that settlement. Once that settlement is reopened on RootBridgeAgent, user can callRootBridgeAgent#retrySettlement
on that failed settlementNonce to try that settlement again. But the PROBLEM is, an attempt to retry that failed settlement will fail on BranchBridgeAgent because the executionHistory for the failed settlement has been set to true. Please take a look and read the //audit comment://Try to execute remote request try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement( recipient, localRouterAddress, data ) returns (bool, bytes memory res) { (success, result) = (true, res); } catch (bytes memory reason) { result = reason;//@audit function call should `return` here to avoid setting executionHistory to true, which would prevent retrying of the failed settlement. } //Update tx state as executed executionHistory[nonce] = true;
But with this fix:
try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement( recipient, localRouterAddress, data ) returns (bool, bytes memory res) { (success, result) = (true, res); } catch (bytes memory reason) { @> return (false, reason); } //Update tx state as executed executionHistory[nonce] = true;
If an error is caught, anyFallback will be triggered in
RootBridgeAgent
which would reopen settlement, and retrying of that settlement will work as expected on BranchBridgeAgent. Also have a look at issue 686
This is necessary otherwise we would introduce a race condition where you deploy a tx you know will revert leaving your deposit redeemable and another tx to retry said deposit one right after the other allowing you to double spend. Retrying is not allowed after redemption being triggered to avoid this!
@0xBugsy This would mean that retrySettlement
function is of no use as it will always revert on BranchBridgeAgent
@0xBugsy This would mean that
retrySettlement
function is of no use as it will always revert on BranchBridgeAgent
At minimum protects users against gas price unpredictability that may trigger a _forceRevert()
without the need to wait and added cost for cross-chain execution of retrieveDeposit
, calling locally redeemDeposit
and only after creating a new callOutAndBridge
Please consider the following scenario:
If you consider the recommendation however,
Please consider the following scenario:
- settlement fails on branchbridgeagent, success value of false is returned
- anyFallback is triggered on rootbridgeagent, which reopens the settlement
- Now, user should be able to retry the failed settlement
- If user tries to retrySettlement, the call will fail on branchbridgeagent because executionHistory for that settlementNonce has been set to true.
- This means that retrySettlement is not usable
If you consider the recommendation however,
- anyExecute call is returned from before executionHistory for that settlementNonce is set to true
- user can now successfully retrySettlement because executionHistory for that settlementNonce has not been set to true(it has not been executed because the settlement failed)
I believe you are confusing retry
and retrieve
after a nonce is executed and set to true in executionHistory
you can only redeem
. Retry would work for a nonce not yet executed.
Consequences of remediation suggested are detailed here: https://github.com/code-423n4/2023-05-maia-findings/issues/684#issuecomment-1640906641
retrySettlement should be able to try again a settlement that failed on the BranchBridgeAgent but currently, all failed settlement's executionHistory is set to true on BranchBridgeAgent, which will prevent retrying of that settlement.
As for the "Consequences of remediation",
To give further context on why this is intended if anyFallback is triggered (success set to false) we must set the nonce as executed so as to prevent allowing double spending of assets via calling retrySettlement before fallback arrives to the root environment and afterwards calling redeemSettlement.
Nobody can call redeemSettlement immediately after calling retrySettlement because retrySettlement sets the settlement status to "Success", and redeemDeposit requires that it is "Failed". Also, retrySettlement cannot be called after redeemSettlement because redeemSettlement deletes that settlementNonce data
So the proposed recommendation will not open risks of double spending.
Nobody can call redeemSettlement immediately after calling retrySettlement because retrySettlement sets the settlement status to "Success", and redeemDeposit requires that it is "Failed".
This is explained here:
To give further context on why this is intended if anyFallback is triggered (success set to false) we must set the nonce as executed so as to prevent allowing double spending of assets via calling retrySettlement before fallback arrives to the root environment and afterwards calling redeemSettlement.
More specifically:
via calling retrySettlement before fallback arrives
via calling retrySettlement before fallback arrives
The problem here is that ALL failed settlements on a Branch chain can NEVER be retried. If the settlement succeeded on branch chain, the retrySettlement call will fail(working as intended) But if the settlement failed on branch chain, the retrySettlement will still fail. That's the issue.
via calling retrySettlement before fallback arrives
The problem here is that ALL failed settlements on a Branch chain can NEVER be retried. If the settlement succeeded on branch chain, the retrySettlement call will fail(working as intended) But if the settlement failed on branch chain, the retrySettlement will still fail. That's the issue.
This is invalid. If gas management is successful and fallback is executed that is already an execution, if we left it to false like you are suggesting we would be open to double spending since we cant have a tx be redeemable and retry-able in two completely different networks at the same time
Lines of code
https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1158 https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1182 https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1206
Vulnerability details
Impact
If for whatever reason, any of the calls made to
BranchBridgeAgentExecutor
fromBranchBridgeAgent#anyExecute
fails or reverts, the error is caught in a try-catch, and theexecutionHistory
for thatsettlementNonce
is updated to true. The result is that callingretrySettlement
with that failedsettlementNonce
as parameter would never succeed becauseBranchBridgeAgent
checks theexecutionHistory
, and when it sees that thesettlementNonce
is "true", it assumes that it was successful.Proof of Concept
For example, here is the logic for 0x01 flag in
BranchBridgeAgent#anyExecute
function:In the try block,
BranchBridgeAgentExecutor#executeWithSettlement
is called, and if anything causes it to fail, the whole call will not revert, but (success,result) return values are set. After the errors have been caught, the catch block does not use thereturn
keyword to exit the anyExecute call. This allows the other parts of the function to be called. As we can see, after try-catch block, executionHistory for that settlementNonce is updated to true. So, even for failed settlements(settlements that reverted midway and were not executed), the settlementNonce would be updated to true. Now, users will not be able toretrySettlement
on the failedsettlementNonce
.Occurences
I used 0x01 flag(which is for single asset settlement) as an example. Here are other occurrences of the bug:
https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1158 https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1182 https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1206
Tools Used
Manual Review
Recommended Mitigation Steps
In the catch block of each try-catch in
BranchBridgeAgent#anyExecute
, instead of simply setting theresult
value toreason
, alwaysreturn
(false, reason) to avoid updating executionHistory for thatsettlementNonce
Assessed type
Error