Summary
There's an Integer Overflow/Underflow problem in Statistics::variance function which renders the critical standard deviation calculation (function stddev() ) unusable. Due to this issue, LLMOracleCoordinator::validate function cannot be trusted.
Vulnerability Details
variance function in Statistics.sol contract cannot be used because it encounters with overflow/underflow error even with reasonable numbers.
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;
}
function stddev(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) {
(uint256 _variance, uint256 _mean) = variance(data);
mean = _mean;
ans = sqrt(_variance);
}
Note that variance is a key component of stddev function. To see the vulnerability in action, integrate Foundry to the project, create foundry/Statistics.t.sol inside the already existing test folder, and copy the following contents inside:
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../../contracts/libraries/Statistics.sol";
contract StatisticsTest is Test {
function test_stddev_known_values() public {
uint256[] memory data = new uint256[]();
data[0] = 2;
data[1] = 4;
data[2] = 4;
data[3] = 4;
data[4] = 5;
data[5] = 5;
data[6] = 7;
data[7] = 9;
(uint256 sd, uint256 mean) = Statistics.stddev(data);
assertEq(mean, 5);
}
function test_stddev_known_values_easy() public {
uint256[] memory data = new uint256[]();
data[0] = 2;
data[1] = 4;
data[2] = 6;
(uint256 sd, uint256 mean) = Statistics.stddev(data);
assertEq(mean, 4);
assertEq(sd, 2);
}
function test_variance_known_values() public {
uint256[] memory data = new uint256[]();
data[0] = 2;
data[1] = 4;
data[2] = 6;
(uint256 ans, uint256 mean) = Statistics.variance(data);
assertEq(mean, 4);
console.log("ans", ans);
console.log("mean", mean);
}
}
Then run forge test -vvvv --match-contract StatisticsTest
this will be the terminal output:
Ran 3 tests for test/foundry/Statistics.t.sol:StatisticsTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_stddev_known_values() (gas: 2733)
Traces:
[2733] StatisticsTest::test_stddev_known_values()
└─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_stddev_known_values_easy() (gas: 1472)
Traces:
[1472] StatisticsTest::test_stddev_known_values_easy()
└─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_variance_known_values() (gas: 1398)
Traces:
[1398] StatisticsTest::test_variance_known_values()
└─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
Suite result: FAILED. 0 passed; 3 failed; 0 skipped; finished in 368.90µs (254.78µs CPU time)
Ran 1 test suite in 5.93ms (368.90µs CPU time): 0 tests passed, 3 failed, 0 skipped (3 total tests)
Failing tests:
Encountered 3 failing tests in test/foundry/Statistics.t.sol:StatisticsTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_stddev_known_values() (gas: 2733)
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_stddev_known_values_easy() (gas: 1472)
[FAIL: panic: arithmetic underflow or overflow (0x11)] test_variance_known_values() (gas: 1398)
Encountered a total of 3 failing tests, 0 tests succeeded
Impact
The impact is high because Statistics::stddev is used in LLMOracleCoordinator::finalizeValidation function that is called by LLMOracleCoordinator::validate. This is a critical part of the protocol, and cannot afford to fail in any way.
Tools Used
Foundry
Recommendations
Reconsider the Statistics::variance function or consider using fixed-point arithmetic library like PRBMath