Skip to content

ADR-102 — Coverage-Guided Fuzz Testing and Parser Input Hardening

Status: Accepted Date: 2026-10-02 Relevant Requirements: REQ-RTP-SYS-001, REQ-RTP-S-001, REQ-RTP-S-004, REQ-RTP-S-005 Related ADRs: ADR-016, ADR-077, ADR-100


1. Context

RTP processes external, untrusted, or semi-trusted data across multiple performance-critical surfaces: 1. World chunk data (.mca Anvil and .linear ZSTD region files): Parsed off-tick by anvil-api (AnvilReader, LinearRegionReader, and Nbt). In Minecraft, players can manipulate chunk state or exploit vanilla glitches to create corrupted chunks, oversized NBT structures, or invalid heightmaps. (2026-10-06: Linear reader withdrawn from core, see ADR-077; core parses only .mca.) 2. Redis network frames (RespProtocol): Used in proxy/network mode (rtp-proxy-common). In shared hosting or multi-plugin Redis networks, malformed frames or rogue plugins can inject malicious payloads. 3. Player command arguments and gate expressions (commands-api, GateExpressionParser): Executed on live threads during gameplay.

Traditional static unit testing and random property-based generators (jqwik) can miss edge-case byte sequences, magic-byte boundaries, and resource exhaustion vectors (e.g. integer overflow or unchecked memory allocations).


2. Decision

  1. Parser Input Bounds Hardening:
  2. Enforce explicit maximum size ceilings on dynamic array allocations in binary/frame decoders. Specifically, RespProtocol shall enforce MAX_BULK_STRING_LENGTH (16 MiB) on bulk string allocations, failing closed with a typed IOException instead of allocating unbounded buffers.
  3. All parser failure paths must fail closed safely, producing typed checked exceptions (IOException, CorruptRegionEntryException, RespException) or safe defaults (Verdict.UNKNOWN), with zero unhandled NullPointerException, NegativeArraySizeException, or thread-hanging infinite loops.

  4. Adopt Coverage-Guided In-Process Fuzz Testing (Jazzer) & Two-Tier CI Model:

  5. Introduce com.code-intelligence:jazzer-junit into the test toolchain (gradle/libs.versions.toml).
  6. Write @FuzzTest suites targeting AnvilReader (for .mca and .linear chunk decoding) and RespProtocol (for RESP2 wire parsing). (2026-10-06: Linear reader withdrawn from core, see ADR-077; the Linear fuzz target is deleted.)
  7. Tier 1 (Fast Regression on PR/Push): In standard CI and test execution (./gradlew test), fuzz tests execute deterministic regression seeds in milliseconds without launching the genetic mutation loop, ensuring zero slowdown or non-deterministic test times on PRs.
  8. Tier 2 (Scheduled & Dispatch Exploratory Fuzzing in CI): A dedicated CI workflow (.github/workflows/fuzzing.yml) executes extended in-process coverage-guided mutation loops weekly and on manual dispatch. It executes ./gradlew test with JAZZER_FUZZ=1 enabled, tests the fuzz targets for extended durations, and captures crash artifacts/reproducers if violations or hangs are uncovered.

3. Consequences

Positive

  • Guaranteed Fail-Closed Stability: Prevents server crashes or off-tick thread exhaustion when reading corrupted world save data.
  • DDoS/Memory Bomb Immunity: Eliminates unbounded heap allocations in network frame decoders.
  • Continuous Regressions Guard: Seed corpuses captured during fuzz runs serve as permanent regression unit tests in CI.

Neutral / Trade-offs

  • jazzer-junit adds an opt-in test dependency in anvil-api and rtp-proxy-common; standard unit testing continues unaffected.