DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Assembly Offsets in ParaSwap CallData: Insecure Parsing

#assembly-parsing, #hardcoded-offsets

Summary

The ParaSwapUtils.swap function relies on fixed byte offsets in assembly to parse critical parameters (fromToken and fromAmount), enabling attackers or misconfigurations to inject malicious values and trigger unintended token approvals.

Vulnerability Details

The library enforces a rigid assumption that the callData contains fromToken and fromAmount at exact byte positions (68 and 100) within an assembly block. This approach fails to verify the actual structure or format of the callData. Attackers or uncoordinated updates to the ParaSwap aggregator interface can shift these parameters or repurpose the bytes at those offsets. The contract then approves tokens that were never intended, breaking the principle of secure and predictable on-chain operations. A malicious or malformed callData results in the contract granting the ParaSwap proxy an allowance over arbitrary tokens and amounts, exposing user funds to unauthorized transfers.

function swap(address to, bytes memory callData) external {
_validateCallData(to, callData);
address approvalAddress = IAugustusSwapper(to).getTokenTransferProxy();
address fromToken;
uint256 fromAmount;
assembly {
fromToken := mload(add(callData, 68)) // Assumes fixed byte position for fromToken
fromAmount := mload(add(callData, 100)) // Assumes fixed byte position for fromAmount
}
IERC20(fromToken).safeApprove(approvalAddress, fromAmount);
(bool success, ) = to.call(callData);
require(success, "paraswap call reverted");
}

Impact

Impact: Medium. An attacker can manipulate the assembly offsets to swap or approve tokens not originally intended by the contract. This flaw endangers the integrity of the token-approval process.

Likelihood: Medium. The aggregator often reverts on malformed calls, but partial or malicious aggregator changes, or carefully crafted calls, bypass this defense and cause unintended approvals. The input is expected from a trusted aggregator, but the current approach does not enforce this at the code level.

Tools Used

The attacker or a misconfigured system crafts callData with different byte layouts than the library expects.

They position a malicious token address at byte offset 68 and a high fromAmount at byte offset 100.

The function calls:

assembly {
fromToken := mload(add(callData, 68))
fromAmount := mload(add(callData, 100))
}

This produces unintended values for fromToken and fromAmount. The library proceeds with: IERC20(fromToken).safeApprove(approvalAddress, fromAmount); granting the aggregator an allowance over a token and amount the contract never intended to approve.

Recommendations

Eliminate hardcoded offsets and use ABI decoding to parse callData according to the aggregator’s official interface.

function swap(address to, bytes memory callData) external {
_validateCallData(to, callData);
(bytes4 selector, address fromToken, uint256 fromAmount, /* other fields */)
= abi.decode(callData, (bytes4, address, uint256, /* more types */));
address approvalAddress = IAugustusSwapper(to).getTokenTransferProxy();
IERC20(fromToken).safeApprove(approvalAddress, fromAmount);
(bool success, ) = to.call(callData);
require(success, "Paraswap call reverted");
}
Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid_ParaswapUtils_swap_malicious_callData

This function call only reached via a function called by the keeper. So no malicious callData will be provided.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.