xanoutas/solidity-security-findings huggingface dataset:
Unsanitized Lz0 Message Data allows for impersonation in `mintBBLendxChainSGL` and `depositYBLendSGLLockXchainTOLP`
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarMintXChainModule.sol#L57-L76
https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarAssetXChainModule.sol#L89-L94


# Vulnerability details


### Impact
`mintBBLendXChainSGL` and `depositYBLendSGLLockXchainTOLP` check the `data.user` but fail to validate the `lendData.user` allowing for impersonation and stealing of assets of other users that granted allowance to `Magnetar`

The explanation uses `mintBBLendXChainSGL` but the logic is the same for `depositYBLendSGLLockXchainTOLP`.

The code is as follows:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarMintXChainModule.sol#L57-L76

```solidity
    function mintBBLendXChainSGL(CrossChainMintFromBBAndLendOnSGLData memory data) public payable {
        // Check sender
        _checkSender(data.user);

        address yieldBox = IMarket(data.bigBang).yieldBox();

        // if `mint` was requested the following actions are performed:
        //  - extracts & deposits collateral to YB
        //  - performs bigBang_.addCollateral
        //  - performs bigBang_.borrow
        if (data.mintData.mint) {
            _depositYBBorrowBB(data.mintData, data.bigBang, IYieldBox(yieldBox), data.user, data.marketHelper);
        }

        // decode `composeMsg` and re-encode it with updated params
        (uint16 msgType_,, uint16 msgIndex_, bytes memory tapComposeMsg_, bytes memory nextMsg_) =
            TapiocaOmnichainEngineCodec.decodeToeComposeMsg(data.lendSendParams.lzParams.sendParam.composeMsg);
        /// @audit `lendData.user` is not `_checkSender`
        DepositAndSendForLockingData memory lendData = abi.decode(tapComposeMsg_, (DepositAndSendForLockingData));
        lendData.lendAmount = data.mintData.mintAmount;
```

As shown above `data.user` is sanitized, however `data.lendSendParams.lzParams.sendParam.composeMsg` will generate `tapComposeMsg_`, which will be `decoded` into `lendData`

`lendData` will look as follows:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/interfaces/periph/IMagnetar.sol#L119-L127

```solidity
struct DepositAndSendForLockingData {
    address user;
    address singularity;
    address magnetar;
    uint256 assetId; // Singularity receipt token id
    uint256 lendAmount;
    IDepositData depositData;
    LendOrLockSendParams lockAndParticipateSendParams;
}
```

`lendData.user` is not `data.user`

Meaning we can pass an arbitrary address and perform operations on their behalf


### Additional Instances

The same logic applies to `MagnetarAssetXChainModule`

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarAssetXChainModule.sol#L89-L94

```solidity
Incorrect Math means `data.removeAndRepayData.removeAssetFromSGL` will never work once SGL has accrued interest
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarOptionModule.sol#L158-L159


# Vulnerability details

### Impact
The code to remove shares from Singularity is as folllows:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarOptionModule.sol#L158-L159

```solidity
            singularity_.removeAsset(data.user, removeAssetTo, share);
```

Where `share` is computed in this way:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarOptionModule.sol#L153

```solidity
uint256 share = yieldBox_.toShare(_assetId, _removeAmount, false);
```

The line is calculating: The (incorrectly rounded down) amount of shares of Yieldbox to burn in order to withdraw from Yieldbox the `_removeAmount`

But the code is calling:

`singularity_.removeAsset(data.user, removeAssetTo, share);`

This is asking Singularity to remove a % (part) of the total assets in Singularity.

Due to this the line will stop working as soon as singularity has had any operation that generated interest

### Proof of Concept

Please see the formula used by Singularity for pricing asset:

https://github.com/Tapioca-DAO/Tapioca-bar/blob/c2031ac2e2667ac8f9ac48eaedae3dd52abef559/contracts/markets/singularity/SGLCommon.sol#L199-L216

```solidity
    function _removeAsset(address from, address to, uint256 fraction) internal returns (uint256 share) {
        if (totalAsset.base == 0) {
            return 0;
        }
        Rebase memory _totalAsset = totalAsset;
        uint256 allShare = _totalAsset.elastic + yieldBox.toShare(assetId, totalBorrow.elastic, false);
        share = (fraction * allShare) / _totalAsset.base;
    }
