Skip to content

Commit

Permalink
arbitary-low-level-call: custom type as source
Browse files Browse the repository at this point in the history
  • Loading branch information
Raz0r committed Jul 18, 2024
1 parent 790f9a9 commit dd8fc1a
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 1 deletion.
211 changes: 210 additions & 1 deletion solidity/security/arbitrary-low-level-call.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}

6 changes: 6 additions & 0 deletions solidity/security/arbitrary-low-level-call.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit dd8fc1a

Please sign in to comment.