The USDOLeverageModule contract is a module that is used by the BaseUSDO contract to facilitate functionality for leverage actions. The module functionality is invoked through the invocation of a delegatecall within the BaseUSDO contract’s _executeModule function. Additionally, the BaseUSDO contract facilitates the functionality to execute a market ‘leverage up’ action on the USDOLeverageModule contract of a destination block chain. The function encodes the selector for the USDOLeverageModule contract’s leverageUp function as part of the call data so that the function is invoked once the message has been received by the BaseUSDO contract implementation on the destination block chain. However, the leverageUp function’s delegatecall is problematic because the function invokes a delegatecall on any address that is defined as the module parameter without restriction. Because this is also a public function, a malicious user can pass the address for a contract they have deployed that has functionality with-in it to manipulate the contract’s state. For example, this module contract inherits from the LzApp contract which inherits Open Zeppelin’s Ownable contract. This means that a contract can be deployed that includes a function with the correct signature with functionality that will update the owner to another address which will then be invoked when the respective contract’s address is defined as the module parameter for the leverageUp function invocation. This would then allow for a malicious user to have complete authority over the module’s owner functions. Alternatively, the contract’s function implementation can have functionality that will cause selfdestruct to be invoked on the module when the call is delegated to it causing the module’s bytecode to be deleted from the address. This would be extremely problematic for the protocol because all calls from the BaseUSDO contract to the respective implementation of the module will fail because there is no way to update the address for the module’s implementation after the BaseUSDO contract has been deployed. This would have the effect of causing DOS for the respective USDO implementation’s leverage functionality which will directly impact user funds and the general functionality of the protocol.
The leverageUp function accepts any address as it’s module parameter which then has a call delegated to it with the only restriction being that the address must have a function with the correct signature and the call must be successful. A malicious user can use this to then cause functionality to be invoked that will update their address to be the owner of the module, or to invoke selfdestruct on the module which will cause a DOS for the USDO implementation’s leverage functionality.
Manual review
It is recommended to refactor the USDOLeverageModule contract to have functionality that will allow for specific addresses to be approved for the call delegation. This can be done by implementing a state variable mapping with an associated access controlled function to approve which addresses are allowed for the delegatecall:
/// @audit recommended mitigation:
/// mapping state variable with associated
/// function with access control allowing
/// module addresses to be approved for call
/// delegation
mapping(address => bool) public isApproved;
function setModuleStatus(address _module, bool _status) external onlyOwner {
isApproved[_module] = _status;
}
Additionally, the leverageUp function should then be refactored to have an assertion that checks if the defined module parameter has been approved and revert with a custom error if not:
function leverageUp(
address module,
uint16 _srcChainId,
bytes memory _srcAddress,
uint64 _nonce,
bytes memory _payload
) public {
/// @audit recommended mitigation
if (!isApproved[module]) revert ModuleNotApproved();
(
,
,
uint256 amount,
IUSDOBase.ILeverageSwapData memory swapData,
IUSDOBase.ILeverageExternalContractsData memory externalData,
IUSDOBase.ILeverageLZData memory lzData,
address leverageFor
) = abi.decode(
_payload,
(
uint16,
bytes32,
uint256,
IUSDOBase.ILeverageSwapData,
IUSDOBase.ILeverageExternalContractsData,
IUSDOBase.ILeverageLZData,
address
)
);
uint256 balanceBefore = balanceOf(address(this));
bool credited = creditedPackets[_srcChainId][_srcAddress][_nonce];
if (!credited) {
_creditTo(_srcChainId, address(this), amount);
creditedPackets[_srcChainId][_srcAddress][_nonce] = true;
}
uint256 balanceAfter = balanceOf(address(this));
(bool success, bytes memory reason) = module.delegatecall(
abi.encodeWithSelector(
this.leverageUpInternal.selector,
amount,
swapData,
externalData,
lzData,
leverageFor
)
);
if (!success) {
if (balanceAfter - balanceBefore >= amount) {
IERC20(address(this)).safeTransfer(leverageFor, amount);
}
revert(_getRevertMsg(reason)); //forward revert because it's handled by the main executor
}
emit ReceiveFromChain(_srcChainId, leverageFor, amount);
}
call/delegatecall
The text was updated successfully, but these errors were encountered:
All reactions