Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of State Validation in UsdTokenSwapKeeper's StreamsLookup Callback Pattern Enables Critical Swap Validation Bypass

Description

The UsdTokenSwapKeeper contract implements Chainlink's Data Streams lookup pattern for automated price-based swaps, but contains a critical security vulnerability in its validation flow between checkLog and checkCallback functions.

checkLog function performs initial validations:

if (request.deadline < block.timestamp) {
return (false, new bytes(0));
}
if (request.assetOut != self.asset) {
return (false, new bytes(0));
}

After validations pass, it reverts with StreamsLookup to fetch price data:

revert StreamsLookup(DATA_STREAMS_FEED_LABEL, streams, DATA_STREAMS_QUERY_LABEL, block.timestamp, extraData);

checkCallback processes the price data but fails to re-validate:

function checkCallback(
bytes[] calldata values,
bytes calldata extraData
)
external
pure
override
returns (bool upkeepNeeded, bytes memory performData)
{
bytes memory signedReport = values[0];
upkeepNeeded = true; // Always returns true
performData = abi.encode(signedReport, extraData);
}

The vulnerability stems from broken validation flow in the contract's StreamsLookup pattern. Critical checks performed in checkLog, like deadline and asset validation, vanish during the revert and callback cycle since checkCallback is marked as pure and automatically approves all requests. This prevents necessary state validation and creates a concerning scenario where callback data goes completely unchecked, violating Chainlink's fundamental security requirements for automation callbacks.

The core issue cascades from the modifier choice through the entire security architecture - the pure restriction blocks state reads, while the hardcoded true return bypasses all safeguards, leaving the contract exposed to multiple attack vectors through direct callback manipulation.

Attack Vectors

  1. Direct Callback Exploitation: An attacker could bypass deadline/asset validations by directly calling checkCallback

  2. Validation Race Condition: State could change between initial check and callback execution

  3. Expired Request Execution: Stale or expired requests could be processed due to missing re-validation

Impact

This security flaw creates a critical vulnerability in the swap execution pipeline that could lead to substantial financial damage. By bypassing core validation checks, attackers can force through expired or invalid swaps, targeting mismatched assets and circumventing essential business logic. The severity is amplified because each successfully exploited transaction could result in direct monetary losses through execution of swaps at outdated prices or with incorrect asset pairings, fundamentally compromising the contract's role as a secure market making system.

Proof of Concept

// Example attack flow
1. Attacker observes a failed/expired swap request
2. Constructs malicious extraData with expired request details
3. Directly calls checkCallback with crafted data
4. Function returns true, leading to swap execution without proper validation

Recommended Fix

function checkCallback(
bytes[] calldata values,
bytes calldata extraData
)
external
view // Changed from pure to allow state reads
override
returns (bool upkeepNeeded, bytes memory performData)
{
bytes memory signedReport = values[0];
(address user, uint128 requestId) = abi.decode(extraData, (address, uint128));
// Load storage
UsdTokenSwapKeeperStorage storage self = _getUsdTokenSwapKeeperStorage();
// Re-validate request
UsdTokenSwapConfig.SwapRequest memory request =
self.marketMakingEngine.getSwapRequest(user, requestId);
// Re-check conditions
if (request.deadline < block.timestamp) {
return (false, new bytes(0));
}
if (request.assetOut != self.asset) {
return (false, new bytes(0));
}
// Only proceed if all validations pass
upkeepNeeded = true;
performData = abi.encode(signedReport, extraData);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
6 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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