Skip to content

Commit 5a59962

Browse files
dfa1claude
andcommitted
docs: extract Jazzer fuzz infra into ADR-0020
Move the TODO.md fuzz-infrastructure design detail into a proper ADR (regression/fuzz modes, seed corpus, targets, differential fuzzing, OSS-Fuzz submission). TODO.md now just points to it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 65e10fb commit 5a59962

3 files changed

Lines changed: 98 additions & 15 deletions

File tree

TODO.md

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,7 @@ Per-encoding gotchas:
4848

4949
### Fuzz infrastructure
5050

51-
- [ ] **Jazzer + JUnit 5** — add `com.code-intelligence:jazzer-junit` test dep. Two modes:
52-
regression (`./mvnw test`, replays saved corpus + crashes) and fuzz
53-
(`JAZZER_FUZZ=1`, nightly profile). See research notes in branch
54-
`worktree-security-fuzz` commit history.
55-
- [ ] **Seed corpus from integration fixtures** — drop existing `.vortex` test files into
56-
`reader/src/test/resources/fuzz-corpus/full-file/`. Per-encoding sub-corpora extracted via
57-
a small tool that walks fixtures and dumps each segment to
58-
`core/src/test/resources/fuzz-corpus/<encoding>/`.
59-
- [ ] **Fuzz targets**: `VortexReader.open(byte[])`, `PostscriptParser.parseBlobs`, and one
60-
`@FuzzTest` per encoding `Encoding.decode`. Crash oracle: `ignore = {VortexException.class}`.
61-
- [ ] **Differential fuzz (Java vs Rust)** — round-trip random bytes through Java decode
62-
and `vortex-jni`; assert both throw or both return identical row count + values. Reuse
63-
`RustWritesJavaReadsIntegrationTest` harness.
64-
- [ ] **OSS-Fuzz submission** — Jazzer is a first-class OSS-Fuzz engine; submit the project
65-
once the corpus + targets stabilize. Free continuous fuzzing.
51+
- [ ] **Jazzer fuzz testing** — see [ADR-0020](adr/0020-jazzer-fuzz-infrastructure.md).
6652

