Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Invalid

Phase Race Condition in Oracle Purchase Requests

Summary

Race condition in BuyerAgent's oracle purchase mechanism allows transactions to execute in incorrect market phases, breaking core protocol invariants and potentially leading to out-of-sequence trading operations.

Vulnerability Details

The phase validation and oracle request execution in BuyerAgent.sol are non-atomic operations. The time gap between phase check and request execution allows the protocol phase to change, invalidating the initial phase validation: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L189-L195

function oraclePurchaseRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized {
// @Issue - Phase check occurs here but isn't atomic with request
(uint256 round,) = _checkRoundPhase(Phase.Buy);
// @Issue - Phase could have changed by this point
oraclePurchaseRequests[round] =
swan.coordinator().request(SwanBuyerPurchaseOracleProtocol, _input, _models, swan.getOracleParameters());
}

This vulnerability creates a critical timing issue in the protocol's phase management system. The non-atomic nature of the phase check and oracle request allows for a race condition where:

  1. The function checks if we're in Buy phase

  2. Time passes, potentially changing phases

  3. Oracle request executes in wrong phase

This breaks the core protocol invariant that purchase requests must only occur during Buy phase. The impact is severe:

  • Disrupts the strict ordering of Sell -> Buy -> Withdraw phases

  • Could allow purchases during Sell/Withdraw phases

  • Undermines the entire market timing mechanism

  • May lead to unexpected asset pricing and trading conditions

POC

// 1. Call oraclePurchaseRequest when close to phase boundary
// 2. Transaction gets included in block that advances time to next phase
// 3. Oracle request executes in wrong phase despite initial check passing
async function demonstrateRaceCondition() {
const buyerAgent = await BuyerAgent.deploy();
// Wait until near phase boundary
await time.increaseTo(phaseEndTime - 1);
// Submit request that will execute in wrong phase
const tx = await buyerAgent.oraclePurchaseRequest(input, models);
await tx.wait();
// Verify phase changed during execution
const currentPhase = await buyerAgent.getRoundPhase();
assert(currentPhase.phase !== Phase.Buy);
}

Impact

  • Breaks fundamental phase ordering (Sell -> Buy -> Withdraw)

  • Allows purchase requests during incorrect phases

Recommendations

Implement phase validation at execution time by encoding phase information with the request data. This ensures phase consistency throughout the request lifecycle.

function oraclePurchaseRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized {
(uint256 round, Phase currentPhase,) = getRoundPhase();
+ // Encode phase with request data
+ bytes memory encodedInput = abi.encode(
+ currentPhase,
+ block.timestamp,
+ _input
+ );
oraclePurchaseRequests[round] =
- swan.coordinator().request(SwanBuyerPurchaseOracleProtocol, _input, _models, swan.getOracleParameters());
+ swan.coordinator().request(SwanBuyerPurchaseOracleProtocol, encodedInput, _models, swan.getOracleParameters());
}
+function validateAndExecuteRequest(uint256 taskId) internal {
+ bytes memory result = oracleResult(taskId);
+ (Phase requestPhase, uint256 timestamp, bytes memory data) = abi.decode(result, (Phase, uint256, bytes));
+ require(requestPhase == Phase.Buy, "Invalid phase at request time");
+ // Continue with request processing
+}
Updates

Lead Judging Commences

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

Support

FAQs

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