```

As you can see, the `fraction` will be computed against `_totalAsset.elastic + yieldBox.toShare(assetId, totalBorrow.elastic, false);`

Meaning that the math will be incorrect as soon as any operation is done in Singularity

### Coded POC

This Poc is built on the public repo:
https://github.com/GalloDaSballo/yieldbox-foundry


We show how a change in interest will change `fraction`

In my local testing, `fraction` and `shares` are already out of synch

However, due to decimals it may be possible for them to be the same value, until some interest will make `borrowElastic` grow

### Logs
[PASS] testSingularityRebasedMathIsNotYieldbox() (gas: 34810)
Logs:
  fraction 999999990000000099999
  share 100000000000000000000000000000

[PASS] testSingularityRebasedMathIsNotYieldboxAfterInterest() (gas: 34756)
Logs:
  fraction 666666662222222251851
  share 100000000000000000000000000000

### Code

```solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {TargetFunctions} from "./TargetFunctions.sol";
import {FoundryAsserts} fro
`_claimTwpTapRewardsReceiver` uses spot `claimable`, which is subject to front-run griefing
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L547-L551
https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L152-L156
https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L192


# Vulnerability details

### Impact
The logic for `TapTokenReceiver` to claim `twTap` rewards is as follows:

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L152-L156

```solidity
    function _claimTwpTapRewardsReceiver(bytes memory _data) internal virtual twTapExists {
        ClaimTwTapRewardsMsg memory claimTwTapRewardsMsg_ = TapTokenCodec.decodeClaimTwTapRewardsMsg(_data);

        // Claim rewards, make sure to have approved this contract on TwTap. | /// @audit Stealing 100% of rewards?
        uint256[] memory claimedAmount_ = twTap.claimRewards(claimTwTapRewardsMsg_.tokenId, address(this));
```

Which will ultimate return the value from [`twTap.claimable`](https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L192)

Which is equal to the cumulative amount minus what was already claimed:

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L260-L261

```solidity
            result[i] = ((votes * net) / DIST_PRECISION) - claimed[_tokenId][i]; 
```

`requireClaimPermissions` doesn't check that the `msg.sender` is approved

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L547-L551

```solidity
    function _requireClaimPermission(address _to, uint256 _tokenId) internal view { /// @audit This is actually wrong
        if (!_isApprovedOrOwner(_to, _tokenId) && !isERC721Approved(_ownerOf(_tokenId), _to, address(this), _tokenId)) {
            revert NotApproved(_tokenId, msg.sender); /// @audit This is incorrect
        }
    }
```

This means that anyone can claim on behalf of the xChain sender, making `twTap.claimable` return 0 or low amounts

These tokens will be left in TapTokenReceiver, unable to be claimed

### POC

- Victim performs a xChain call to claim twTap rewards
- Attacker claims on their behalf to `TapToken`
- Since allowance was already set (or can be front-run), `_requireClaimPermission` passes
- The rewards are sent to `TapToken` and the Victim lost them
- The Victims message will claim 0 or a small amount of rewards since [`claimed[_tokenId][i]`](https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L565) was incremented by the attacker


### Mitigation

The fundamental issue for this is that `TapTokenReceiver` is made able to claim tokens for all and that there's lack of validation for those c
`exitPosition` allows to exit to self and steal funds due to lack of msg.sender / ownership checks
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L293-L314


# Vulnerability details

### Impact
Magnetar allows calling `TapiocaOptionBroker.exitPosition`, but there's no check to verify that the position belongs to the caller

This allows any attacker to exit positions that are approved to Magnetar and Sweep the underlying tokens to themselves

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L293-L314

```solidity

    function _processTapTokenOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);

        bytes4 funcSig = bytes4(_actionCalldata[:4]);
        if (
            funcSig == ITapiocaOptionBroker.exerciseOption.selector
                || funcSig == ITapiocaOptionBroker.participate.selector
                || funcSig == ITapiocaOptionBroker.exitPosition.selector /// @audit Can exit to self and steal funds
                || funcSig == ITapiocaOptionLiquidityProvision.lock.selector
                || funcSig == ITapiocaOptionLiquidityProvision.unlock.selector
        ) {
            _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
            return;
        }
        revert Magnetar_ActionNotValid(MagnetarAction.TapToken, _actionCalldata);
    }
```

