Commit 5940c53fed7d49d70a3ddf272635fb41b16a7af2

Authored by m-holger
Committed by GitHub
2 parents bbe732c0 2bb9e06d

Merge pull request #1255 from m-holger/fuzz

Refactor xref reconstruction
fuzz/CMakeLists.txt
@@ -127,6 +127,7 @@ set(CORPUS_OTHER @@ -127,6 +127,7 @@ set(CORPUS_OTHER
127 69977a.fuzz 127 69977a.fuzz
128 69977b.fuzz 128 69977b.fuzz
129 69977c.fuzz 129 69977c.fuzz
  130 + 69977d.fuzz
130 70055.fuzz 131 70055.fuzz
131 70245.fuzz 132 70245.fuzz
132 70306.fuzz 133 70306.fuzz
fuzz/qpdf_extra/69977d.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 #include <qpdf/BufferInputSource.hh> 2 #include <qpdf/BufferInputSource.hh>
3 #include <qpdf/Pl_DCT.hh> 3 #include <qpdf/Pl_DCT.hh>
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
  5 +#include <qpdf/Pl_Flate.hh>
5 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
6 #include <qpdf/Pl_TIFFPredictor.hh> 7 #include <qpdf/Pl_TIFFPredictor.hh>
7 #include <qpdf/QPDF.hh> 8 #include <qpdf/QPDF.hh>
@@ -183,6 +184,7 @@ FuzzHelper::doChecks() @@ -183,6 +184,7 @@ FuzzHelper::doChecks()
183 184
184 Pl_PNGFilter::setMemoryLimit(1'000'000); 185 Pl_PNGFilter::setMemoryLimit(1'000'000);
185 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 186 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
  187 + Pl_Flate::setMemoryLimit(10'000'000);
186 188
187 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 189 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
188 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 190 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qtest/fuzz.test
@@ -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' => 72], # increment when adding new files 24 + ['qpdf' => 73], # increment when adding new files
25 ); 25 );
26 26
27 my $n_tests = 0; 27 my $n_tests = 0;
include/qpdf/Pl_Flate.hh
@@ -42,6 +42,11 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline @@ -42,6 +42,11 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
42 QPDF_DLL 42 QPDF_DLL
43 ~Pl_Flate() override; 43 ~Pl_Flate() override;
44 44
  45 + // Limit the memory used.
  46 + // NB This is a static option affecting all Pl_PNGFilter instances.
  47 + QPDF_DLL
  48 + static void setMemoryLimit(unsigned long long limit);
  49 +
45 QPDF_DLL 50 QPDF_DLL
46 void write(unsigned char const* data, size_t len) override; 51 void write(unsigned char const* data, size_t len) override;
47 QPDF_DLL 52 QPDF_DLL
@@ -87,6 +92,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline @@ -87,6 +92,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
87 action_e action; 92 action_e action;
88 bool initialized; 93 bool initialized;
89 void* zdata; 94 void* zdata;
  95 + unsigned long long written{0};
90 std::function<void(char const*, int)> callback; 96 std::function<void(char const*, int)> callback;
91 }; 97 };
92 98
libqpdf/Pl_Flate.cc
@@ -7,6 +7,11 @@ @@ -7,6 +7,11 @@
7 #include <qpdf/QIntC.hh> 7 #include <qpdf/QIntC.hh>
8 #include <qpdf/QUtil.hh> 8 #include <qpdf/QUtil.hh>
9 9
  10 +namespace
  11 +{
  12 + unsigned long long memory_limit{0};
  13 +} // namespace
  14 +
10 int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION; 15 int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION;
11 16
12 Pl_Flate::Members::Members(size_t out_bufsize, action_e action) : 17 Pl_Flate::Members::Members(size_t out_bufsize, action_e action) :
@@ -64,6 +69,12 @@ Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default) @@ -64,6 +69,12 @@ Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default)
64 } 69 }
65 70
66 void 71 void
  72 +Pl_Flate::setMemoryLimit(unsigned long long limit)
  73 +{
  74 + memory_limit = limit;
  75 +}
  76 +
  77 +void
