Summary
Subtracting mean
from an element of the dataset will likely result in arithmetic underflow in unsigned arithmetic in most cases.
Vulnerability Details
In the Statistics
library, the variance
method is used to calculate the variance of an array of unsigned integers, data
. Variance measures how far each element in the dataset is from the mean. For each element data[i]
, the method calculates the difference between that element and the mean:
https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/libraries/Statistics.sol#L18-L26
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++) {
uint256 diff = data[i] - mean;
sum += diff * diff;
}
ans = sum / data.length;
}
However, since unsigned integers are used, this can result in arithmetic underflow when the data
array contains more than one distinct value.
PoC
describe("with validation", function () {
const [numGenerations, numValidations] = [2, 2];
const dummyScore = parseEther("0.9");
--- const scores = Array.from({ length: numGenerations }, () => dummyScore);
+++ const scores = [parseEther("0.9"), parseEther("0.8")];
let generatorAllowancesBefore: bigint[];
let validatorAllowancesBefore: bigint[];
....
yarn test ./test/LLMOracleCoordinator.test.ts
1) LLMOracleCoordinator
with validation
should validate (2/2):
Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation overflowed outside of an unchecked block)
at LLMOracleCoordinator.variance (contracts/libraries/Statistics.sol:22)
at LLMOracleCoordinator.stddev (contracts/libraries/Statistics.sol:32)
at LLMOracleCoordinator.finalizeValidation (contracts/llm/LLMOracleCoordinator.sol:365)
at LLMOracleCoordinator.validate (contracts/llm/LLMOracleCoordinator.sol:304)
Impact
There is a high probability of reverts during task finalization, which will block purchases.
Tools Used
Manual review, Hardhat tests
Recommendations
Modify the difference calculation to account for which value is less than the other, preventing arithmetic underflow:
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++) {
--- uint256 diff = data[i] - mean;
+++ uint256 diff = data[i] > mean ? data[i] - mean : mean - data[i];
sum += diff * diff;
}
ans = sum / data.length;
}