Commit 1ec5d3daa871d7d983dbdbb83b2e6ac4fc64486e

Authored by m-holger
Committed by GitHub
2 parents 9ffa2014 2b6500ea

Merge pull request #1236 from m-holger/fuzz

Add additional xref reconstruction sanity checks and fuzz test cases
fuzz/CMakeLists.txt
@@ -123,6 +123,7 @@ set(CORPUS_OTHER @@ -123,6 +123,7 @@ set(CORPUS_OTHER
123 69913.fuzz 123 69913.fuzz
124 69969.fuzz 124 69969.fuzz
125 69977.fuzz 125 69977.fuzz
  126 + 69977a.fuzz
126 70055.fuzz 127 70055.fuzz
127 ) 128 )
128 129
fuzz/dct_fuzzer_seed_corpus/e0b87af81384c81c7f5c3d71dfe525daeddc1d19 0 → 100644
No preview for this file type
fuzz/qpdf_extra/69977a.fuzz 0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
@@ -13,7 +13,7 @@ my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; @@ -13,7 +13,7 @@ my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS";
13 13
14 my @fuzzers = ( 14 my @fuzzers = (
15 ['ascii85' => 1], 15 ['ascii85' => 1],
16 - ['dct' => 1], 16 + ['dct' => 2],
17 ['flate' => 1], 17 ['flate' => 1],
18 ['hex' => 1], 18 ['hex' => 1],
19 ['json' => 40], 19 ['json' => 40],
@@ -21,7 +21,7 @@ my @fuzzers = ( @@ -21,7 +21,7 @@ my @fuzzers = (
21 ['pngpredictor' => 1], 21 ['pngpredictor' => 1],
22 ['runlength' => 6], 22 ['runlength' => 6],
23 ['tiffpredictor' => 2], 23 ['tiffpredictor' => 2],
24 - ['qpdf' => 66], # increment when adding new files 24 + ['qpdf' => 67], # increment when adding new files
25 ); 25 );
26 26
27 my $n_tests = 0; 27 my $n_tests = 0;
libqpdf/Pl_DCT.cc
@@ -35,6 +35,16 @@ error_handler(j_common_ptr cinfo) @@ -35,6 +35,16 @@ error_handler(j_common_ptr cinfo)
35 longjmp(jerr->jmpbuf, 1); 35 longjmp(jerr->jmpbuf, 1);
36 } 36 }
37 37
  38 +static void
  39 +emit_message(j_common_ptr cinfo, int msg_level)
  40 +{
  41 + if (msg_level == -1) {
  42 + auto* jerr = reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
  43 + jerr->msg = "Pl_DCT::decompress: JPEG data is corrupt";
  44 + longjmp(jerr->jmpbuf, 1);
  45 + }
  46 +}
  47 +
38 Pl_DCT::Members::Members() : 48 Pl_DCT::Members::Members() :
39 action(a_decompress), 49 action(a_decompress),
40 buf("DCT compressed image") 50 buf("DCT compressed image")
@@ -116,6 +126,9 @@ Pl_DCT::finish() @@ -116,6 +126,9 @@ Pl_DCT::finish()
116 cinfo_compress.err = jpeg_std_error(&(jerr.pub)); 126 cinfo_compress.err = jpeg_std_error(&(jerr.pub));
117 cinfo_decompress.err = jpeg_std_error(&(jerr.pub)); 127 cinfo_decompress.err = jpeg_std_error(&(jerr.pub));
118 jerr.pub.error_exit = error_handler; 128 jerr.pub.error_exit = error_handler;
  129 + if (m->action == a_decompress && throw_on_corrupt_data) {
  130 + jerr.pub.emit_message = emit_message;
  131 + }