67 Pl_Flate::setWarnCallback(std::function<void(char const*, int)> callback) 78 Pl_Flate::setWarnCallback(std::function<void(char const*, int)> callback)
68 { 79 {
69 m->callback = callback; 80 m->callback = callback;
@@ -170,6 +181,12 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush) @@ -170,6 +181,12 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
170 } 181 }
171 uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out); 182 uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out);
172 if (ready > 0) { 183 if (ready > 0) {
  184 + if (memory_limit) {
  185 + m->written += ready;
  186 + if (m->written > memory_limit) {
  187 + throw std::runtime_error("PL_Flate memory limit exceeded");
  188 + }
  189 + }
173 this->getNext()->write(m->outbuf.get(), ready); 190 this->getNext()->write(m->outbuf.get(), ready);
174 zstream.next_out = m->outbuf.get(); 191 zstream.next_out = m->outbuf.get();
175 zstream.avail_out = QIntC::to_uint(m->out_bufsize); 192 zstream.avail_out = QIntC::to_uint(m->out_bufsize);
libqpdf/QPDF.cc
@@ -572,18 +572,13 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -572,18 +572,13 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
572 m->file->seek(0, SEEK_END); 572 m->file->seek(0, SEEK_END);
573 qpdf_offset_t eof = m->file->tell(); 573 qpdf_offset_t eof = m->file->tell();
574 m->file->seek(0, SEEK_SET); 574 m->file->seek(0, SEEK_SET);
575 - qpdf_offset_t line_start = 0;  
576 - // Don't allow very long tokens here during recovery.  
577 - static size_t const MAX_LEN = 100; 575 + // Don't allow very long tokens here during recovery. All the interesting tokens are covered.
  576 + static size_t const MAX_LEN = 10;
578 while (m->file->tell() < eof) { 577 while (m->file->tell() < eof) {
579 - m->file->findAndSkipNextEOL();  
580 - qpdf_offset_t next_line_start = m->file->tell();  
581 - m->file->seek(line_start, SEEK_SET);  
582 QPDFTokenizer::Token t1 = readToken(m->file, MAX_LEN); 578 QPDFTokenizer::Token t1 = readToken(m->file, MAX_LEN);
583 qpdf_offset_t token_start = m->file->tell() - toO(t1.getValue().length()); 579 qpdf_offset_t token_start = m->file->tell() - toO(t1.getValue().length());
584 - if (token_start >= next_line_start) {  
585 - // don't process yet -- wait until we get to the line containing this token  
586 - } else if (t1.isInteger()) { 580 + if (t1.isInteger()) {
  581 + auto pos = m->file->tell();
587 QPDFTokenizer::Token t2 = readToken(m->file, MAX_LEN); 582 QPDFTokenizer::Token t2 = readToken(m->file, MAX_LEN);
588 if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) { 583 if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) {
589 int obj = QUtil::string_to_int(t1.getValue().c_str()); 584 int obj = QUtil::string_to_int(t1.getValue().c_str());
@@ -595,17 +590,19 @@ QPDF::reconstruct_xref(QPDFExc&amp; e) @@ -595,17 +590,19 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
595 "", 0, "ignoring object with impossibly large id " + std::to_string(obj))); 590 "", 0, "ignoring object with impossibly large id " + std::to_string(obj)));
596 } 591 }
597 } 592 }
  593 + m->file->seek(pos, SEEK_SET);
598 } else if (!m->trailer.isInitialized() && t1.isWord("trailer")) { 594 } else if (!m->trailer.isInitialized() && t1.isWord("trailer")) {
  595 + auto pos = m->file->tell();
599 QPDFObjectHandle t = readTrailer(); 596 QPDFObjectHandle t = readTrailer();
600 if (!t.isDictionary()) { 597 if (!t.isDictionary()) {
601 // Oh well. It was worth a try. 598 // Oh well. It was worth a try.
602 } else { 599 } else {
603 setTrailer(t); 600 setTrailer(t);
604 } 601 }
  602 + m->file->seek(pos, SEEK_SET);
605 } 603 }
606 check_warnings(); 604 check_warnings();
607 - m->file->seek(next_line_start, SEEK_SET);  
608 - line_start = next_line_start; 605 + m->file->findAndSkipNextEOL();
609 } 606 }
610 m->deleted_objects.clear(); 607 m->deleted_objects.clear();
611 608