Commit 3d569e217187acc206afe6a7d6fb942ac61c5400
Committed by
GitHub
Merge pull request #1221 from m-holger/fuzz
Refine handling of severely damaged files
Showing
27 changed files
with
42 additions
and
42 deletions
fuzz/CMakeLists.txt
fuzz/qpdf_extra/69857.fuzz
0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
libqpdf/QPDF.cc
| ... | ... | @@ -471,6 +471,10 @@ QPDF::parse(char const* password) |
| 471 | 471 | |
| 472 | 472 | initializeEncryption(); |
| 473 | 473 | m->parsed = true; |
| 474 | + if (m->xref_table.size() > 0 && !getRoot().getKey("/Pages").isDictionary()) { | |
| 475 | + // QPDFs created from JSON have an empty xref table and no root object yet. | |
| 476 | + throw damagedPDF("", 0, "unable to find page tree"); | |
| 477 | + } | |
| 474 | 478 | } |
| 475 | 479 | |
| 476 | 480 | void |
| ... | ... | @@ -543,6 +547,9 @@ QPDF::reconstruct_xref(QPDFExc& e) |
| 543 | 547 | |
| 544 | 548 | m->file->seek(0, SEEK_END); |
| 545 | 549 | qpdf_offset_t eof = m->file->tell(); |
| 550 | + // Sanity check on object ids. All objects must appear in xref table / stream. In all realistic | |
| 551 | + // scenarios at leat 3 bytes are required. | |
| 552 | + auto max_obj_id = eof / 3; | |
| 546 | 553 | m->file->seek(0, SEEK_SET); |
| 547 | 554 | qpdf_offset_t line_start = 0; |
| 548 | 555 | // Don't allow very long tokens here during recovery. |
| ... | ... | @@ -560,7 +567,12 @@ QPDF::reconstruct_xref(QPDFExc& e) |
| 560 | 567 | if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) { |
| 561 | 568 | int obj = QUtil::string_to_int(t1.getValue().c_str()); |
| 562 | 569 | int gen = QUtil::string_to_int(t2.getValue().c_str()); |
| 563 | - insertReconstructedXrefEntry(obj, token_start, gen); | |
| 570 | + if (obj <= max_obj_id) { | |
| 571 | + insertReconstructedXrefEntry(obj, token_start, gen); | |
| 572 | + } else { | |
| 573 | + warn(damagedPDF( | |
| 574 | + "", 0, "ignoring object with impossibly large id " + std::to_string(obj))); | |
| 575 | + } | |
| 564 | 576 | } |
| 565 | 577 | } else if (!m->trailer.isInitialized() && t1.isWord("trailer")) { |
| 566 | 578 | QPDFObjectHandle t = readTrailer(); | ... | ... |
libqpdf/QPDFWriter.cc
| ... | ... | @@ -1121,7 +1121,6 @@ QPDFWriter::enqueueObject(QPDFObjectHandle object) |
| 1121 | 1121 | } else if (obj.renumber == -1) { |
| 1122 | 1122 | // This can happen if a specially constructed file indicates that an object stream is |
| 1123 | 1123 | // inside itself. |
| 1124 | - QTC::TC("qpdf", "QPDFWriter ignore self-referential object stream"); | |
| 1125 | 1124 | } |
| 1126 | 1125 | return; |
| 1127 | 1126 | } else if (!m->linearized) { | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -277,7 +277,6 @@ QPDF ignore first extra space in xref entry 0 |
| 277 | 277 | QPDF ignore second extra space in xref entry 0 |
| 278 | 278 | QPDF ignore length error xref entry 0 |
| 279 | 279 | QPDF_encryption pad short parameter 0 |
| 280 | -QPDFWriter ignore self-referential object stream 0 | |
| 281 | 280 | QPDFObjectHandle found old angle 1 |
| 282 | 281 | QPDF_Stream special filters 3 |
| 283 | 282 | QPDFTokenizer block long token 0 | ... | ... |
qpdf/qtest/invalid-objects.test
| ... | ... | @@ -19,7 +19,7 @@ my $n_tests = 4; |
| 19 | 19 | $td->runtest("closed input source", |
| 20 | 20 | {$td->COMMAND => "test_driver 73 minimal.pdf"}, |
| 21 | 21 | {$td->FILE => "test73.out", |
| 22 | - $td->EXIT_STATUS => 2}, | |
| 22 | + $td->EXIT_STATUS => 0}, | |
| 23 | 23 | $td->NORMALIZE_NEWLINES); |
| 24 | 24 | |
| 25 | 25 | $td->runtest("empty object", | ... | ... |
qpdf/qtest/qpdf/bad11-recover.out
| 1 | 1 | WARNING: bad11.pdf: file is damaged |
| 2 | 2 | WARNING: bad11.pdf (trailer, offset 905): /Prev key in trailer dictionary is not an integer |
| 3 | 3 | WARNING: bad11.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad11.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/bad12-recover.out
qpdf/qtest/qpdf/bad12.out
qpdf/qtest/qpdf/bad2-recover.out
| 1 | 1 | WARNING: bad2.pdf: file is damaged |
| 2 | 2 | WARNING: bad2.pdf: can't find startxref |
| 3 | 3 | WARNING: bad2.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad2.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/bad3-recover.out
| 1 | 1 | WARNING: bad3.pdf: file is damaged |
| 2 | 2 | WARNING: bad3.pdf (offset 542): xref not found |
| 3 | 3 | WARNING: bad3.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad3.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/bad4-recover.out
| 1 | 1 | WARNING: bad4.pdf: file is damaged |
| 2 | 2 | WARNING: bad4.pdf (xref table, offset 547): xref syntax invalid |
| 3 | 3 | WARNING: bad4.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad4.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/bad5-recover.out
| 1 | 1 | WARNING: bad5.pdf: file is damaged |
| 2 | 2 | WARNING: bad5.pdf (xref table, offset 591): invalid xref entry (obj=2) |
| 3 | 3 | WARNING: bad5.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad5.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/bad6-recover.out
qpdf/qtest/qpdf/bad6.out
qpdf/qtest/qpdf/bad8-recover.out
| 1 | 1 | WARNING: bad8.pdf: file is damaged |
| 2 | 2 | WARNING: bad8.pdf (offset 543): xref not found |
| 3 | 3 | WARNING: bad8.pdf: Attempting to reconstruct cross-reference table |
| 4 | +WARNING: bad8.pdf (object 2 0, offset 128): expected endobj | |
| 4 | 5 | /QTest is implicit |
| 5 | 6 | /QTest is direct and has type null (2) |
| 6 | 7 | /QTest is null | ... | ... |
qpdf/qtest/qpdf/fuzz-16214.out
| ... | ... | @@ -11,8 +11,8 @@ WARNING: fuzz-16214.pdf (object 1 0, offset 7189): expected n n obj |
| 11 | 11 | WARNING: fuzz-16214.pdf: Attempting to reconstruct cross-reference table |
| 12 | 12 | WARNING: fuzz-16214.pdf (offset 7207): error decoding stream data for object 2 0: stream inflate: inflate: data: invalid code lengths set |
| 13 | 13 | WARNING: fuzz-16214.pdf (offset 7207): getStreamData called on unfilterable stream |
| 14 | -WARNING: fuzz-16214.pdf (object 11 0, offset 11551): supposed object stream 5 has wrong type | |
| 15 | -WARNING: fuzz-16214.pdf (object 11 0, offset 11551): object stream 5 has incorrect keys | |
| 14 | +WARNING: fuzz-16214.pdf (object 8 0, offset 7207): supposed object stream 5 has wrong type | |
| 15 | +WARNING: fuzz-16214.pdf (object 8 0, offset 7207): object stream 5 has incorrect keys | |
| 16 | 16 | WARNING: fuzz-16214.pdf (object 21 0, offset 3639): expected endstream |
| 17 | 17 | WARNING: fuzz-16214.pdf (object 21 0, offset 3112): attempting to recover stream length |
| 18 | 18 | WARNING: fuzz-16214.pdf (object 21 0, offset 3112): recovered stream length: 340 | ... | ... |
qpdf/qtest/qpdf/issue-119.out
| 1 | -WARNING: issue-119.pdf (object 4 0, offset 298): expected dictionary key but found non-name object; inserting key /QPDFFake1 | |
| 2 | -WARNING: issue-119.pdf (object 4 0, offset 298): expected dictionary key but found non-name object; inserting key /QPDFFake2 | |
| 3 | -qpdf: operation succeeded with warnings; resulting file may have some problems | |
| 1 | +qpdf: issue-119.pdf: unable to find page tree | ... | ... |
qpdf/qtest/qpdf/issue-120.out
| 1 | -WARNING: issue-120.pdf (offset 85): loop detected resolving object 3 0 | |
| 2 | -WARNING: issue-120.pdf (object 6 0, offset 85): supposed object stream 3 is not a stream | |
| 3 | -WARNING: issue-120.pdf: file is damaged | |
| 4 | -WARNING: issue-120.pdf (object 8 10, offset 26880): expected n n obj | |
| 5 | -WARNING: issue-120.pdf: Attempting to reconstruct cross-reference table | |
| 6 | -WARNING: issue-120.pdf: object 8 10 not found in file after regenerating cross reference table | |
| 7 | -qpdf: operation succeeded with warnings; resulting file may have some problems | |
| 1 | +qpdf: issue-120.pdf: unable to find page tree | ... | ... |
qpdf/qtest/qpdf/issue-143.out
| ... | ... | @@ -14,6 +14,4 @@ WARNING: issue-143.pdf (object 1 0, offset 21): stream dictionary lacks /Length |
| 14 | 14 | WARNING: issue-143.pdf (object 1 0, offset 84): attempting to recover stream length |
| 15 | 15 | WARNING: issue-143.pdf (object 1 0, offset 84): recovered stream length: 606 |
| 16 | 16 | WARNING: issue-143.pdf object stream 1 (object 2 0, offset 33): expected dictionary key but found non-name object; inserting key /QPDFFake1 |
| 17 | -WARNING: issue-143.pdf: object 0/0 has unexpected xref entry type | |
| 18 | -WARNING: issue-143.pdf (object 2 0, offset 84): supposed object stream 12336 is not a stream | |
| 19 | -qpdf: operation succeeded with warnings; resulting file may have some problems | |
| 17 | +qpdf: issue-143.pdf: unable to find page tree | ... | ... |
qpdf/qtest/qpdf/issue-147.out
| ... | ... | @@ -3,6 +3,5 @@ WARNING: issue-147.pdf: file is damaged |
| 3 | 3 | WARNING: issue-147.pdf: can't find startxref |
| 4 | 4 | WARNING: issue-147.pdf: Attempting to reconstruct cross-reference table |
| 5 | 5 | WARNING: issue-147.pdf (trailer, offset 9): expected dictionary key but found non-name object; inserting key /QPDFFake1 |
| 6 | -WARNING: issue-147.pdf (object 62 0, offset 88): expected endobj | |
| 7 | -WARNING: issue-147.pdf (trailer, offset 90): invalid /ID in trailer dictionary | |
| 8 | -qpdf: issue-147.pdf: invalid password | |
| 6 | +WARNING: issue-147.pdf: ignoring object with impossibly large id 62 | |
| 7 | +qpdf: issue-147.pdf: unable to find /Root dictionary | ... | ... |
qpdf/qtest/qpdf/issue-51.out
| ... | ... | @@ -2,15 +2,4 @@ WARNING: issue-51.pdf: can't find PDF header |
| 2 | 2 | WARNING: issue-51.pdf: reported number of objects (0) is not one plus the highest object number (8) |
| 3 | 3 | WARNING: issue-51.pdf (object 7 0, offset 476): dictionary has duplicated key /0000; last occurrence overrides earlier ones |
| 4 | 4 | WARNING: issue-51.pdf (object 7 0, offset 553): expected endobj |
| 5 | -WARNING: issue-51.pdf (object 1 0, offset 236): dictionary has duplicated key /00000000; last occurrence overrides earlier ones | |
| 6 | -WARNING: issue-51.pdf (object 1 0, offset 359): expected endobj | |
| 7 | -WARNING: issue-51.pdf (offset 70): loop detected resolving object 2 0 | |
| 8 | -WARNING: issue-51.pdf (object 2 0, offset 26): stream dictionary lacks /Length key | |
| 9 | -WARNING: issue-51.pdf (object 2 0, offset 71): attempting to recover stream length | |
| 10 | -WARNING: issue-51.pdf (object 2 0, offset 71): unable to recover stream data; treating stream as empty | |
| 11 | -WARNING: issue-51.pdf (object 2 0, offset 977): expected endobj | |
| 12 | -WARNING: issue-51.pdf (object 3 0): object has offset 0 | |
| 13 | -WARNING: issue-51.pdf (object 4 0): object has offset 0 | |
| 14 | -WARNING: issue-51.pdf (object 5 0): object has offset 0 | |
| 15 | -WARNING: issue-51.pdf (object 6 0): object has offset 0 | |
| 16 | -WARNING: issue-51.pdf (object 8 0): object has offset 0 | |
| 5 | +issue-51.pdf: unable to find page tree | ... | ... |
qpdf/qtest/qpdf/job-api.out
| ... | ... | @@ -21,6 +21,7 @@ captured stderr |
| 21 | 21 | WARNING: bad2.pdf: file is damaged |
| 22 | 22 | WARNING: bad2.pdf: can't find startxref |
| 23 | 23 | WARNING: bad2.pdf: Attempting to reconstruct cross-reference table |
| 24 | +WARNING: bad2.pdf (object 2 0, offset 128): expected endobj | |
| 24 | 25 | WARNING: bad2.pdf (object 4 0, offset 389): expected endobj |
| 25 | 26 | qpdf: operation succeeded with warnings |
| 26 | 27 | test 84 done | ... | ... |
qpdf/qtest/qpdf/test73.out
| 1 | 1 | getRoot: attempted to dereference an uninitialized QPDFObjectHandle |
| 2 | -WARNING: closed input source: object 1/0: error reading object: QPDF operation attempted on a QPDF object with no input source. QPDF operations are invalid before processFile (or another process method) or after closeInputSource | |
| 3 | -closed input source: unable to find /Root dictionary | |
| 2 | +WARNING: closed input source: object 4/0: error reading object: QPDF operation attempted on a QPDF object with no input source. QPDF operations are invalid before processFile (or another process method) or after closeInputSource | |
| 3 | +test 73 done | ... | ... |
qpdf/qtest/specific-bugs.test
| ... | ... | @@ -16,19 +16,19 @@ my $td = new TestDriver('specific-bugs'); |
| 16 | 16 | |
| 17 | 17 | # The number is the github issue number in which the bug was reported. |
| 18 | 18 | my @bug_tests = ( |
| 19 | - ["51", "resolve loop", 3], | |
| 19 | + ["51", "resolve loop", 2], | |
| 20 | 20 | ["99", "object 0", 2], |
| 21 | 21 | ["99b", "object 0", 2], |
| 22 | 22 | ["100", "xref reconstruction loop", 2], |
| 23 | 23 | ["101", "resolve for exception text", 2], |
| 24 | 24 | ["117", "other infinite loop", 3], |
| 25 | 25 | ["118", "other infinite loop", 2], |
| 26 | - ["119", "other infinite loop", 3], | |
| 27 | - ["120", "other infinite loop", 3], | |
| 26 | + ["119", "other infinite loop", 2], | |
| 27 | + ["120", "other infinite loop", 2], | |
| 28 | 28 | ["106", "zlib data error", 3], |
| 29 | 29 | ["141a", "/W entry size 0", 2], |
| 30 | 30 | ["141b", "/W entry size 0", 2], |
| 31 | - ["143", "self-referential ostream", 3, "--preserve-unreferenced"], | |
| 31 | + ["143", "self-referential ostream", 2, "--preserve-unreferenced"], | |
| 32 | 32 | ["146", "very deeply nested array", 2], |
| 33 | 33 | ["147", "previously caused memory error", 2], |
| 34 | 34 | ["148", "free memory on bad flate", 2], | ... | ... |
qpdf/test_driver.cc