Skip to content

Commit afb05ba

Browse files
pxrlnicholaspai
andauthored
improve: L05 Low Level Call to External Exchange Contract With Arbitrary Calldata (#439)
The SwapAndBridge contract enables swapping an amount of tokens via a decentralized exchange service of preference and then bridge the swapped amount via Across. Both swap and bridging actions take place within a single transaction. To execute the swap, the SwapAndBridge contract performs a low level call to the designated exchange contract. For this call, the calldata parameters are arbitrarily given by the user. The only restriction is that the called function selector must be different than an ERC-20 token's transferFrom function. Performing a low level call with arbitrary data to an external contract should be avoided as it increases the possible attack surface. Instead of blacklisting the suspicious calldata parameters, consider whitelisting the allowed ones. More specifically, consider whitelisting the specific swap function selectors of the external exchange in each SwapAndBridge contract. This fix originally authored by Nick. Co-authored-by: nicholaspai <9457025+nicholaspai@users.noreply.github.com>
1 parent dda5659 commit afb05ba

File tree

1 file changed

+24
-9
lines changed

1 file changed

+24
-9
lines changed

contracts/SwapAndBridge.sol

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import "@uma/core/contracts/common/implementation/MultiCaller.sol";
1414
contract SwapAndBridgeBase is Lockable, MultiCaller {
1515
using SafeERC20 for IERC20;
1616

17-
// The transferFrom selector to block any calldata attempting to call transferFrom.
18-
bytes4 public constant TRANSFER_FROM_SELECTOR = IERC20.transferFrom.selector;
17+
// This contract performs a low level call with arbirary data to an external contract. This is a large attack
18+
// surface and we should whitelist which function selectors are allowed to be called on the exchange.
19+
mapping(bytes4 => bool) public allowedSelectors;
1920

2021
// Across SpokePool we'll submit deposits to with acrossInputToken as the input token.
2122
V3SpokePoolInterface public immutable spokePool;
@@ -57,16 +58,24 @@ contract SwapAndBridgeBase is Lockable, MultiCaller {
5758
****************************************/
5859
error MinimumExpectedInputAmount();
5960
error LeftoverSrcTokens();
60-
error TransferFromCalldata();
61+
error InvalidFunctionSelector();
6162

6263
/**
6364
* @notice Construct a new SwapAndBridgeBase contract.
6465
* @param _spokePool Address of the SpokePool contract that we'll submit deposits to.
6566
* @param _exchange Address of the exchange where tokens will be swapped.
67+
* @param _allowedSelectors Function selectors that are allowed to be called on the exchange.
6668
*/
67-
constructor(V3SpokePoolInterface _spokePool, address _exchange) {
69+
constructor(
70+
V3SpokePoolInterface _spokePool,
71+
address _exchange,
72+
bytes4[] memory _allowedSelectors
73+
) {
6874
spokePool = _spokePool;
6975
exchange = _exchange;
76+
for (uint256 i = 0; i < _allowedSelectors.length; i++) {
77+
allowedSelectors[_allowedSelectors[i]] = true;
78+
}
7079
}
7180

7281
// This contract supports two variants of swap and bridge, one that allows one token and another that allows the caller to pass them in.
@@ -78,11 +87,10 @@ contract SwapAndBridgeBase is Lockable, MultiCaller {
7887
IERC20 _swapToken,
7988
IERC20 _acrossInputToken
8089
) internal {
81-
// Do not allow transferFrom calls to prevent abuse of approvals.
8290
// Note: this check should never be impactful, but is here out of an abundance of caution.
83-
// If the exchange address in the contract is also an ERC20 token that is approved by some
91+
// For example, if the exchange address in the contract is also an ERC20 token that is approved by some
8492
// user on this contract, a malicious actor could call transferFrom to steal the user's tokens.
85-
if (bytes4(routerCalldata) == TRANSFER_FROM_SELECTOR) revert TransferFromCalldata();
93+
if (!allowedSelectors[bytes4(routerCalldata)]) revert InvalidFunctionSelector();
8694

8795
// Pull tokens from caller into this contract.
8896
_swapToken.safeTransferFrom(msg.sender, address(this), swapTokenAmount);
@@ -170,15 +178,17 @@ contract SwapAndBridge is SwapAndBridgeBase {
170178
* @notice Construct a new SwapAndBridge contract.
171179
* @param _spokePool Address of the SpokePool contract that we'll submit deposits to.
172180
* @param _exchange Address of the exchange where tokens will be swapped.
181+
* @param _allowedSelectors Function selectors that are allowed to be called on the exchange.
173182
* @param _swapToken Address of the token that will be swapped for acrossInputToken. Cannot be 0x0
174183
* @param _acrossInputToken Address of the token that will be bridged via Across as the inputToken.
175184
*/
176185
constructor(
177186
V3SpokePoolInterface _spokePool,
178187
address _exchange,
188+
bytes4[] memory _allowedSelectors,
179189
IERC20 _swapToken,
180190
IERC20 _acrossInputToken
181-
) SwapAndBridgeBase(_spokePool, _exchange) {
191+
) SwapAndBridgeBase(_spokePool, _exchange, _allowedSelectors) {
182192
swapToken = _swapToken;
183193
acrossInputToken = _acrossInputToken;
184194
}
@@ -222,8 +232,13 @@ contract UniversalSwapAndBridge is SwapAndBridgeBase {
222232
* @notice Construct a new SwapAndBridgeBase contract.
223233
* @param _spokePool Address of the SpokePool contract that we'll submit deposits to.
224234
* @param _exchange Address of the exchange where tokens will be swapped.
235+
* @param _allowedSelectors Function selectors that are allowed to be called on the exchange.
225236
*/
226-
constructor(V3SpokePoolInterface _spokePool, address _exchange) SwapAndBridgeBase(_spokePool, _exchange) {}
237+
constructor(
238+
V3SpokePoolInterface _spokePool,
239+
address _exchange,
240+
bytes4[] memory _allowedSelectors
241+
) SwapAndBridgeBase(_spokePool, _exchange, _allowedSelectors) {}
227242

228243
/**
229244
* @notice Swaps tokens on this chain via specified router before submitting Across deposit atomically.

0 commit comments

Comments
 (0)