Open c4-submissions opened 11 months ago
0xA5DF marked the issue as sufficient quality report
0xA5DF marked the issue as primary issue
0xBugsy (sponsor) confirmed
0xBugsy marked the issue as disagree with severity
Despite only affecting functions that don't use token deposits and disagreeing with severity, this is a great finding and we will address it in our codebase
There is no loss of funds or denial of service to users. However, part of the protocol (the emergency features) are rendered ineffective. Medium (barely).
alcueca changed the severity to 2 (Med Risk)
alcueca marked the issue as satisfactory
alcueca marked the issue as selected for report
Hi @alcueca, thank you for taking the time to read this. Here are some points I'd like to point out about the duplicate issue #190:
syncBranchBridgeAgent
, the issue of calls originating still exists (as mentioned in the note under point 1 in my report above). In short, bridge agent synchronization on root chain has no relation to this issue. The root cause is the absence of this check require(localPortAddress.isBridgeAgent(address(this)), "Unrecognized BridgeAgent!");
in the branch bridge agent functions itself.Thank you once again for taking the time to read this.
Lines of code
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L195
Vulnerability details
Impact
Bridge agents are removed/toggled off to stop communication to/from them (confirmed by sponsor - check image below) in case of some situation such as a bug in protocol or in case of an upgrade to a newer version of the protocol (in case LayerZero decides to upgrade/migrate their messaging library)
Admin router contracts are able to disable or toggle off anybody's bridge agents due to any reasons through the removeBranchBridgeAgent() function in CoreRootRouter.sol contract. But although this toggles off the branch bridge agent in the Branch chain's BranchPort, it still allows No Deposit calls to originate from that deactivated branch bridge agent.
Proof of Concept
Here is the whole process:
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/CoreRootRouter.sol#L186
1. Admin in CoreRootRouter decides to remove branch bridge agent through removeBranchBridgeAgent() function
Here is the execution path of the call:
Root chain to Endpoint (EP): removeBranchBridgeAgent => callOut => _performCall => send
EP to Branch chain: receivePayload => lzReceive => lzReceiveNonBlocking => _execute => executeNoSettlement (Executor) => executeNoSettlement (Router) => _removeBranchBridgeAgent => toggleBridgeAgent (Port)
The following state change occurs in function toggleBridgeAgent():
Note: Over here there is no call made back to the root chain again which means the synced branch bridge agent is still synced in root (as shown here). But even if it is synced in Root chain, there are no checks or even state variables to track activeness (bool isActive) of branch bridge agent on the Root chain end as well. Not related to this POC but good to be aware of.
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L195
2. Now although bridge agent has been deactivated from BranchPort's state, it can still be used for no deposit calls/communication when calling function callOut(). This is because in the function callOut(), there is no check to see if it the current BranchBridgeAgent (that is deactivated) is active in the BranchPort through the modifier requiresBridgeAgent(). Now let's see how a call can occur through a deactivated branch bridge agent.
3. User calls callOut() to communicate through the branch BridgeAgent (that was deactivated):
4. Here is the execution path of the call from callOut() function in BaseBranchRouter contract:
Branch chain to EP: callOut() => callOut (branch bridge agent - that was deactivated) => _performCall => send
EP to Root chain: receivePayload => lzReceive => lzReceiveNonBlocking => _execute => executeNoDeposit => execute (Router) => logic continues based on custom Router's implementation
There are few important points to note here:
Tools Used
Manual Review
Recommended Mitigation Steps
The issues here to solve are:
Solution for callOut() function (check added on Line 202 - same can be applied for callOutSigned()):
Solution for point 2: In user's branch bridge agent, the following check should be added in Func ID 0x00 No deposit if block in the lzReceiveNonBlockingFunction.
Check added on Line 602:
In case of the Maia administration contracts, the calls from the root chain CoreRootRouterAddress-CoreRootBridgeagent pair should not be stopped. Therefore, in the administration's corresponding CoreBranchBridgeAgent contract, do not add the above check in order to allow no deposit calls to go through.
This way when the branch bridge agent is inactive, only the Maia Administration CoreRootRouter can continue execution.
Assessed type
Error