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 127 69977a.fuzz
128 128 69977b.fuzz
129 129 69977c.fuzz
  130 + 69977d.fuzz
130 131 70055.fuzz
131 132 70245.fuzz
132 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 2 #include <qpdf/BufferInputSource.hh>
3 3 #include <qpdf/Pl_DCT.hh>
4 4 #include <qpdf/Pl_Discard.hh>
  5 +#include <qpdf/Pl_Flate.hh>
5 6 #include <qpdf/Pl_PNGFilter.hh>
6 7 #include <qpdf/Pl_TIFFPredictor.hh>
7 8 #include <qpdf/QPDF.hh>
... ... @@ -183,6 +184,7 @@ FuzzHelper::doChecks()
183 184  
184 185 Pl_PNGFilter::setMemoryLimit(1'000'000);
185 186 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
  187 + Pl_Flate::setMemoryLimit(10'000'000);
186 188  
187 189 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
188 190 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
... ...
fuzz/qtest/fuzz.test
... ... @@ -21,7 +21,7 @@ my @fuzzers = (
21 21 ['pngpredictor' => 1],
22 22 ['runlength' => 6],
23 23 ['tiffpredictor' => 2],
24   - ['qpdf' => 72], # increment when adding new files
  24 + ['qpdf' => 73], # increment when adding new files
25 25 );
26 26  
27 27 my $n_tests = 0;
... ...
include/qpdf/Pl_Flate.hh
... ... @@ -42,6 +42,11 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
42 42 QPDF_DLL
43 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 50 QPDF_DLL
46 51 void write(unsigned char const* data, size_t len) override;
47 52 QPDF_DLL
... ... @@ -87,6 +92,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
87 92 action_e action;
88 93 bool initialized;
89 94 void* zdata;
  95 + unsigned long long written{0};
90 96 std::function<void(char const*, int)> callback;
91 97 };
92 98  
... ...
libqpdf/Pl_Flate.cc
... ... @@ -7,6 +7,11 @@
7 7 #include <qpdf/QIntC.hh>
8 8 #include <qpdf/QUtil.hh>
9 9  
  10 +namespace
  11 +{
  12 + unsigned long long memory_limit{0};
  13 +} // namespace
  14 +
10 15 int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION;
11 16  
12 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 69 }
65 70  
66 71 void
  72 +Pl_Flate::setMemoryLimit(unsigned long long limit)
  73 +{
  74 + memory_limit = limit;
  75 +}
  76 +
  77 +void
67 78 Pl_Flate::setWarnCallback(std::function<void(char const*, int)> callback)
68 79 {
69 80 m->callback = callback;
... ... @@ -170,6 +181,12 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
170 181 }
171 182 uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out);
172 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 190 this->getNext()->write(m->outbuf.get(), ready);
174 191 zstream.next_out = m->outbuf.get();
175 192 zstream.avail_out = QIntC::to_uint(m->out_bufsize);
... ...
libqpdf/QPDF.cc
... ... @@ -572,18 +572,13 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
572 572 m->file->seek(0, SEEK_END);
573 573 qpdf_offset_t eof = m->file->tell();
574 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 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 578 QPDFTokenizer::Token t1 = readToken(m->file, MAX_LEN);
583 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 582 QPDFTokenizer::Token t2 = readToken(m->file, MAX_LEN);
588 583 if ((t2.isInteger()) && (readToken(m->file, MAX_LEN).isWord("obj"))) {
589 584 int obj = QUtil::string_to_int(t1.getValue().c_str());
... ... @@ -595,17 +590,19 @@ QPDF::reconstruct_xref(QPDFExc&amp; e)
595 590 "", 0, "ignoring object with impossibly large id " + std::to_string(obj)));
596 591 }
597 592 }
  593 + m->file->seek(pos, SEEK_SET);
598 594 } else if (!m->trailer.isInitialized() && t1.isWord("trailer")) {
  595 + auto pos = m->file->tell();
599 596 QPDFObjectHandle t = readTrailer();
600 597 if (!t.isDictionary()) {
601 598 // Oh well. It was worth a try.
602 599 } else {
603 600 setTrailer(t);
604 601 }
  602 + m->file->seek(pos, SEEK_SET);
605 603 }
606 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 607 m->deleted_objects.clear();
611 608  
... ...