From 7445e0ac1ec1e3856b24411c1b356a5b2d64aaae Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 9 Jul 2024 16:38:02 +0100 Subject: [PATCH 1/4] Fix QPDF::setSuppressWarnings --- libqpdf/QPDF.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index e11e3006..548a307f 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -334,7 +334,7 @@ QPDF::setSuppressWarnings(bool val) void QPDF::setMaxWarnings(int val) { - m->suppress_warnings = val; + m->max_warnings = val; } void From 2e378d920d18714b3c4f60c82e7f9458b0dcf333 Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 9 Jul 2024 16:53:33 +0100 Subject: [PATCH 2/4] Add additional sanity check during xref reconstruction Check that xref table is not empty after recovery. Empty xref tables disable other sanity checks. --- libqpdf/QPDF.cc | 5 +++++ qpdf/qtest/qpdf/issue-147.out | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 548a307f..adb7d9a7 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -641,6 +641,11 @@ QPDF::reconstruct_xref(QPDFExc& e) throw damagedPDF("", 0, "unable to find trailer dictionary while recovering damaged file"); } + if (m->xref_table.empty()) { + // We cannot check for an empty xref table in parse because empty tables are valid when + // creating QPDF objects from JSON. + throw damagedPDF("", 0, "unable to find objects while recovering damaged file"); + } // We could iterate through the objects looking for streams and try to find objects inside of // them, but it's probably not worth the trouble. Acrobat can't recover files with any errors diff --git a/qpdf/qtest/qpdf/issue-147.out b/qpdf/qtest/qpdf/issue-147.out index e35bdbc2..9e766df1 100644 --- a/qpdf/qtest/qpdf/issue-147.out +++ b/qpdf/qtest/qpdf/issue-147.out @@ -4,4 +4,4 @@ WARNING: issue-147.pdf: can't find startxref WARNING: issue-147.pdf: Attempting to reconstruct cross-reference table WARNING: issue-147.pdf (trailer, offset 9): expected dictionary key but found non-name object; inserting key /QPDFFake1 WARNING: issue-147.pdf: ignoring object with impossibly large id 62 -qpdf: issue-147.pdf: unable to find /Root dictionary +qpdf: issue-147.pdf: unable to find objects while recovering damaged file From 7172dbd4e09df332ae93d2d7368419695cf72831 Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 9 Jul 2024 17:17:10 +0100 Subject: [PATCH 3/4] Add additional fuzzer test cases Add test case for oss-fuzz 15471 and 69977a --- fuzz/CMakeLists.txt | 1 + .../e0b87af81384c81c7f5c3d71dfe525daeddc1d19 | Bin 0 -> 1170 bytes fuzz/qpdf_extra/69977a.fuzz | Bin 0 -> 230121 bytes fuzz/qtest/fuzz.test | 4 ++-- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 fuzz/dct_fuzzer_seed_corpus/e0b87af81384c81c7f5c3d71dfe525daeddc1d19 create mode 100644 fuzz/qpdf_extra/69977a.fuzz diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index 299234bc..2a74424a 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -123,6 +123,7 @@ set(CORPUS_OTHER 69913.fuzz 69969.fuzz 69977.fuzz + 69977a.fuzz 70055.fuzz ) diff --git a/fuzz/dct_fuzzer_seed_corpus/e0b87af81384c81c7f5c3d71dfe525daeddc1d19 b/fuzz/dct_fuzzer_seed_corpus/e0b87af81384c81c7f5c3d71dfe525daeddc1d19 new file mode 100644 index 0000000000000000000000000000000000000000..a24492b1e7a03573bc812764b2201e313fd37422 GIT binary patch literal 1170 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 literal 0 HcmV?d00001 diff --git a/fuzz/qtest/fuzz.test b/fuzz/qtest/fuzz.test index ba7626f4..cd3f6837 100644 --- a/fuzz/qtest/fuzz.test +++ b/fuzz/qtest/fuzz.test @@ -13,7 +13,7 @@ my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; my @fuzzers = ( ['ascii85' => 1], - ['dct' => 1], + ['dct' => 2], ['flate' => 1], ['hex' => 1], ['json' => 40], @@ -21,7 +21,7 @@ my @fuzzers = ( ['pngpredictor' => 1], ['runlength' => 6], ['tiffpredictor' => 2], - ['qpdf' => 66], # increment when adding new files + ['qpdf' => 67], # increment when adding new files ); my $n_tests = 0; From 2b6500ea176b5d704439401f9cbd40ea733dfe13 Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 9 Jul 2024 20:55:51 +0100 Subject: [PATCH 4/4] In Pl_DCT::decompress refactor handling of corrupt data If throw_on_corrupt is set, use a custom implementation of libjeg's emit_message procedure to throw an exception when the first corrupt data warning is encountered. --- libqpdf/Pl_DCT.cc | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libqpdf/Pl_DCT.cc b/libqpdf/Pl_DCT.cc index 70597193..7073c614 100644 --- a/libqpdf/Pl_DCT.cc +++ b/libqpdf/Pl_DCT.cc @@ -35,6 +35,16 @@ error_handler(j_common_ptr cinfo) longjmp(jerr->jmpbuf, 1); } +static void +emit_message(j_common_ptr cinfo, int msg_level) +{ + if (msg_level == -1) { + auto* jerr = reinterpret_cast(cinfo->err); + jerr->msg = "Pl_DCT::decompress: JPEG data is corrupt"; + longjmp(jerr->jmpbuf, 1); + } +} + Pl_DCT::Members::Members() : action(a_decompress), buf("DCT compressed image") @@ -116,6 +126,9 @@ Pl_DCT::finish() cinfo_compress.err = jpeg_std_error(&(jerr.pub)); cinfo_decompress.err = jpeg_std_error(&(jerr.pub)); jerr.pub.error_exit = error_handler; + if (m->action == a_decompress && throw_on_corrupt_data) { + jerr.pub.emit_message = emit_message; + } bool error = false; // The jpeg library is a "C" library, so we use setjmp and longjmp for exception handling. @@ -319,11 +332,6 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) jpeg_buffer_src(cinfo, b); (void)jpeg_read_header(cinfo, TRUE); - if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) { - // err->num_warnings is the number of corrupt data warnings emitted. - // err->msg_code could also be the code of an informational message. - throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt"); - } (void)jpeg_calc_output_dimensions(cinfo); unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components); if (memory_limit > 0 && @@ -336,14 +344,10 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) (*cinfo->mem->alloc_sarray)(reinterpret_cast(cinfo), JPOOL_IMAGE, width, 1); (void)jpeg_start_decompress(cinfo); - while (cinfo->output_scanline < cinfo->output_height && - (!throw_on_corrupt_data || cinfo->err->num_warnings == 0)) { + while (cinfo->output_scanline < cinfo->output_height) { (void)jpeg_read_scanlines(cinfo, buffer, 1); getNext()->write(buffer[0], width * sizeof(buffer[0][0])); } (void)jpeg_finish_decompress(cinfo); - if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) { - throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt"); - } getNext()->finish(); }