119 132
120 bool error = false; 133 bool error = false;
121 // The jpeg library is a "C" library, so we use setjmp and longjmp for exception handling. 134 // 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) @@ -319,11 +332,6 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
319 jpeg_buffer_src(cinfo, b); 332 jpeg_buffer_src(cinfo, b);
320 333
321 (void)jpeg_read_header(cinfo, TRUE); 334 (void)jpeg_read_header(cinfo, TRUE);
322 - if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) {  
323 - // err->num_warnings is the number of corrupt data warnings emitted.  
324 - // err->msg_code could also be the code of an informational message.  
325 - throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt");  
326 - }  
327 (void)jpeg_calc_output_dimensions(cinfo); 335 (void)jpeg_calc_output_dimensions(cinfo);
328 unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components); 336 unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components);
329 if (memory_limit > 0 && 337 if (memory_limit > 0 &&
@@ -336,14 +344,10 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) @@ -336,14 +344,10 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
336 (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1); 344 (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
337 345
338 (void)jpeg_start_decompress(cinfo); 346 (void)jpeg_start_decompress(cinfo);
339 - while (cinfo->output_scanline < cinfo->output_height &&  
340 - (!throw_on_corrupt_data || cinfo->err->num_warnings == 0)) { 347 + while (cinfo->output_scanline < cinfo->output_height) {
341 (void)jpeg_read_scanlines(cinfo, buffer, 1); 348 (void)jpeg_read_scanlines(cinfo, buffer, 1);
342 getNext()->write(buffer[0], width * sizeof(buffer[0][0])); 349 getNext()->write(buffer[0], width * sizeof(buffer[0][0]));
343 } 350 }
344 (void)jpeg_finish_decompress(cinfo); 351 (void)jpeg_finish_decompress(cinfo);
345 - if (throw_on_corrupt_data && cinfo->err->num_warnings > 0) {  
346 - throw std::runtime_error("Pl_DCT::decompress: JPEG data is corrupt");  
347 - }  
348 getNext()->finish(); 352 getNext()->finish();
349 } 353 }
libqpdf/QPDF.cc
@@ -334,7 +334,7 @@ QPDF::setSuppressWarnings(bool val) @@ -334,7 +334,7 @@ QPDF::setSuppressWarnings(bool val)
334 void 334 void
335 QPDF::setMaxWarnings(int val) 335 QPDF::setMaxWarnings(int val)
336 { 336 {
337 - m->suppress_warnings = val; 337 + m->max_warnings = val;
338 } 338 }
339 339
340 void 340 void
@@ -641,6 +641,11 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -641,6 +641,11 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
641 641
642 throw damagedPDF("", 0, "unable to find trailer dictionary while recovering damaged file"); 642 throw damagedPDF("", 0, "unable to find trailer dictionary while recovering damaged file");
643 } 643 }
  644 + if (m->xref_table.empty()) {
  645 + // We cannot check for an empty xref table in parse because empty tables are valid when
  646 + // creating QPDF objects from JSON.
  647 + throw damagedPDF("", 0, "unable to find objects while recovering damaged file");
  648 + }
644 649
645 // We could iterate through the objects looking for streams and try to find objects inside of 650 // We could iterate through the objects looking for streams and try to find objects inside of
646 // them, but it's probably not worth the trouble. Acrobat can't recover files with any errors 651 // them, but it's probably not worth the trouble. Acrobat can't recover files with any errors
qpdf/qtest/qpdf/issue-147.out
@@ -4,4 +4,4 @@ WARNING: issue-147.pdf: can&#39;t find startxref @@ -4,4 +4,4 @@ WARNING: issue-147.pdf: can&#39;t find startxref
4 WARNING: issue-147.pdf: Attempting to reconstruct cross-reference table 4 WARNING: issue-147.pdf: Attempting to reconstruct cross-reference table
5 WARNING: issue-147.pdf (trailer, offset 9): expected dictionary key but found non-name object; inserting key /QPDFFake1 5 WARNING: issue-147.pdf (trailer, offset 9): expected dictionary key but found non-name object; inserting key /QPDFFake1
6 WARNING: issue-147.pdf: ignoring object with impossibly large id 62 6 WARNING: issue-147.pdf: ignoring object with impossibly large id 62
7 -qpdf: issue-147.pdf: unable to find /Root dictionary 7 +qpdf: issue-147.pdf: unable to find objects while recovering damaged file