-
Notifications
You must be signed in to change notification settings - Fork 230
Fuzz Testing
PDF-Writer ships a set of libFuzzer harnesses targeting the parser/decoder code in the library and its bundled dependencies. They're opt-in: a single CMake flag wires the instrumentation into every binary, and the harnesses build alongside the regular test executable. Maintainers run them occasionally to surface crash, UB and memory bugs that the unit tests don't reach.
- LLVM Clang (Apple Clang on macOS doesn't ship libFuzzer)
- LLD linker
- Linux is the simplest target. On macOS use Homebrew:
then use
brew install llvm lld/opt/homebrew/opt/llvm/bin/clangand/opt/homebrew/opt/llvm/bin/clang++as the compilers.
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DBUILD_FUZZING_HARNESS=ON ..
cmake --build . -- -j$(nproc)
The BUILD_FUZZING_HARNESS=ON flag is applied globally: PDFWriter and every bundled dependency (zlib, freetype, libpng, libjpeg, libtiff, libaesgm) is compiled with -fsanitize=fuzzer-no-link,address so libFuzzer has real coverage signal across the whole library — not just the harness body. Each harness binary additionally links -fsanitize=fuzzer to pull in the libFuzzer runtime.
Each harness targets one parser or decoder. Run any of them by pointing at its seed corpus directory:
| Harness | Target | Corpus | Dict |
|---|---|---|---|
PDFParserFuzzingHarness |
PDFParser (deep — page tree, indirect objects, stream decoding) |
Materials/fuzzing/MinimalFuzzingCorpus |
pdf.dict |
JPEGImageParserFuzzingHarness |
JPEGImageParser::Parse |
Materials/fuzzing/JPEGCorpus |
jpeg.dict |
TIFFImageHandlerFuzzingHarness |
PDFWriter::CreateFormXObjectFromTIFFStream (full TIFF → PDF pipeline) |
Materials/fuzzing/TIFFCorpus |
— |
InputFlateDecodeStreamFuzzingHarness |
InputFlateDecodeStream (FlateDecode / zlib filter) |
Materials/fuzzing/FlateCorpus |
— |
InputLZWDecodeStreamFuzzingHarness |
InputLZWDecodeStream (LZWDecode filter) |
Materials/fuzzing/LZWCorpus |
— |
InputAESDecodeStreamFuzzingHarness |
InputAESDecodeStream / InputAESDecodeStreamSSL (chosen at build time by USE_OPENSSL_AES) |
Materials/fuzzing/AESCorpus |
— |
OpenTypeFileInputFuzzingHarness |
OpenTypeFileInput::ReadOpenTypeFile |
Materials/fuzzing/OpenTypeCorpus |
— |
CFFFileInputFuzzingHarness |
CFFFileInput::ReadCFFFile |
Materials/fuzzing/CFFCorpus |
— |
The TIFF and AES harnesses are conditional on their feature flags: TIFFImageHandlerFuzzingHarness is not built when PDFHUMMUS_NO_TIFF is set, and InputAESDecodeStreamFuzzingHarness picks between the OpenSSL-backed and libaesgm-backed AES implementation depending on USE_OPENSSL_AES.
The AES harness uses a fixed 16-byte key (0..15) — it fuzzes for memory-safety bugs in the AES path (IV handling, padding, short ciphertext), not for crypto correctness.
The deep PDF parser harness caps per-iteration work at 1000 pages, 1000 objects, and 1 MB per decoded stream so libFuzzer can iterate at thousands of inputs per second. The production library validates these values independently; the caps are throughput tuning, not security bounds.
./PDFWriterTesting/PDFParserFuzzingHarness \
-dict=../PDFWriterTesting/Materials/fuzzing/pdf.dict \
../PDFWriterTesting/Materials/fuzzing/MinimalFuzzingCorpus
./PDFWriterTesting/JPEGImageParserFuzzingHarness \
-dict=../PDFWriterTesting/Materials/fuzzing/jpeg.dict \
../PDFWriterTesting/Materials/fuzzing/JPEGCorpus
./PDFWriterTesting/TIFFImageHandlerFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/TIFFCorpus
./PDFWriterTesting/InputFlateDecodeStreamFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/FlateCorpus
./PDFWriterTesting/InputLZWDecodeStreamFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/LZWCorpus
./PDFWriterTesting/InputAESDecodeStreamFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/AESCorpus
./PDFWriterTesting/OpenTypeFileInputFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/OpenTypeCorpus
./PDFWriterTesting/CFFFileInputFuzzingHarness \
../PDFWriterTesting/Materials/fuzzing/CFFCorpus
Dict files give libFuzzer a vocabulary of format-specific anchor tokens (PDF keywords, JPEG markers) to use as mutation building blocks. Harnesses without a dict still work; adding one would help libFuzzer reach deeper code faster.
libFuzzer prints periodic stats (cov: for edges covered, ft: for features, corp: for active corpus size, exec/s: for iterations per second). Crashes are saved to crash-* files in the working directory for analysis. Stop with Ctrl-C; libFuzzer writes a final summary.
For multi-core scaling and dictionary support beyond what libFuzzer provides, compile with AFL++:
cmake -DCMAKE_C_COMPILER=afl-clang-fast \
-DCMAKE_CXX_COMPILER=afl-clang-fast++ -DBUILD_FUZZING_HARNESS=ON ..
Then run:
afl-fuzz -i ../PDFWriterTesting/Materials/fuzzing/MinimalFuzzingCorpus \
-o fuzzing-out -x ../PDFWriterTesting/Materials/fuzzing/pdf.dict \
-t 50 -- ./PDFParserFuzzingHarness
For static instrumentation of the existing test suite (instead of random-input fuzzing), see the Sanitizer Builds Of PDFWriter page. The two are complementary — sanitizers + fuzzing together is the gold standard, and the harness build already enables -fsanitize=fuzzer-no-link,address. UBSan composes on top with -DPDFHUMMUS_SANITIZER=undefined.
- First Steps In Creating a PDF file
- Creating PDF Pages
- Images Support
- Text Support
- Adding Content to PDF Pages
- Links
- Unicode and UnicodeString class
- PDF Embedding
- Custom input and output
- Using Form XObjects
- Forward Referencing
- JPG Images Support
- TIFF Images Support
- PNG Images support