Any position that was allowed to be managed by Magnetar can be exited by magnetar

Meaning that an attacker could simply be the first to exit and then sweep all funds off of Magnetar

Leaving the original depositors with nothing

This is due to a lack of check for ownership on the TOB Position, which combined with the claimPermission changes, allows us to steal the tokens as any random caller

### POC

- Call `exitPosition` on any lock that has given approval to Magnetar
- Sweep the position to self


### Mitigation

twTAP and Magnetar have had multiple integration bugs that are mostly tied to the ability of Magnetar (a singleton) to claim on behalf of multiple actors

In my opinion this is a  fundamental issue with this architeture as it tends to cause issues when simply checking for permissions

If you wish to maintain a similar set of permission checks, you would be forced to deploy one Magnetar per user, as a sort of a macro

Alternatively, you'll have to change the claim permissions to be more granular and not front-runnable (permit will not work)


## Assessed type

Invalid Validation
Magnetar Calling arbitrary approved target means calling self - Calling self allows impersonation
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L325-L333


# Vulnerability details

### Impact
Magnetar has added the ability of performing arbitrary calls

The only protection is that the contract must be whitelisted by `Cluster`

It stands to reason that Magnetar itself will be whitelisted since `Cluster` is a singleton

We can see that the only validation is for `cluster.isWhitelisted`

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L325-L333

```solidity
    function _processOFTOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);
        _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
    }
```


meaning we can call Magnetar since it will be whitelisted, in `_executeCall`

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L338-L353

```solidity
function _executeCall(address _target, bytes calldata _actionCalldata, uint256 _actionValue, bool _allowFailure)
        private
    {
        bool success;
        bytes memory returnData;

        if (_actionValue > 0) { /// @audit Arbitrary Execution, we can steal everything here
            (success, returnData) = _target.call{value: _actionValue}(_actionCalldata);
        } else {
            (success, returnData) = _target.call(_actionCalldata);
        }

        if (!success && !_allowFailure) {
            _getRevertMsg(returnData);
        }
    }
```

Due to the above, We can perform an OFTOperation with Magnetar as the target, and we can simply call `burst` as Magnetar itself, allowing us to execute any arbitrary operation as authorized.

Effectively sweeping any token, claiming all rewards, etc..

### POC

- Magnetar calls self with arbitrary payload
- All checks on all calls pass since Magnetar is approved on the Cluster
- We can now perform any arbitrary call as if we are Magnetar

This, in conjunction with the arbitrary external calls, allows sidestepping and impersonating every user in Magnetar


### Mitigation

This is one instance of a broader issues that I don't believe we have been given sufficient information to fully investigate

In order for whitehats to fully investigate the possible set of arbitrary combinations, we'd need the full list of all addresses that are whitelisted.

As any single address allowing for ACE will allow stealing all funds from Magnetar, even if the arbitrary call shown in this instance where to be patched.

### Immediate Suggested Mitigation

I don't believe we have full information to suggest a safe remediation that will work 100% of the time

The first step towards a safe remediation is to change to be:
Arbitrary operation allowed = Sweep and Steal all tokens from any approved
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L325-L353


# Vulnerability details

### Impact
`_processOFTOperation` in Magnetar only verifies that the `_target` is whitelisted

An example would be `sgl` or `yieldbox`

The `_actionCalldata` allows us to specify any arbitrary data

Due to this we are able to `transferFrom` tokens from any victim that granted allowance, and transfer those tokens away

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L325-L353

```solidity
    function _processOFTOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);
        _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
    }

    /**
     * @dev Executes a call to an address, optionally reverting on failure. Make sure to sanitize prior to calling.
     */
    function _executeCall(address _target, bytes calldata _actionCalldata, uint256 _actionValue, bool _allowFailure)
        private
    {
        bool success;
        bytes memory returnData;

        if (_actionValue > 0) { /// @audit Arbitrary Execution, we can steal everything here
            (success, returnData) = _target.call{value: _actionValue}(_actionCalldata);
        } else {
            (success, returnData) = _target.call(_actionCalldata);
        }

        if (!success && !_allowFailure) {
            _getRevertMsg(returnData);
        }
    }
```

### POC

- Simply call `_processOFTOperation` and `transferFrom` tokens that are approved
- Note that due to race conditions on `permit` even temporary allowances could be used to steal funds

