Dria

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

Block Gas Limit Dos Vulnerability in LLM Oracle Request Function

Summary

The LLMOracleCoordinator's request function accepts unbounded byte arrays for input and model data, storing them directly in contract storage. Without size limits, this can consume excessive gas due to storage operations and memory expansion, potentially reaching block gas limits. Large requests could use up to 50% of a block's gas limit (>15M gas), enabling denial of service attacks and network congestion. Additionally, storing large data on-chain makes oracle response processing expensive due to storage read costs.

The request function accepts unbounded byte arrays for input and model data, which when combined with state changes and mapped storage, can lead to potential block gas limit issues.

Let's analyze the gas-critical operations in the request function:

https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/llm/LLMOracleCoordinator.sol#L150

function request(
bytes32 protocol,
bytes memory input, // Unbounded bytes
bytes memory models, // Unbounded bytes
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
// ... fee checks ...
// State changes requiring gas
uint256 taskId = nextTaskId;
unchecked {
++nextTaskId;
}
// Critical operation: Large data storage
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input, // Stored in contract storage
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models // Stored in contract storage
});
emit Request(taskId, msg.sender, protocol);
emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration);
return taskId;
}

Gas Consumption Analysis

Let's break down the gas costs:

  1. Storage Costs:

    • Writing to new storage slots: 20,000 gas per slot

    • For bytes storage:

      // Example gas calculation for input data
      Base storage cost: 20,000
      + (input.length / 32) * 20,000 // Each 32 bytes needs a new slot
      + remaining bytes cost
  2. Memory Expansion:

    // Memory expansion cost formula
    memory_cost = 3 * words + (words * words) / 512
    // where words = (input.length + 31) / 32
  3. Event Emission:

    emit Request(taskId, msg.sender, protocol);
    // Base cost: 375 gas
    // + 375 gas per indexed parameter (3 total)
    // + 8 gas per byte of data

Problematic Scenario

// Example malicious or unintentional large request
bytes memory largeInput = new bytes(1_000_000); // 1MB of data
bytes memory largeModels = new bytes(500_000); // 500KB of data
// This request could exceed block gas limit
coordinator.request(
"protocol",
largeInput, // ~6,250,000 gas for storage
largeModels, // ~3,125,000 gas for storage
parameters // Additional gas for other operations
);

Current Ethereum block gas limit: ~30M gas
Potential gas consumption of a large request: >15M gas (50% of block)

Impact Examples

  1. Network Congestion:

// Multiple requests with large data in same block
for(uint i = 0; i < 3; i++) {
coordinator.request(
"protocol",
largeInput, // 6M+ gas each
largeModels, // 3M+ gas each
parameters
);
}
// Result: Block becomes full, other transactions blocked
  1. Oracle Processing:

function respond(uint256 taskId, uint256 nonce, bytes calldata output, bytes calldata metadata)
public
onlyRegistered(LLMOracleKind.Generator)
{
TaskRequest storage task = requests[taskId];
// Loading large input/models from storage
// High gas cost for oracle responses
}

Proposed Fix

contract LLMOracleCoordinator {
// Add size limits
uint256 public constant MAX_INPUT_SIZE = 128 * 1024; // 128KB
uint256 public constant MAX_MODELS_SIZE = 32 * 1024; // 32KB
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
// Add size validation
require(input.length <= MAX_INPUT_SIZE, "Input too large");
require(models.length <= MAX_MODELS_SIZE, "Models too large");
// Calculate approximate gas cost
uint256 estimatedGas =
21000 + // Base transaction cost
(input.length / 32) * 20000 + // Input storage cost
(models.length / 32) * 20000 + // Models storage cost
50000; // Other operations buffer
require(estimatedGas <= block.gaslimit / 3, "Request too large");
// Continue with request...
}
}

Gas-Optimized Alternative Architecture

contract LLMOracleCoordinator {
// Store large data off-chain
struct TaskRequest {
address requester;
bytes32 protocol;
bytes32 inputHash; // Hash of input stored off-chain
bytes32 modelsHash; // Hash of models stored off-chain
LLMOracleTaskParameters parameters;
TaskStatus status;
uint256 generatorFee;
uint256 validatorFee;
uint256 platformFee;
}
function request(
bytes32 protocol,
bytes32 inputHash,
bytes32 modelsHash,
LLMOracleTaskParameters calldata parameters
) public returns (uint256) {
// Much lower gas cost, fixed size storage
requests[taskId] = TaskRequest({
inputHash: inputHash,
modelsHash: modelsHash,
// ... other fields
});
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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