6753
## Build
6854

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# ADR 0020: Jazzer fuzz testing infrastructure
2+
3+
- **Status:** Proposed
4+
- **Date:** 2026-07-12
5+
- **Deciders:** project maintainer
6+
- **Related:** [ADR 0003 — VortexException sanitization](0003-vortex-exception-sanitization.md),
7+
[ADR 0004 — Resource caps and `ReadOptions`](0004-resource-caps-read-options.md),
8+
[TODO.md §Security](../TODO.md)
9+
10+
## Context
11+
12+
The reader's security contract (TODO.md §Security) requires every malformed input to fail as
13+
`VortexException`, never a raw JVM crash class (`ArrayIndexOutOfBoundsException`,
14+
`OutOfMemoryError`, `StackOverflowError`, a raw FlatBuffer/Protobuf parser exception). Per-encoding
15+
adversarial unit tests cover known gotchas by hand (non-monotonic offsets, out-of-range codes,
16+
negative dimensions, …), but hand-written cases only exercise inputs someone already thought of —
17+
they don't explore the input space the way a coverage-guided fuzzer does.
18+
19+
[Jazzer](https://github.com/CodeIntelligenceTesting/jazzer) is a coverage-guided fuzzer for the
20+
JVM built on libFuzzer, with first-class JUnit 5 integration (`@FuzzTest`) and is also a supported
21+
OSS-Fuzz engine.
22+
23+
## Decision
24+
25+
Adopt Jazzer via the `com.code-intelligence:jazzer-junit` test dependency, in two modes:
26+
27+
- **regression**`./mvnw test` replays the saved corpus and crash reproducers, so every
28+
fuzz-discovered bug becomes a permanent part of the ordinary test run.
29+
- **fuzz**`JAZZER_FUZZ=1`, run on a nightly profile; this is the mode that actually explores
30+
new inputs. It must not run on every normal build.
31+
32+
### Seed corpus
33+
34+
Existing `.vortex` integration fixtures seed `reader/src/test/resources/fuzz-corpus/full-file/`.
35+
A small extraction tool walks those fixtures and dumps each segment to
36+
`core/src/test/resources/fuzz-corpus/<encoding>/`, producing per-encoding sub-corpora without
37+
hand-crafting inputs.
38+
39+
### Fuzz targets
40+
41+
- `VortexReader.open(byte[])` — full-file parse path.
42+
- `PostscriptParser.parseBlobs` — postscript/footer parsing boundary.
43+
- One `@FuzzTest` per encoding `Encoding.decode` — per-encoding decode boundary.
44+
45+
Crash oracle: `ignore = {VortexException.class}` — anything else is a real finding, per the
46+
security contract in TODO.md §Security.
47+
48+
### Differential fuzz (Java vs Rust)
49+
50+
Round-trip random bytes through Java decode and `vortex-jni`; assert both throw or both return
51+
identical row count + values. Reuses the `RustWritesJavaReadsIntegrationTest` harness — this mode
52+
catches semantic divergence from the Rust reference, not just crashes.
53+
54+
### OSS-Fuzz submission
55+
56+
Once the corpus and targets stabilize, submit the project to OSS-Fuzz. Jazzer is a first-class
57+
OSS-Fuzz engine, giving free continuous fuzzing beyond what CI runs.
58+
59+
## Consequences
60+
61+
### Positive
62+
63+
- Explores the untrusted-input space beyond hand-written adversarial tests; the per-encoding
64+
gotcha list in TODO.md §Security becomes a floor, not a ceiling.
65+
- Crash reproducers become permanent regression tests via the JUnit 5 integration.
66+
- Differential mode catches semantic divergence from the Rust reference, not just crashes.
67+
- OSS-Fuzz gives continuous fuzzing without maintaining dedicated infrastructure.
68+
69+
### Negative
70+
71+
- New test dependency (`jazzer-junit`) and a nightly CI profile to maintain.
72+
- The fuzz corpus needs periodic curation as encodings evolve, or it silently stops finding
73+
anything new.
74+
75+
### Risks to manage
76+
77+
- `JAZZER_FUZZ=1` must stay opt-in (nightly profile only) — active fuzzing on every
78+
`./mvnw test` would make the normal build slow and non-deterministic.
79+
- Crash reproducers must be triaged into either a real fix or an explicit `VortexException`
80+
classification before landing in the corpus, or noise accumulates.
81+
82+
## Alternatives considered
83+
84+
- **Hand-written adversarial tests only (status quo)** — cheap, but bounded by what a human
85+
enumerates; the per-encoding gotcha list in TODO.md §Security exists precisely because this
86+
approach misses things.
87+
- **AFL/libFuzzer via a custom JNI harness** — more mature native fuzzing ecosystem, but requires
88+
building and maintaining a harness that bridges into JVM bytecode. Jazzer already does this and
89+
ships JUnit 5 integration out of the box.
90+
91+
## References
92+
93+
- [Jazzer](https://github.com/CodeIntelligenceTesting/jazzer)
94+
- [TODO.md §Security](../TODO.md)
95+
- [ADR 0003 — VortexException sanitization](0003-vortex-exception-sanitization.md)
96+
- [ADR 0004 — Resource caps and `ReadOptions`](0004-resource-caps-read-options.md)

adr/ADR.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ the decision shipped in (blank = not yet shipped).
3434
| 0017 | In-house FlatBuffers codegen + MemorySegment runtime | Accepted | |
3535
| 0018 | Apache Calcite SQL adapter — push-down source | Accepted | |
3636
| 0019 | Columnar transducer façade for compute | Proposed | |
37+
| 0020 | Jazzer fuzz testing infrastructure | Proposed | |

0 commit comments

Comments
 (0)