Dria

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

Arithmetic Underflow Vulnerability in Statistics Library

Arithmetic Underflow Vulnerability in Statistics Library Leading to Denial of Service

Summary

A critical arithmetic underflow vulnerability was identified in the Statistics library. This flaw occurs within the variance function, where subtracting the mean from data points can result in an underflow, causing the transaction to revert. Consequently, functions dependent on variance, such as stddev and LLMOracleCoordinator.sol::finalizeValidation and LLMOracleCoordinator.sol::validate, are perpetually reverted, leading to a Denial of Service (DoS) condition.

Vulnerability Details

The vulnerability resides in the Statistics.sol library, particularly within the variance function. The problematic line is https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/libraries/Statistics.sol?plain=1#L22:

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++) {
// This line calculates the difference between each data point and the mean
@> uint256 diff = data[i] - mean;
sum += diff * diff;
}
ans = sum / data.length;
}

Since both data[i] and mean are unsigned integers (uint256), if data[i] is less than mean, the subtraction results in an underflow. In Solidity versions ^0.8.0, such underflows automatically trigger a revert with panic code 0x11 (Arithmetic operation overflowed outside of an unchecked block).

Impact

Due to the perpetual reversion of variance and stddev and finalizeValidation functions, validate function and validation process cannot complete successfully. This halts the entire validation process, effectively rendering the contract non-functional for its intended purpose.

variance: Directly causes underflow during subtraction.

stddev: Relies on variance, thereby inheriting the vulnerability.

LLMOracleCoordinator::validate and LLMOracleCoordinator::finalizeValidation: Utilizes stddev, leading to cascading failures and a Denial of Service (DoS).

The Likelihood of underflow is High and its almost always triggered because there will be at least one data point lower than the mean in a dataset. As a result, the contract encounters frequent reverts in the variance function, and, by extension, in all dependent functions like stddev and finalizeValidation and validate, making the contract highly susceptible to Denial of Service (DoS) through routine usage.

Tools Used

manual review and unit test

Steps to Reproduce

To verify the arithmetic underflow vulnerability in the variance function, the following steps can be followed. This involves deploying a wrapper contract around the Statistics library to directly test the affected functions, and then running a Hardhat test script to confirm the expected revert behavior.

Create a Wrapper Contract

// contracts/StatisticsTest.sol
pragma solidity ^0.8.20;
import "./libraries/Statistics.sol";
contract StatisticsTest {
using Statistics for uint256[];
function testVariance(uint256[] memory data) external pure returns (uint256, uint256) {return Statistics.variance(data);}
function testStdDev(uint256[] memory data) external pure returns (uint256, uint256) {return Statistics.stddev(data);}
}

Add test

// test/StatisticsTest.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { Contract } from "ethers";
describe("StatisticsTest Contract", function () {
let statisticsTest: Contract;
// Deploy the contract before running tests
beforeEach(async function () {
const StatisticsTestFactory = await ethers.getContractFactory("StatisticsTest");
statisticsTest = await StatisticsTestFactory.deploy();
await statisticsTest.waitForDeployment();
});
describe("Underflow Tests", function () {
// Test case for the variance function to check overflow
it("variance function should revert with underflow", async function () {
const data = [123, 6712, 820, 1, 346, 82, 9216];
try {
// Attempt to call the variance function with data that may cause overflow
await statisticsTest.testVariance(data);
// Throw an error if no revert occurred, as the test expects one
throw new Error("Expected revert but did not receive one");
} catch (error) {
// Check if the revert message contains the expected overflow message
if (error.message.includes("Arithmetic operation overflowed outside of an unchecked block")) {
console.log("Revert message:", error.message); // Log the error message if it contains the expected string
} else {
// Throw an error with details if the revert message is unexpected
throw new Error(`Unexpected revert message: ${error.message}`);
}
}
});
// Test case for the standard deviation function to check overflow
it("stddev function should revert with underflow", async function () {
const data = [2348967, 2356784, 1257896, 8901, 1256]; // Input data for testing
try {
// Attempt to call the standard deviation function with data that may cause overflow
await statisticsTest.testStdDev(data);
// Throw an error if no revert occurred, as the test expects one
throw new Error("Expected revert but did not receive one");
} catch (error) {
// Check if the revert message contains the expected overflow message
if (error.message.includes("Arithmetic operation overflowed outside of an unchecked block")) {
console.log("Revert message:", error.message); // Log the error message if it contains the expected string
} else {
// Throw an error with details if the revert message is unexpected
throw new Error(`Unexpected revert message: ${error.message}`);
}
}
});
});
});

Expected Outcome

When the testVariance function is called with the test data, the transaction should revert with the panic code 0x11, indicating an arithmetic overflow.

Similarly, calling the testStdDev function with its designated test data should also cause a revert due to the inherited vulnerability from variance.

Recommendations

To mitigate the identified arithmetic underflow vulnerability and prevent reverts in the variance and related functions, you can consider implementing check before subtraction:

Modify the variance function to check whether each data point data[i] is greater than or equal to the mean before performing the subtraction.

Example Adjustment:

uint256 diff;
if (data[i] >= mean) {
diff = data[i] - mean;
} else {
diff = mean - data[i]; // Or handle as needed based on design choice
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 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.