Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ UnitTests/testdata/interaction-traces/** text eol=lf
Fuzz/corpus/regression/** binary
Fuzz/corpus/regression/README.md text
Fuzz/corpus/regression/manifest.json text
Fuzz/corpus/**/*.bin binary
LoopLibCore/cmaps/** binary
LoopLibCore/liberation-fonts-ttf/** -whitespace
3rdparty_licenses/** -whitespace
Expand Down
46 changes: 45 additions & 1 deletion Fuzz/corpus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,51 @@ fuzzer-generated against Loop and redistributable under `LICENSE`.
| `Fuzz/corpus/fuzz_images/` | `fuzz_images` |

`scripts/ci/check_fuzz_corpus.py` enforces that every tracked seed has a
manifest entry and that checksums, harness ownership, and paths stay in sync.
manifest entry, that checksums, harness ownership, and paths stay in sync, and
that each harness owns at least one manifested seed. Empty `.gitkeep` directories
are not a corpus. Harness seed files (`*.bin`) are marked `binary` in
`.gitattributes` so ASCII PDFs are not rewritten with CRLF.

Synthetic coverage seeds use `origin: synthetic` and `issue: 0`. They are not
crash reproductions; they exist so `fuzz_pdf_parser`, `fuzz_content_stream`, and
`fuzz_stream_filters` do not start mutation from an empty directory.

## Synthetic coverage seeds

These files are hand-built, tiny, and redistributable. They were chosen to hit
the harness entry points deterministically instead of waiting for libFuzzer to
invent a `%PDF` header or a filter payload.

### `fuzz_pdf_parser`

| File | Why it is here |
|------|----------------|
| `minimal-one-page.bin` | Valid one-page PDF so mutation starts from a parseable document. |
| `truncated-xref.bin` | Objects present, xref/trailer cut off. |
| `cyclic-kids.bin` | `/Kids` array that references its own Pages node. |
| `wrong-generation.bin` | Xref and `/Root` generation 1 vs object header generation 0. |
| `bad-object-stream.bin` | `/ObjStm` with `/N 1000000000` and a compressed xref entry. |
| `unknown-encrypt-filter.bin` | `/Encrypt` names a Filter the reader does not implement. |
| `not-pdf.bin` | Non-PDF bytes (same class as `malformed-not-pdf.pdf`). |

`encrypted-without-password.pdf` is a preflight/operator golden only. It is not a
fuzz seed: `fuzz_pdf_parser`'s password callback always reports success, so an
encrypted corpus file would hang `-runs=0`. Changing that harness is deferred
until a `LOOP_BUILD_FUZZERS` compile-commands entry exists for clang-tidy.

The same malformed PDFs (except the valid page and the unknown-filter blob) are
also preflight goldens under `loop-preflight/testdata/fixtures/`.

### `fuzz_content_stream`

Raw page-content operators for `PDFPageContentProcessor::processForm`: path
ops, text ops, unbalanced `q`, an unknown operator, and whitespace.

### `fuzz_stream_filters`

First byte selects the filter (`FlateDecode`, `LZWDecode`, `ASCII85Decode`,
`ASCIIHexDecode`, `RunLengthDecode`). Payloads are a valid zlib "hello",
truncated Flate/LZW/ASCIIHex, a short ASCII85 string, and a RunLength EOD.

## MIC-326 / R-003 (JBIG2, `fuzz_images`)

Expand Down
2 changes: 2 additions & 0 deletions Fuzz/corpus/fuzz_content_stream/empty-whitespace.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@


Expand Down
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_content_stream/path-ops.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
q 1 0 0 1 0 0 cm 0 0 100 100 re f Q
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_content_stream/text-ops.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BT 72 720 Td (Hello) Tj ET
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_content_stream/unbalanced-q.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
q q q 0 0 10 10 re f
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_content_stream/unknown-operator.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 2 3 notanoperator 0 0 1 1 re f
Binary file added Fuzz/corpus/fuzz_pdf_parser/bad-object-stream.bin
Binary file not shown.
17 changes: 17 additions & 0 deletions Fuzz/corpus/fuzz_pdf_parser/cyclic-kids.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [2 0 R] /Count 1 >>
endobj
xref
0 3
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
trailer
<< /Size 3 /Root 1 0 R >>
startxref
115
%%EOF
21 changes: 21 additions & 0 deletions Fuzz/corpus/fuzz_pdf_parser/minimal-one-page.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_pdf_parser/not-pdf.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this is not a PDF file
10 changes: 10 additions & 0 deletions Fuzz/corpus/fuzz_pdf_parser/truncated-xref.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >>
endobj
25 changes: 25 additions & 0 deletions Fuzz/corpus/fuzz_pdf_parser/unknown-encrypt-filter.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >>
endobj
4 0 obj
<< /Filter /NotARealFilter /V 1 /R 2 >>
endobj
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000186 00000 n
trailer
<< /Size 5 /Root 1 0 R /Encrypt 4 0 R >>
startxref
241
%%EOF
21 changes: 21 additions & 0 deletions Fuzz/corpus/fuzz_pdf_parser/wrong-generation.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00001 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 1 R >>
startxref
186
%%EOF
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_stream_filters/ascii85-hello.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
87cURDZ>
1 change: 1 addition & 0 deletions Fuzz/corpus/fuzz_stream_filters/asciihex-truncated.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
68656c6c6
Binary file added Fuzz/corpus/fuzz_stream_filters/flate-hello.bin
Binary file not shown.
Binary file not shown.
Binary file added Fuzz/corpus/fuzz_stream_filters/lzw-truncated.bin
Binary file not shown.
Binary file added Fuzz/corpus/fuzz_stream_filters/runlength-eod.bin
Binary file not shown.
198 changes: 198 additions & 0 deletions Fuzz/corpus/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,204 @@
"sha256": "f1a4da02f8010927a81512a95fbfe007b0bfe9e3357d50d4e573a2f6b16cbb15",
"expected": "terminates-without-crash",
"minimized": true
},
{
"id": "minimal-one-page",
"path": "Fuzz/corpus/fuzz_pdf_parser/minimal-one-page.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "7d5371d3d9d5588cdb4c7851773488380aa8e9644001ad32ba5dad8084272746",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Tiny valid one-page PDF so mutation starts from a parseable document rather than empty input."
},
{
"id": "truncated-xref",
"path": "Fuzz/corpus/fuzz_pdf_parser/truncated-xref.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "80f923f2942e28fcc70e7c90e64d24c360ec45abe3f6044cfa377c93dcae1e67",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Header and objects with the xref/trailer cut off; reader must fail closed instead of hanging on a missing startxref."
},
{
"id": "cyclic-kids",
"path": "Fuzz/corpus/fuzz_pdf_parser/cyclic-kids.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "a6064012c9fc3ba84a7ea5fbc090ba25b924a3c8f0d665e042df6e6c4e561600",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Pages tree /Kids array that references itself; catalog parse already throws on page-tree cycles."
},
{
"id": "wrong-generation",
"path": "Fuzz/corpus/fuzz_pdf_parser/wrong-generation.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "8e7f5df3a3855ea1a3bb8fde4f646ff86832b86495b16d9777e9ea351760ca29",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Xref and /Root use generation 1; object header is 1 0 obj so permissive recovery cannot bind the catalog."
},
{
"id": "bad-object-stream",
"path": "Fuzz/corpus/fuzz_pdf_parser/bad-object-stream.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "1e44a341740f690a19232927211617523ac477850acf38914c71584e767b9c22",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "ObjStm with /N 1000000000 plus a compressed xref entry; object-stream budget must refuse it."
},
{
"id": "unknown-encrypt-filter",
"path": "Fuzz/corpus/fuzz_pdf_parser/unknown-encrypt-filter.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "f61f1aa3eda15214e11e64be1fe31809a12160ff36d6115133f63a13b9bf22d6",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Trailer /Encrypt names a Filter the reader does not implement; must throw, not hang."
},
{
"id": "not-pdf",
"path": "Fuzz/corpus/fuzz_pdf_parser/not-pdf.bin",
"harness": "fuzz_pdf_parser",
"origin": "synthetic",
"issue": 0,
"sha256": "9e67370e5d4000310a444bb02bc324cb68c4f58536b558603f9d8d5f308a70e1",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Non-PDF bytes so the parser corpus covers the same fail-closed path as malformed-not-pdf.pdf."
},
{
"id": "path-ops",
"path": "Fuzz/corpus/fuzz_content_stream/path-ops.bin",
"harness": "fuzz_content_stream",
"origin": "synthetic",
"issue": 0,
"sha256": "486902521e5a52e18ee452752e4975be4a5c88f208295f78016f070451e47c5c",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Graphics-state and path operators for PDFPageContentProcessor::processForm."
},
{
"id": "text-ops",
"path": "Fuzz/corpus/fuzz_content_stream/text-ops.bin",
"harness": "fuzz_content_stream",
"origin": "synthetic",
"issue": 0,
"sha256": "2e3fcec9e1530e391d662429366fe4f43616f833c38379f044f17ec7740e78fa",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Text-object operators without a font resource; processor must tolerate missing fonts."
},
{
"id": "unbalanced-q",
"path": "Fuzz/corpus/fuzz_content_stream/unbalanced-q.bin",
"harness": "fuzz_content_stream",
"origin": "synthetic",
"issue": 0,
"sha256": "3869b03df19ccbd551dc512ea9021af8a7013946fa6e6a675ac4eb8b54401e50",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Nested q without matching Q to exercise graphics-state nesting."
},
{
"id": "unknown-operator",
"path": "Fuzz/corpus/fuzz_content_stream/unknown-operator.bin",
"harness": "fuzz_content_stream",
"origin": "synthetic",
"issue": 0,
"sha256": "15739194e2eab7cc693a465afb744d0487ea95d1597178331dde2b04f942b73d",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Unknown operator mixed with a legal path so the tokenizer cannot skip the whole stream."
},
{
"id": "empty-whitespace",
"path": "Fuzz/corpus/fuzz_content_stream/empty-whitespace.bin",
"harness": "fuzz_content_stream",
"origin": "synthetic",
"issue": 0,
"sha256": "01f99895c51c5c8c34e193dc93f742e059fe25bbdb1b240d062c41c47a5f1ba1",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Whitespace-only content stream."
},
{
"id": "flate-hello",
"path": "Fuzz/corpus/fuzz_stream_filters/flate-hello.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "529495a527b0304cb854d0c64323142630aae37ccf61011c4efe5c5f5494523a",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 0 (FlateDecode) plus a valid zlib payload."
},
{
"id": "flate-truncated",
"path": "Fuzz/corpus/fuzz_stream_filters/flate-truncated.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "b4c81cc52ac5638f010b89360c795e94dd2e3d6b8a655df06674087b98031dca",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 0 plus a truncated zlib stream."
},
{
"id": "lzw-truncated",
"path": "Fuzz/corpus/fuzz_stream_filters/lzw-truncated.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "ccbe8b97cafaa6525ed5d67b5b0eab41d7f4a1e0424e73eac5df2b02e15ae34d",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 1 (LZWDecode) plus a short incomplete payload."
},
{
"id": "ascii85-hello",
"path": "Fuzz/corpus/fuzz_stream_filters/ascii85-hello.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "afb89fa1e1c651f97443650d8bb1e6efc8d25c39cbc61d20b06868d70d63e606",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 2 (ASCII85Decode) plus a short ASCII85 payload."
},
{
"id": "asciihex-truncated",
"path": "Fuzz/corpus/fuzz_stream_filters/asciihex-truncated.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "252758b82b396f15f44d3d354d19bbb9c9dcb2ed1b15f480ded8dea2b1d68119",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 3 (ASCIIHexDecode) plus odd-length hex without EOD."
},
{
"id": "runlength-eod",
"path": "Fuzz/corpus/fuzz_stream_filters/runlength-eod.bin",
"harness": "fuzz_stream_filters",
"origin": "synthetic",
"issue": 0,
"sha256": "474faade882292c4efe6c9b84873857cc289d7f8f1671932e77883719548909f",
"expected": "terminates-without-crash",
"minimized": true,
"notes": "Selector 4 (RunLengthDecode) plus one literal byte and EOD."
}
]
}
Loading
Loading