Files
ghostty/test/fuzz-libghostty/README.md
Mitchell Hashimoto 8cebcaa468 fuzz: stream cmin
2026-03-01 15:00:13 -08:00

125 lines
4.1 KiB
Markdown

# AFL++ Fuzzer for Libghostty
This directory contains [AFL++](https://aflplus.plus/) fuzzing harnesses for
libghostty-vt (Zig module).
## Fuzz Targets
| Target | Binary | Description |
| -------- | ------------- | ------------------------------------------------------- |
| `parser` | `fuzz-parser` | VT parser only (`Parser.next` byte-at-a-time) |
| `stream` | `fuzz-stream` | Full terminal stream (`nextSlice` + `next` via handler) |
The stream target creates a small `Terminal` and exercises the readonly
`Stream` handler, covering printing, CSI dispatch, OSC, DCS, SGR, cursor
movement, scrolling regions, and more. The first byte of each input selects
between the slice path (SIMD fast-path) and the scalar path.
## Prerequisites
Install AFL++ so that `afl-cc` and `afl-fuzz` are on your `PATH`.
- **macOS (Homebrew):** `brew install aflplusplus`
- **Linux:** build from source or use your distro's package (e.g.
`apt install afl++` on Debian/Ubuntu).
## Building
From this directory (`test/fuzz-libghostty`):
```sh
zig build
```
This compiles Zig static libraries for each fuzz target, emits LLVM bitcode,
then links each with `afl.c` using `afl-cc` to produce instrumented binaries
at `zig-out/bin/fuzz-parser` and `zig-out/bin/fuzz-stream`.
## Running the Fuzzer
Each target has its own run step:
```sh
zig build run-parser # Run the VT parser fuzzer
zig build run-stream # Run the VT stream fuzzer
```
Or invoke `afl-fuzz` directly:
```sh
afl-fuzz -i corpus/stream-initial -o afl-out/stream -- zig-out/bin/fuzz-stream @@
```
The fuzzer runs indefinitely. Let it run for as long as you like; meaningful
coverage is usually reached within a few hours, but longer runs can find
deeper bugs. Press `ctrl+c` to stop the fuzzer when you're done.
## Finding Crashes and Hangs
After (or during) a run, results are written to `afl-out/<target>/default/`:
```
afl-out/stream/default/
├── crashes/ # Inputs that triggered crashes
├── hangs/ # Inputs that triggered hangs/timeouts
└── queue/ # All interesting inputs (the evolved corpus)
```
Each file in `crashes/` or `hangs/` is a raw byte file that triggered the
issue. The filename encodes metadata about how it was found (e.g.
`id:000000,sig:06,...`).
## Reproducing a Crash
Replay any crashing input by piping it into the harness:
```sh
cat afl-out/stream/default/crashes/<filename> | zig-out/bin/fuzz-stream
```
## Corpus Management
After a fuzzing run, the queue in `afl-out/<target>/default/queue/` typically
contains many redundant inputs. Use `afl-cmin` to find the smallest
subset that preserves full edge coverage, and `afl-tmin` to shrink
individual test cases.
> **Important:** The instrumented binary reads input from **stdin**, not
> from file arguments. Do **not** use `@@` with `afl-cmin`, `afl-tmin`,
> or `afl-showmap` — it will cause them to see only the C harness
> coverage (~4 tuples) instead of the Zig VT parser coverage.
### Corpus minimization (`afl-cmin`)
Reduce the evolved queue to a minimal set covering all discovered edges:
```sh
AFL_NO_FORKSRV=1 afl-cmin.bash \
-i afl-out/stream/default/queue \
-o corpus/stream-cmin \
-- zig-out/bin/fuzz-stream
```
`AFL_NO_FORKSRV=1` is required because the Python `afl-cmin` wrapper has
a bug in AFL++ 4.35c. Use the `afl-cmin.bash` script instead (typically
found in AFL++'s `libexec` directory).
### Windows compatibility
AFL++ output filenames contain colons (e.g., `id:000024,time:0,...`), which
are invalid on Windows (NTFS). After running `afl-cmin`,
rename the output files to replace colons with underscores before committing:
```sh
./corpus/sanitize-filenames.sh
```
### Corpus directories
| Directory | Contents |
| ------------------------ | ----------------------------------------------- |
| `corpus/parser-initial/` | Hand-written seed inputs for vt-parser |
| `corpus/parser-cmin/` | Output of `afl-cmin` (edge-deduplicated corpus) |
| `corpus/stream-initial/` | Hand-written seed inputs for vt-stream |
| `corpus/stream-cmin/` | Output of `afl-cmin` (edge-deduplicated corpus) |