Skip to main content

Compute differential coverage for fuzzer runs.

Project description

Differential Coverage

Absolute coverage numbers aren't painting the whole picture: Different fuzzers may reach the same total coverage, but cover different parts of the program. Fuzzer (A) may reach more coverage than fuzzer (B), but (B) may reach coverage that (A) doesn't reach — so it's still valuable.

Definition

Differential coverage is a measure for this. It was proposed by Leonelli et al. in their paper on TwinFuzz[^1]. This implementation relies on the formulas from the SBFT’25 Competition Report[^2]. There, Crump et al. define a fuzzers overall score compared to others as

$$ relscore(f,b,s,e) = \left|f_0\in F|e\notin \text{cov}(b,t,s)\forall t\in \text{trials}(f_0,b)\right| \times\frac {\left|{t\in \text{trials}(f,b)\ |\ e \in \text{cov}(b,t,s)}\right|} {\left|{t\in \text{trials}(f,b)\ |\ \text{cov}(b,t,s)\neq \emptyset}\right|} $$

The score of a fuzzer is then

$$ \text{score}(f,b,s)=\sum_{e\in E}\text{relscore}(f,b,s,e) $$

Explanation

This can be simplified to the following:

$$ \text{differential coverage}(f,e) = \text{number of fuzzers that never hit }e \times\frac {\text{number of trials of }f\text{ that hit }e} {\text{number of trials of }f\text{ with non-empty cov}} $$ $$ \text{score}(f) = \text{sum of differential coverage}(f,e)\text{ over all edges e} $$

Usage

Assume <input_dir> is a directory of directories for each fuzzer, where each fuzzer subdirectory contains coverage files for each trial. Currently, the output format of afl-showmap is supported (lines with edge_id:count, where we only care if count > 0).

PRs for other data formats welcome :)

Command Line Interface

differential_coverage <input_dir>

Example

tests/sample_coverage provides sample coverage data to explain differential coverage. It contains 3 fuzzers and 3 edges:

  • Edge 1 is always hit by all fuzzers
  • Edge 2 is always hit by fuzzer_c, sometimes hit by fuzzer_a, and never hit by fuzzer_b
  • Edge 3 is always hit by fuzzer_b and fuzzer_c, but only sometimes by fuzzer_a
differential_coverage tests/sample_coverage

then produces

fuzzer_c: 1.00
fuzzer_a: 0.50
fuzzer_b: 0.00

API

from pathlib import Path
from differential_coverage import (
    calculate_scores_for_campaign,
    calculate_differential_coverage_scores,
)

# From a campaign directory (one subdir per fuzzer, each with coverage files):
scores = read_campaign_and_calculate_score(Path("path/to/campaign_dir"))
# -> dict[str, float]: fuzzer name -> score

# From in-memory campaign data (fuzzer -> trial_id -> edge_id -> count):
campaign = {...}  # dict[str, dict[str, dict[int, int]]]
scores = calculate_differential_coverage_scores(campaign)
# -> dict[str, float]: fuzzer name -> score

Installation

pip install .

Development

Install dev dependencies and set up the pre-commit hook (runs a couple of checks before committing):

pip install -e ".[dev]"
pre-commit install

[^1]: TWINFUZZ: Differential Testing of Video Hardware Acceleration Stacks, https://www.ndss-symposium.org/ndss-paper/twinfuzz-differential-testing-of-video-hardware-acceleration-stacks/

[^2]: SBFT’25 Competition Report — Fuzzing Track, https://ieeexplore.ieee.org/document/11086561

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

differential_coverage-0.1.0.tar.gz (4.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

differential_coverage-0.1.0-py3-none-any.whl (4.6 kB view details)

Uploaded Python 3

File details

Details for the file differential_coverage-0.1.0.tar.gz.

File metadata

  • Download URL: differential_coverage-0.1.0.tar.gz
  • Upload date:
  • Size: 4.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for differential_coverage-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2be8aab709cd5003d76b66524dee857d16e6e6a1adb11379425abc31cc40d17a
MD5 83c37d5ea82656b706a4ede1786cbb17
BLAKE2b-256 17b6e877b4405f6afadf952f3f6b7e5fd6588fbdb940206b0017eb6ae11d480e

See more details on using hashes here.

Provenance

The following attestation bundles were made for differential_coverage-0.1.0.tar.gz:

Publisher: ci.yml on riesentoaster/differential-coverage

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file differential_coverage-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for differential_coverage-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f9609f8828e1ba71889e1cbb7e66e37d4fbb2fe7244683ac97f6e243f57b3f56
MD5 1a7ad9df4e66f140c4474fb0042c2c90
BLAKE2b-256 567449b712fc51025bb69721a24bd3c9136993d80e6089ac75221501b28518eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for differential_coverage-0.1.0-py3-none-any.whl:

Publisher: ci.yml on riesentoaster/differential-coverage

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page