Fuzzing and parser security¶
5DO reads several untrusted formats: save states, ISO/BIN/CUE/CHD images, the Opera file system, and guest-driven CD-ROM command streams. The standalone tools/FiveDo.Fuzz harness mutates those inputs deterministically and reports any exception that is not a documented, typed rejection.
The harness is not a coverage-guided libFuzzer/AFL replacement. It is a fast, reproducible regression and nightly safety net that runs on every platform with the normal .NET SDK and no native toolchain.
Targets¶
| Target | Input | What it exercises |
|---|---|---|
state | Valid save state plus random bytes | Versioned state header, section table, CRC, decompression, every section loader, and one frame after a successful load |
disc | Zero-filled ISO and random CHD bytes | ISO/CHD openers, sector reads, and container validation |
cue | Valid CUE+BIN plus mutated text | CUE parser, file resolution, track/index parsing, and sector reads |
opera-fs | Synthetic Opera volume plus mutations | Volume header, directory chain, entry names/copies, cycle/depth limits, and file reads |
cd-command | Random command/poll/tick/read stream | CdromDevice command state machine in both FreeDO and Opera timing modes, including eject, inject, reset, and data/status drains |
cpu | Random ARM60 words plus a random CPSR | Decode/execute bounds, banked-register switching, data-abort handling, and both ARM timing modes |
The state, disc, and cue targets expect many inputs to be rejected. A rejection is a typed failure when it uses one of the documented exception types:
InvalidStateExceptionDiscImageExceptionInvalidDataExceptionIOExceptionandUnauthorizedAccessExceptionArgumentExceptionNotSupportedExceptionFormatExceptionSecurityException
Any other exception is an unexpected failure. The harness writes the input, the failure text, and the exact seed/iteration to the reproduction directory and exits with code 1.
Usage¶
# Time-limited run (the nightly default is 120 seconds per target)
dotnet run --project tools/FiveDo.Fuzz -c Release -- --seconds 60 --target all --json
# Bounded deterministic run
dotnet run --project tools/FiveDo.Fuzz -c Release -- --iterations 500 --seed 42 --target state,cue
# One target with a human-readable report
dotnet run --project tools/FiveDo.Fuzz -c Release -- --seconds 30 --target opera-fs
Options:
| Option | Meaning |
|---|---|
--target LIST | all, or a comma-separated list of state, disc, cue, opera-fs, cd, cpu. |
--iterations N | Maximum iterations per target. When omitted, the run is time-limited. |
--seconds N | Seconds per target. Defaults to 30 when --iterations is omitted. |
--seed N | Base deterministic seed. Defaults to 1. |
--out DIR | Reproduction directory. Defaults to artifacts/fuzz. |
--json | Emit the machine-readable report. |
-h, --help | Show help. |
The report includes per-target iteration and typed/unexpected counts. The process exits with code 0 when there are no unexpected failures, 1 when a defect is found, and 2 for invalid command-line options.
Reproducing a failure¶
A failure directory contains:
failure.txt records the target, seed, iteration, description, exception type, message, and stack trace. Rerun the same case with the reported seed and enough iterations:
dotnet run --project tools/FiveDo.Fuzz -c Release -- `
--target state --seed 397 --iterations 43 --out artifacts/fuzz-repro
The generator is deterministic for a given seed and target, so iteration 42 is the same input. Add a focused regression test to tests/FiveDo.Tests once the defect is fixed; the nightly run is the broad safety net, not a substitute for a targeted test.
Nightly CI¶
.github/workflows/fuzz.yml runs the harness every night with a run-specific seed and uploads the JSON report and any reproduction directories. It can also be started manually with a custom duration.
Adding a target¶
- Implement
IFuzzTargetintools/FiveDo.Fuzz/. - Generate a
FuzzCasewith a description and, when needed, companion files. - Execute the parser or device and let documented rejections propagate.
- Register the target in
FuzzTargetKind,FuzzOptions.EnumerateTargets, andFuzzTargetFactory. - Add a bounded run to
FuzzHarnessTestsand update this table.
Keep case generation cheap and deterministic. Use FuzzContext.Random only; do not use Random.Shared, wall-clock time, or machine-specific paths in generated input.
Scope and limitations¶
- The harness targets parsers, the CD command boundary, and the ARM60 instruction decoder. It does not fuzz DSP, MADAM, or GPU pipelines; those have their own deterministic conformance and golden tests.
- The
cd-commandtarget uses a synthetic 16-block Opera disc. It validates command state-machine safety, not title-specific CD timing. - The
disctarget does not generate valid compressed CHD payloads; valid CHD coverage comes from the compatibility corpus anddisc verify. - A clean run is evidence of robustness, not a proof of security. Report security issues through the process in
SECURITY.md.
Evidence¶
- 2026-11-02: a 60-second-per-target run (seed 20261102) completed 6,705,667 iterations across the state, disc, CUE, Opera FS, and CD targets with 0 unexpected failures and 1,758,669 typed rejections. A 60-second CPU-stream run completed 71,783 iterations with 0 failures. The JSON reports are
artifacts/fuzz-nightly.jsonandartifacts/fuzz-cpu.json. - The nightly workflow is the ongoing gate. Record a new clean run here when a release gate requires it.