Open c4-bot-5 opened 4 months ago
cryptotechmaker marked the issue as disagree with severity
Low/Invalid; even if the market helper is not checked (and I agree it's ok to add that verification) the module which is going to be executed is checked on the BB/SGL side and the action that's being performed also checks the allowances
dmvt marked the issue as unsatisfactory: Invalid
Agree with the sponsor on this one. Arguably this could be invalidated for overinflated severity. The check suggested is fair but I can't reasonably leave this in place when submitted as a high.
dmvt marked the issue as primary issue
Emm this report is one of the early report that marked as invalid,
but I think the severity is not inflated and the severity is high and the issue clearly leads to theft of fund.
/// @dev Modules will not return result data.
if (_action.id == MagnetarAction.OptionModule) {
_executeModule(MagnetarModule.OptionModule, _action.call);
continue; // skip the rest of the loop
}
user needs to give a lot of approve for magnatar contract to allow magnater contract pull fund out of user's account to complete transaction.
to prevent abuse of allowance, this check is made in-place
function exitPositionAndRemoveCollateral(ExitPositionAndRemoveCollateralData memory data) public payable {
// Check sender
_checkSender(data.user);
which calls:
function _checkSender(address _from) internal view {
if (_from != msg.sender && !cluster.isWhitelisted(0, msg.sender)) {
revert Magnetar_NotAuthorized(msg.sender, _from);
}
}
the from != msg.sender is super important, otherwise,
if user A gives allowance to magnetar contract, user B can set data.user to user A and steal fund from user A directly.
(Module[] memory modules, bytes[] memory calls) = IMarketHelper(data.externalData.marketHelper).repay(
address(this), data.user, false, data.removeAndRepayData.repayAmount
);
(bool[] memory successes, bytes[] memory results) = bigBang_.execute(modules, calls, true);
As for sponsor comments:
the module which is going to be executed is checked on the BB/SGL side and the action that's being performed also checks the allowances
this is the code in BBCollateral module
bigBang_.execute multilcall to bigBang module and one of the module is BBCollateral module
function removeCollateral(address from, address to, uint256 share)
external
optionNotPaused(PauseType.RemoveCollateral)
solvent(from, false)
notSelf(to)
allowedBorrow(from, share)
{
_removeCollateral(from, to, share);
}
the validation that sponsor mentions is in the modifier
allowedBorrow(from, share)
which calls:
function _allowedBorrow(address from, uint256 share) internal virtual override {
if (from != msg.sender) {
// TODO review risk of using this
(uint256 pearlmitAllowed,) = penrose.pearlmit().allowance(from, msg.sender, address(yieldBox), collateralId);
require(allowanceBorrow[from][msg.sender] >= share || pearlmitAllowed >= share, "Market: not approved");
if (allowanceBorrow[from][msg.sender] != type(uint256).max) {
allowanceBorrow[from][msg.sender] -= share;
}
}
}
obviously "from" is not msg.sender but msg.sender is the magnatar contract that hold user's allowance.
https://github.com/Tapioca-DAO/TapiocaZ/pull/180/files
the exact same issue should be fixed in Option module as well.
https://github.com/code-423n4/2024-02-tapioca-findings/issues/100
https://hacken.io/discover/sushi-hack-explained/
this type of exploit can occurs
and in this case.
This should be valid. According to the sponsor, even if the market helper is not checked the module which is going to be executed is checked on the BB/SGL side
.
This is true. However the bigbang/sgl markets do the check on msg.sender
, which is the magnetar contract itself, which is expected to have allowance from the users. Checks are not done on the initiator of this transaction. This is highlighted below.
function _allowedBorrow(address from, uint256 share) internal virtual override {
if (from != msg.sender) {
if (share == 0) revert AllowanceNotValid();
// TODO review risk of using this
(uint256 pearlmitAllowed,) = penrose.pearlmit().allowance(from, msg.sender, address(yieldBox), collateralId);
require(allowanceBorrow[from][msg.sender] >= share || pearlmitAllowed >= share, "Market: not approved");
if (allowanceBorrow[from][msg.sender] != type(uint256).max) {
allowanceBorrow[from][msg.sender] -= share;
}
}
}
Magnetar is a privileged contract, and this function allows other users to abuse this privilege. This is basically approval hijacking, and so is high severity.
@dmvt this can be approved as a high risk.
While we switched the model to use "atomic" approvals using Pearlmit, it's better to be safe than sorry. The reviewed code also still has an obsolete allowanceBorrow
which could help initiate this attack.
dmvt removed the grade
dmvt marked the issue as selected for report
For transparency, the sponsor (0xWeiss) confirmed this finding outside of Github. C4 staff have added the applicable label on Tapioca's behalf.
Lines of code
https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarOptionModule.sol#L173-L199
Vulnerability details
Impact
The
MagnetarOptionModule
contract implements theexitPositionAndRemoveCollateral
function which allows users to do a series of operations which is irrelevant to the issue. The user passes in the variabledata
, and later,data.externalData
is used to extract out relevant contract addresses. These are then checked against a whitelist.The main issue is that the
data.externalData
also has amarketHelper
field which is not checked against a whitelist and ends up being used.The helper contracts are used to construct the calldata for market operations. In the above snippet, the helper contract is passed in some data, and it is expected to create a calldata out of the passed in data. The expected output is the repay module and a
call
value which when executed, will repay for thedata.user
's account.However, since the
marketHelper
contract is never checked against a whitelist, malicious user can pass in any address in that place. So the above call can return any data payload, and thebigBang_.execute
will execute it without any checks. This means the malicious helper contract can return aborrow
payload of some random user, and the contract will end up borrowing USDO against that user's position. The Magnetar contract is assumed to have approval for market operations, and thus the Magnetar's approval is essentially exploited by the attacker to perform arbitrary actions on any user's account.This can be used by any user to steal collateral from other user's bigbang position, or borrow out usdo tokens on their position. Since this is direct theft, this is a high severity issue.
Proof of Concept
The absence of checks is evident from the code snippet. Assuming
marketHelper
contract is malicious, we see that is used in 2 places to create payloads, which must also be deemed malicious.These are then executed, and the Magnetar is assumed to have approvals from users, so these are obviously malicious interactions.
In the other module contracts, the
marketHelper
is checked against a whitelist, but not in this module. This is a clear oversight. Below is the example from the MagnetarMintCommonModule.Tools Used
Manual Review
Recommended Mitigation Steps
Check the helper contract against a whitelist
Assessed type
Invalid Validation