### Mitigation

Magnetar cannot perform arbitrary execution and receive allowances

Separate allowances from arbitrary execution

Or alternatively have a separate Magnetar per user, this is a similar issue to the one we had to solve for LeverageMacros for eBTC: https://github.com/ebtc-protocol/ebtc/blob/release-0.7/packages/contracts/contracts/LeverageMacroBase.sol

Each user must deploy a separate macro, as the ability of:
- Having allowances comingled
- Arbitrary Code Execution

Creates too many variables to make it safe

Separating each by user makes it safer and less likely to have these issues


## Assessed type

Invalid Validation
`IMarket.execute.selector`, `_checkSender` bypass allows to execute arbitrary operations
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L256-L281


# Vulnerability details


### Summary
Because of an incorrect interpretation of `calldata` for the `execute` signature

We are able to bypass the `_checkSender` and perform arbitrary `execute` operations as Magnetar

### Impact
Market.execute uses the following signature:
```solidity
    function execute(Module[] calldata modules, bytes[] calldata calls, bool revertOnFail)
```

For Calldata variables, the size `4:36` is going to be the `length` of the `calldata`

We can specify an arbitrary length that matches the value of any address that is whitelisted, or any address that we're able to generate, this will allow us to bypass the check, and perform arbitrary execution in the market

After forging our length, we have bypassed the check, allowing us to execute, while having permissions / allowances from other users

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/Magnetar.sol#L256-L281

```solidity

    function _processMarketOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);

        /// @dev owner address should always be first param.
        // addCollateral(address from,...)
        // borrow(address from,...)
        // addAsset(address from,...)
        // repay(address _from,...)
        // buyCollateral(address from,...)
        // sellCollateral(address from,...)
        bytes4 funcSig = bytes4(_actionCalldata[:4]);
        if (
            funcSig == IMarket.execute.selector || funcSig == ISingularity.addAsset.selector /// @audit ??????
                || funcSig == ISingularity.removeAsset.selector
        ) {
            /// @dev Owner param check. See Warning above.
            _checkSender(abi.decode(_actionCalldata[4:36], (address))); /// @audit we can forge this 80%
            _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
            return;
        }
```

### POC
This will allow us to transfer any token that is approved to us, stealing them

We can proceed to mine an address with low enough zeros, or simply use a suitable system address for any chain, as the requirement for `_checkSender(abi.decode(_actionCalldata[4:36], (address)))` is simply to match the `msg.sender`

An example would be the Optimism Portal on L2 which would have very low address, meaning we would be able to have a sufficient amount of operations there

Once we can bypass the check, we will be able to have Magnetar `execute` any market operation such as transfers of other people tokens, allowing us to steal them

### Coded POC

The Coded POC shows how we can forge calldata to bypass the check, as long as we can use an address that matc
Funds can be stolen through remote transfer functionality
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L224


# Vulnerability details

## Proof of Concept
User can send LZ message  through any Oft token using TapiocaOmnichainSender.sendPacket function. User provides params that should be used and also provides [composed message](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainSender.sol#L57C75-L57C86) if he needs to send it. 
What is important for composed message is [during crafting message](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/BaseTapiocaOmnichainEngine.sol#L166-L172), `msg.sender` is stored as `srcChainSender_`. In this way we know who have triggered composed call.
```solidity
function encode(
        bytes32 _sendTo,
        uint64 _amountShared,
        bytes memory _composeMsg
    ) internal view returns (bytes memory _msg, bool hasCompose) {
        hasCompose = _composeMsg.length > 0;
        // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src.
        _msg = hasCompose
            ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg)
            : abi.encodePacked(_sendTo, _amountShared);
    }
```
Then amount that should be sent to other chain is burnt(if any) and LZ call [is sent](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainSender.sol#L73-L74).

On another chain the call will be handled [by `TapiocaOmnichainReceiver._lzReceive` function](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L72C14-L72C24). This function [will mint tokens to recipient](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L85) and then in case if composed message was included, then it will [sent it to endpoint](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L93-L98), so it can be triggered later.

When composed message is triggered, then [`lzCompose` function](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L120C14-L120C23) handles it. As you can see function retrieves `srcChainSender_` to know who was initiator of compose call on source chain. Then [`_lzCompose` function](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L139) conti