Dria

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

Generators and Validators won't receive fees due to an underflow in `Statistics::variance` causing `LLMOracleCoordinator::finalizeValidation` to fail.

Vulnerability Details

For tasks with at least one required validation, the finalizeValidation function is invoked during the last validate call. This function calculates the standard deviation of the scores using Statistics::stddev, which in turn relies on Statistics::variance. However, a vulnerability in Statistics::variance causes an underflow in most cases, leading to a revert that fails the final validate call, preventing generators and validators from receiving their fees.

File: llm/LLMOracleCoordinator.sol
function validate(...) public {
// code...
if (isCompleted) {
// code...
@> finalizeValidation(taskId);
}
}
function finalizeValidation(uint256 taskId) private {
// code...
(uint256 _stddev, uint256 _mean) = Statistics.stddev(scores);
// code...
(uint256 stddev, uint256 mean) = Statistics.stddev(generationScores);
// code...
}
File: libraries/Statistics.sol
function variance(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) {
mean = avg(data);
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
/// @audit underflow
@> uint256 diff = data[i] - mean;
sum += diff * diff;
}
ans = sum / data.length;
}

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

https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/libraries/Statistics.sol#L22

This is due to the fact that even if one value in the data array is different than the rest, the mean of the data will be greater than atleast one value (ignoring the soidity rounding down), and the difference, data[i] - mean will underflow.

Proof of Concept

Add this to LLMOracleCoordinator.test.ts

describe("final validation fails when scores are not equal", function () {
const [numGenerations, numValidations] = [3, 2];
const dummyScore = parseEther("0.9");
const dummyScore2 = parseEther("0.5");
const scores = [dummyScore, dummyScore2, dummyScore];
this.beforeAll(async () => {
taskId++;
});
it("should revert the final validation", async function () {
await safeRequest(coordinator, token, requester, taskId, input, models, {
difficulty,
numGenerations,
numValidations,
});
for (let i = 0; i < numGenerations; i++) {
await safeRespond(coordinator, generators[i], output, metadata, taskId, BigInt(i));
}
await safeValidate(coordinator, validators[0], scores, metadata, taskId, 0n);
// final validation reverts with underflow panic
await expect(safeValidate(coordinator, validators[1], scores, metadata, taskId, 1n)).to.be.revertedWithPanic(0x11);
});
});

Impact

The generators and validators will not receive their fee and the task will not be completed.

Tools Used

Manual Review, Foundry

Recommendations

Modify the Statistics::variance function as:

File: libraries/Statistis.sol
function variance(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) {
mean = avg(data);
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
/// @audit underflow
@> uint256 diff = data[i] - mean;
sum += diff * diff;
}
ans = sum / data.length;
}
Updates

Lead Judging Commences

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

Underflow in computing variance

Support

FAQs

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