From dd8fc1a081cc96935c84a0b4db6c73adb55d33b8 Mon Sep 17 00:00:00 2001 From: raz0r Date: Thu, 18 Jul 2024 18:36:17 +0300 Subject: [PATCH] arbitary-low-level-call: custom type as source --- .../security/arbitrary-low-level-call.sol | 211 +++++++++++++++++- .../security/arbitrary-low-level-call.yaml | 6 + 2 files changed, 216 insertions(+), 1 deletion(-) diff --git a/solidity/security/arbitrary-low-level-call.sol b/solidity/security/arbitrary-low-level-call.sol index 72bf10d..be1fe66 100644 --- a/solidity/security/arbitrary-low-level-call.sol +++ b/solidity/security/arbitrary-low-level-call.sol @@ -675,4 +675,213 @@ contract DistributorTreasury is Ownable { receive() external payable {} } - + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ILiFi } from "../Interfaces/ILiFi.sol"; +import { LibSwap } from "./LibSwap.sol"; +import { LibAsset } from "../Libraries/LibAsset.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { SwapperV2 } from "../Helpers/SwapperV2.sol"; +import { Validatable } from "../Helpers/Validatable.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ERC20 } from "solady/tokens/ERC20.sol"; +import { NativeAssetTransferFailed, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +interface IGasZip { + function deposit( + uint256 destinationChains, + address recipient + ) external payable; +} + +/// @title GasZipFacet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality to swap ERC20 tokens to native and deposit them to the gas.zip protocol (https://www.gas.zip/) +/// @custom:version 1.0.0 +contract GasZipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { + using SafeTransferLib for address; + + /// @dev GasZip-specific bridge data + /// @param gasZipChainId The Gas.zip-specific chainId of the chain on which gas should be received on (https://dev.gas.zip/gas/chain-support/outbound) + struct GasZipData { + uint256 gasZipChainId; + } + + /// State /// + IGasZip public immutable gasZipRouter; + + /// Constructor /// + constructor(address _gasZipRouter) { + gasZipRouter = IGasZip(_gasZipRouter); + } + + function swap(bytes32 transactionId, SwapData calldata _swap) internal { + if (!LibAsset.isContract(_swap.callTo)) revert InvalidContract(); + uint256 fromAmount = _swap.fromAmount; + if (fromAmount == 0) revert NoSwapFromZeroBalance(); + uint256 nativeValue = LibAsset.isNativeAsset(_swap.sendingAssetId) + ? _swap.fromAmount + : 0; + uint256 initialSendingAssetBalance = LibAsset.getOwnBalance( + _swap.sendingAssetId + ); + uint256 initialReceivingAssetBalance = LibAsset.getOwnBalance( + _swap.receivingAssetId + ); + + if (nativeValue == 0) { + LibAsset.maxApproveERC20( + IERC20(_swap.sendingAssetId), + _swap.approveTo, + _swap.fromAmount + ); + } + + if (initialSendingAssetBalance < _swap.fromAmount) { + revert InsufficientBalance( + _swap.fromAmount, + initialSendingAssetBalance + ); + } + + // solhint-disable-next-line avoid-low-level-calls + // ruleid: arbitrary-low-level-call + (bool success, bytes memory res) = _swap.callTo.call{ + value: nativeValue + }(_swap.callData); + if (!success) { + LibUtil.revertWith(res); + } + + uint256 newBalance = LibAsset.getOwnBalance(_swap.receivingAssetId); + + emit AssetSwapped( + transactionId, + _swap.callTo, + _swap.sendingAssetId, + _swap.receivingAssetId, + _swap.fromAmount, + newBalance > initialReceivingAssetBalance + ? newBalance - initialReceivingAssetBalance + : newBalance, + block.timestamp + ); + } + + /// @notice Bridges tokens using the gas.zip protocol + /// @dev this function only supports native flow. For ERC20 flows this facet should be used as a protocol step instead + /// @param _bridgeData The core information needed for bridging + /// @param _gasZipData GasZip-specific bridge data + function startBridgeTokensViaGasZip( + ILiFi.BridgeData memory _bridgeData, + GasZipData calldata _gasZipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + validateBridgeData(_bridgeData) + doesNotContainSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + { + // this function shall only be used for native assets + if (!LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) + revert InvalidCallData(); + + depositToGasZipNative( + _bridgeData.minAmount, + _gasZipData.gasZipChainId, + _bridgeData.receiver + ); + + emit LiFiTransferStarted(_bridgeData); + } + + /// @notice Performs a swap before bridging via the gas.zip protocol + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _gasZipData GasZip-specific bridge data + function swapAndStartBridgeTokensViaGasZip( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + GasZipData calldata _gasZipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + containsSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + validateBridgeData(_bridgeData) + { + // this function shall only be used for ERC20 assets + if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) + revert InvalidCallData(); + + // deposit and swap ERC20 tokens + _bridgeData.minAmount = _depositAndSwap( + _bridgeData.transactionId, + _bridgeData.minAmount, + _swapData, + payable(msg.sender) + ); + + // deposit to gas.zip + depositToGasZipNative( + _bridgeData.minAmount, + _gasZipData.gasZipChainId, + _bridgeData.receiver + ); + + emit LiFiTransferStarted(_bridgeData); + } + + /// @notice Swaps ERC20 tokens to native and deposits these native tokens in the GasZip router contract + /// @dev this function can be used as a LibSwap.SwapData protocol step to combine it with any other bridge + /// @param _swapData The swap data that executes the swap from ERC20 to native + /// @param _destinationChains A value that represents a list of chains to which gas should be distributed (see https://dev.gas.zip/gas/code-examples/deposit for more details) + /// @param _recipient The address to receive the gas on dst chain + function depositToGasZipERC20( + LibSwap.SwapData calldata _swapData, + uint256 _destinationChains, + address _recipient + ) public { + // get the current native balance + uint256 currentNativeBalance = address(this).balance; + + // execute the swapData that swaps the ERC20 token into native + // semgrep currently does not propagate tainted values into libraries + // original line was: LibSwap.swap(0, _swapData); + swap(0, _swapData); + + // calculate the swap output amount using the initial native balance + uint256 swapOutputAmount = address(this).balance - + currentNativeBalance; + + // call the gas zip router and deposit tokens + gasZipRouter.deposit{ value: swapOutputAmount }( + _destinationChains, + _recipient + ); + } + + /// @notice Deposits native tokens in the GasZip router contract + /// @dev this function can be used as a LibSwap.SwapData protocol step to combine it with any other bridge + /// @param _amountToZip The amount to be deposited to the protocol + /// @param _destinationChains a value that represents a list of chains to which gas should be distributed (see https://dev.gas.zip/gas/code-examples/deposit for more details) + /// @param _recipient the address to receive the gas on dst chain + function depositToGasZipNative( + uint256 _amountToZip, + uint256 _destinationChains, + address _recipient + ) public payable { + // call the gas zip router and deposit tokens + gasZipRouter.deposit{ value: _amountToZip }( + _destinationChains, + _recipient + ); + } +} + diff --git a/solidity/security/arbitrary-low-level-call.yaml b/solidity/security/arbitrary-low-level-call.yaml index 92465d0..bea86e9 100644 --- a/solidity/security/arbitrary-low-level-call.yaml +++ b/solidity/security/arbitrary-low-level-call.yaml @@ -19,14 +19,20 @@ rules: - https://blocksecteam.medium.com/li-fi-attack-a-cross-chain-bridge-vulnerability-no-its-due-to-unchecked-external-call-c31e7dadf60f - https://etherscan.io/address/0xe7597f774fd0a15a617894dc39d45a28b97afa4f # Auctus Options - https://etherscan.io/address/0x73a499e043b03fc047189ab1ba72eb595ff1fc8e # Li.Fi + - https://x.com/DecurityHQ/status/1813195945477087718 # Li.Fi hack #2 mode: taint pattern-sources: - pattern: function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) external { ... } - pattern: function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) public { ... } + - pattern: function $F(..., $TYPE calldata $DATA, ...) public { ... } + - pattern: function $F(..., $TYPE calldata $DATA, ...) external { ... } pattern-sinks: - pattern: $ADDR.call($DATA); - pattern: $ADDR.call{$VALUE:...}($DATA); - pattern: $ADDR.call{$VALUE:..., $GAS:...}($DATA); + - pattern: $DATA.$ADDR.call($DATA.$CALLDATA); + - pattern: $DATA.$ADDR.call{$VALUE:...}($DATA.$CALLDATA); + - pattern: $DATA.$ADDR.call{$VALUE:..., $GAS:...}($DATA.$CALLDATA); languages: - solidity severity: ERROR