Open sherlock-admin3 opened 6 months ago
Low/Informational/ It's not a required feature. This allows you to airdrop some native tokens to a destination
The protocol team fixed this issue in PR/commit https://github.com/Tapioca-DAO/TapiocaZ/pull/177.
Escalate
The sponsor's comment is misleading. He is referencing the dstNativeAmount
and dstNativeAddr
which can be left as 0, as this functionality is not needed.
The whole report talks about the dstGasForCall
that is hardcoded to 0. This will lead to sgReceive
not being called and open up the possibility of a DoS attack vector on the receiving side.
The report also highlights the gas differences between chains and the importance of properly setting dstGasForCall
per destination chain. Recommendations were implemented in the Tapioca-DAO/TapiocaZ#177.
Based on all the arguments this should be a valid medium severity issue.
Escalate
The sponsor's comment is misleading. He is referencing the
dstNativeAmount
anddstNativeAddr
which can be left as 0, as this functionality is not needed. The whole report talks about thedstGasForCall
that is hardcoded to 0. This will lead tosgReceive
not being called and open up the possibility of a DoS attack vector on the receiving side. The report also highlights the gas differences between chains and the importance of properly settingdstGasForCall
per destination chain. Recommendations were implemented in the Tapioca-DAO/TapiocaZ#177. Based on all the arguments this should be a valid medium severity issue.
You've created a valid escalation!
To remove the escalation from consideration: Delete your comment.
You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.
Hardcoding zero gas actually triggers a revert on the same chain down the logic in RelayerV2::_getPrices
// decoding the _adapterParameters - reverts if type 2 and there is no dstNativeAddress
require(_adapterParameters.length == 34 || _adapterParameters.length > 66, "Relayer: wrong _adapterParameters size");
uint16 txType;
uint extraGas;
assembly {
txType := mload(add(_adapterParameters, 2))
extraGas := mload(add(_adapterParameters, 34))
}
require(extraGas > 0, "Relayer: gas too low");
The call never gets delivered to the destination chain to introduce the said DOS
This call does get delivered to the destination chain. The dstGasForCall
is the gas passed to the sgReceive
, and the base gas is a configuration inside Stargate:
function _txParamBuilder(
uint16 _chainId,
uint8 _type,
IStargateRouter.lzTxObj memory _lzTxParams
) internal view returns (bytes memory) {
bytes memory lzTxParam;
address dstNativeAddr;
{
bytes memory dstNativeAddrBytes = _lzTxParams.dstNativeAddr;
assembly {
dstNativeAddr := mload(add(dstNativeAddrBytes, 20))
}
}
>>> uint256 totalGas = gasLookup[_chainId][_type].add(_lzTxParams.dstGasForCall);
if (_lzTxParams.dstNativeAmount > 0 && dstNativeAddr != address(0x0)) {
>>> lzTxParam = txParamBuilderType2(totalGas, _lzTxParams.dstNativeAmount, _lzTxParams.dstNativeAddr);
} else {
>>> lzTxParam = txParamBuilderType1(totalGas);
}
return lzTxParam;
}
You can see that dstGasForCall
is simply added to the totalGas. In other words, Stargate will always deliver the message but if you hardcode dstGasForCall
to 0 sgReceive
will revert.
Setting dstGasForCall
to 0 would be a strange configuration parameter if it would disallow sending messages.
You are right. Great find 🫡
@windhustler should we also use the set gas receiver stuff on the following PR, correct?: https://github.com/Tapioca-DAO/TapiocaZ/pull/174/files
Hey, yes you need to use the appropriate dstGasForCall
here as well. You can reuse the _sgReceiveGas
value from here: https://github.com/Tapioca-DAO/TapiocaZ/pull/177/files.
While it is a valid report, the main root is hardcoded dstGasCall
to 0. And the same root vulnerability was found in the "Pashov Audit Group" audit - H-07.
According to Sherlock's rules, this makes the report invalid.
@nevillehuang what do you think?
@cvetanovv I've just checked H-07. It has several false/inaccurate claims which is probably the reason why Tapioca team hasn't fixed this:
It claims if dstGasForCall == 0
, a fee will be charged based on the default 200k gas, i.e. sgRecieve
on the destination will be called with 200k gas. This is not correct, see https://github.com/sherlock-audit/2024-02-tapioca-judging/issues/72#issuecomment-2034713451. The 200k gas is the default value if you are sending layer zero messages with gas set to 0. Using Stargate is different. dstGasForCall
is exclusively used as gas passed for sgReceive
.
Based on the false assumptions it derives the impact of underpaying/overpaying Stargate fees which is quite different than the impact this report claims.
It doesn't make the distinction between setting different gas configurations for different chains.
After doing some research I agree with @windhustler comment. So, I plan to accept the escalation and make the issue a valid Medium.
I think I agree with medium severity, unless @cryptotechmaker would like to clarify the above comment here
Result: Medium Unique
GiuseppeDeLaZara
medium
Gas parameters for Stargate swap are hardcoded leading to stuck messages
Summary
The
dstGasForCall
for transferring erc20s through Stargate is hardcoded to 0 in theBalancer
contract leading tosgReceive
not being called during Stargate swap. As a consequence, thesgReceive
has to be manually called to clear thecachedSwapLookup
mapping, but this can be DoSed due to the fact that themTOFT::sgReceive
doesn't validate any of its parameters. This can be exploited to perform a long-term DoS attack.Vulnerability Detail
Gas parameters for Stargate
Stargate Swap allows the caller to specify the:
dstGasForCall
which is the gas amount forwarded while calling thesgReceive
on the destination contract.dstNativeAmount
anddstNativeAddr
which is the amount and address where the native token is sent to.Inside the
Balancer.sol
contract, thedstGasForCall
is hardcoded to 0. ThedstGasForCall
gets forwarded from StargateRouter
into the StargateBridge
contract.It gets encoded inside the payload that is sent through the LayerZero message. The payload gets decoded inside the
Bridge::lzReceive
on destination chain. AnddstGasForCall
is forwarded to thesgReceive
function:If it is zero like in the
Balancer.sol
contract or its value is too small thesgReceive
will fail, but the payload will be saved in thecachedSwapLookup
mapping. At the same time the tokens are transferred to the destination contract, which is themTOFT
. Now anyone can call thesgReceive
manually through theclearCachedSwap
function:Although not the intended behavior there seems to be no issue with erc20 token sitting on the
mTOFT
contract for a shorter period of time.sgReceive
This leads to the second issue. The
sgReceive
function interface specifies thechainId
,srcAddress
, andtoken
.chainId
is the layerZero chainId of the source chain. In their docs referred to endpointId: https://layerzero.gitbook.io/docs/technical-reference/mainnet/supported-chain-idssrcAddress
is the address of the source sending contracttoken
is the address of the token that was sent to the destination contract.In the current implementation, the
sgReceive
function doesn't check any of these parameters. In practice this means that anyone can specify themTOFT
address as the receiver and initiate Stargate Swap from any chain to themTOFT
contract.In conjunction with the first issue, this opens up the possibility of a DoS attack.
Let's imagine the following scenario:
mTOFT
on Ethereum and Avalanche that holdUSDC
as the underlying token.sgReceive
on Avalanche fails and 1000 USDCs are sitting onmTOFT
contract on Avalanche.mTOFT
contract as the receiver.mTOFT
has 1USDT
but 999USDC
as the griever's transaction has called thesgRecieve
function that pushed 1 USDC to theTOFTVault
.clearCachedSwap
function fails because it tries to transfer the original 1000 USDC.mTOFT
contract and try calling theclearCachedSwap
again.Impact
Hardcoding the
dstGasCall
to 0 in conjuction with not checking thesgReceive
parameters opens up the possibility of a long-term DoS attack.Code Snippet
Tool used
Manual Review
Recommendation
The
dstGasForCall
shouldn't be hardcoded to 0. It should be a configurable value that is set by the admin of theBalancer
contract.Take into account that this value will be different for different chains.
For instance, Arbitrum has a different gas model than Ethereum due to its specific precompiles: https://docs.arbitrum.io/arbos/gas.
The recommended solution is: