Commit 671b6e2ecf883b71e098785b77dd877564c8d1b0

Authored by m-holger
1 parent ad3ecadf

Limit memory usage of Pl_Runlength during fuzzing

Fixes oss-fuzz case 394129398.

Issue arose from chaining multiple runlength filters and inflating a
compressed stream of ~100 bytes to several gigabytes.

There is no obvious fix without imposing an arbitrary implementation limit
and therefore potentially excluding valid PDF files.
fuzz/CMakeLists.txt
@@ -147,10 +147,12 @@ set(CORPUS_OTHER @@ -147,10 +147,12 @@ set(CORPUS_OTHER
147 369662293.fuzz 147 369662293.fuzz
148 369662293a.fuzz 148 369662293a.fuzz
149 376305073.fuzz 149 376305073.fuzz
  150 + 376305073a.fuzz
150 377977949.fuzz 151 377977949.fuzz
151 389339260.fuzz 152 389339260.fuzz
152 389974979.fuzz 153 389974979.fuzz
153 391974927.fuzz 154 391974927.fuzz
  155 + 394129398.fuzz
154 ) 156 )
155 157
156 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 158 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/qpdf_crypt_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFPageObjectHelper.hh> 10 #include <qpdf/QPDFPageObjectHelper.hh>
@@ -108,6 +109,7 @@ FuzzHelper::doChecks() @@ -108,6 +109,7 @@ FuzzHelper::doChecks()
108 Pl_DCT::setScanLimit(50); 109 Pl_DCT::setScanLimit(50);
109 110
110 Pl_PNGFilter::setMemoryLimit(1'000'000); 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
  112 + Pl_RunLength::setMemoryLimit(1'000'000);
111 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
112 Pl_Flate::setMemoryLimit(200'000); 114 Pl_Flate::setMemoryLimit(200'000);
113 115
fuzz/qpdf_crypt_insecure_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFPageObjectHelper.hh> 10 #include <qpdf/QPDFPageObjectHelper.hh>
@@ -108,6 +109,7 @@ FuzzHelper::doChecks() @@ -108,6 +109,7 @@ FuzzHelper::doChecks()
108 Pl_DCT::setScanLimit(50); 109 Pl_DCT::setScanLimit(50);
109 110
110 Pl_PNGFilter::setMemoryLimit(1'000'000); 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
  112 + Pl_RunLength::setMemoryLimit(1'000'000);
111 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
112 Pl_Flate::setMemoryLimit(200'000); 114 Pl_Flate::setMemoryLimit(200'000);
113 115
fuzz/qpdf_extra/376305073a.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_extra/394129398.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFPageObjectHelper.hh> 10 #include <qpdf/QPDFPageObjectHelper.hh>
@@ -106,6 +107,7 @@ FuzzHelper::doChecks() @@ -106,6 +107,7 @@ FuzzHelper::doChecks()
106 Pl_DCT::setScanLimit(50); 107 Pl_DCT::setScanLimit(50);
107 108
108 Pl_PNGFilter::setMemoryLimit(1'000'000); 109 Pl_PNGFilter::setMemoryLimit(1'000'000);
  110 + Pl_RunLength::setMemoryLimit(1'000'000);
109 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 111 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
110 Pl_Flate::setMemoryLimit(200'000); 112 Pl_Flate::setMemoryLimit(200'000);
111 113
fuzz/qpdf_lin_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFPageObjectHelper.hh> 10 #include <qpdf/QPDFPageObjectHelper.hh>
@@ -107,6 +108,7 @@ FuzzHelper::doChecks() @@ -107,6 +108,7 @@ FuzzHelper::doChecks()
107 Pl_DCT::setScanLimit(50); 108 Pl_DCT::setScanLimit(50);
108 109
109 Pl_PNGFilter::setMemoryLimit(1'000'000); 110 Pl_PNGFilter::setMemoryLimit(1'000'000);
  111 + Pl_RunLength::setMemoryLimit(1'000'000);
110 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 112 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
111 Pl_Flate::setMemoryLimit(200'000); 113 Pl_Flate::setMemoryLimit(200'000);
112 114
fuzz/qpdf_outlines_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFOutlineDocumentHelper.hh> 10 #include <qpdf/QPDFOutlineDocumentHelper.hh>
@@ -84,6 +85,7 @@ FuzzHelper::doChecks() @@ -84,6 +85,7 @@ FuzzHelper::doChecks()
84 Pl_DCT::setScanLimit(50); 85 Pl_DCT::setScanLimit(50);
85 86
86 Pl_PNGFilter::setMemoryLimit(1'000'000); 87 Pl_PNGFilter::setMemoryLimit(1'000'000);
  88 + Pl_RunLength::setMemoryLimit(1'000'000);
87 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 89 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
88 Pl_Flate::setMemoryLimit(200'000); 90 Pl_Flate::setMemoryLimit(200'000);
89 91
fuzz/qpdf_pages_fuzzer.cc
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/Pl_Discard.hh> 4 #include <qpdf/Pl_Discard.hh>
5 #include <qpdf/Pl_Flate.hh> 5 #include <qpdf/Pl_Flate.hh>
6 #include <qpdf/Pl_PNGFilter.hh> 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 #include <qpdf/Pl_TIFFPredictor.hh> 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 #include <qpdf/QPDF.hh> 9 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 10 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
@@ -105,6 +106,7 @@ FuzzHelper::doChecks() @@ -105,6 +106,7 @@ FuzzHelper::doChecks()
105 Pl_DCT::setScanLimit(50); 106 Pl_DCT::setScanLimit(50);
106 107
107 Pl_PNGFilter::setMemoryLimit(1'000'000); 108 Pl_PNGFilter::setMemoryLimit(1'000'000);
  109 + Pl_RunLength::setMemoryLimit(1'000'000);
108 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 110 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
109 Pl_Flate::setMemoryLimit(200'000); 111 Pl_Flate::setMemoryLimit(200'000);
110 112
fuzz/qtest/fuzz.test
@@ -11,7 +11,7 @@ my $td = new TestDriver(&#39;fuzz&#39;); @@ -11,7 +11,7 @@ my $td = new TestDriver(&#39;fuzz&#39;);
11 11
12 my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; 12 my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS";
13 13
14 -my $n_qpdf_files = 88; # increment when adding new files 14 +my $n_qpdf_files = 90; # increment when adding new files
15 15
16 my @fuzzers = ( 16 my @fuzzers = (
17 ['ascii85' => 1], 17 ['ascii85' => 1],
fuzz/runlength_fuzzer.cc
@@ -25,6 +25,7 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) : @@ -25,6 +25,7 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
25 void 25 void
26 FuzzHelper::doChecks() 26 FuzzHelper::doChecks()
27 { 27 {
  28 + Pl_RunLength::setMemoryLimit(1'000'000);
28 Pl_Discard discard; 29 Pl_Discard discard;
29 Pl_RunLength p("decode", &discard, Pl_RunLength::a_decode); 30 Pl_RunLength p("decode", &discard, Pl_RunLength::a_decode);
30 p.write(const_cast<unsigned char*>(data), size); 31 p.write(const_cast<unsigned char*>(data), size);
include/qpdf/Pl_Flate.hh
@@ -46,7 +46,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline @@ -46,7 +46,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
46 ~Pl_Flate() override; 46 ~Pl_Flate() override;
47 47
48 // Limit the memory used. 48 // Limit the memory used.
49 - // NB This is a static option affecting all Pl_PNGFilter instances. 49 + // NB This is a static option affecting all Pl_Flate instances.
50 QPDF_DLL 50 QPDF_DLL
51 static void setMemoryLimit(unsigned long long limit); 51 static void setMemoryLimit(unsigned long long limit);
52 52
include/qpdf/Pl_RunLength.hh
@@ -32,6 +32,11 @@ class QPDF_DLL_CLASS Pl_RunLength: public Pipeline @@ -32,6 +32,11 @@ class QPDF_DLL_CLASS Pl_RunLength: public Pipeline
32 QPDF_DLL 32 QPDF_DLL
33 ~Pl_RunLength() override; 33 ~Pl_RunLength() override;
34 34
  35 + // Limit the memory used.
  36 + // NB This is a static option affecting all Pl_RunLength instances.
  37 + QPDF_DLL
  38 + static void setMemoryLimit(unsigned long long limit);
  39 +
35 QPDF_DLL 40 QPDF_DLL
36 void write(unsigned char const* data, size_t len) override; 41 void write(unsigned char const* data, size_t len) override;
37 QPDF_DLL 42 QPDF_DLL
libqpdf/Pl_RunLength.cc
@@ -3,6 +3,11 @@ @@ -3,6 +3,11 @@
3 #include <qpdf/QTC.hh> 3 #include <qpdf/QTC.hh>
4 #include <qpdf/QUtil.hh> 4 #include <qpdf/QUtil.hh>
5 5
  6 +namespace
  7 +{
  8 + unsigned long long memory_limit{0};
  9 +} // namespace
  10 +
6 Pl_RunLength::Members::Members(action_e action) : 11 Pl_RunLength::Members::Members(action_e action) :
7 action(action) 12 action(action)
8 { 13 {
@@ -17,6 +22,12 @@ Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e acti @@ -17,6 +22,12 @@ Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e acti
17 } 22 }
18 } 23 }
19 24
  25 +void
  26 +Pl_RunLength::setMemoryLimit(unsigned long long limit)
  27 +{
  28 + memory_limit = limit;
  29 +}
  30 +
20 Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default) 31 Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default)
21 { 32 {
22 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer 33 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
@@ -67,6 +78,9 @@ Pl_RunLength::encode(unsigned char const* data, size_t len) @@ -67,6 +78,9 @@ Pl_RunLength::encode(unsigned char const* data, size_t len)
67 void 78 void
68 Pl_RunLength::decode(unsigned char const* data, size_t len) 79 Pl_RunLength::decode(unsigned char const* data, size_t len)
69 { 80 {
  81 + if (memory_limit && (len + m->out.size()) > memory_limit) {
  82 + throw std::runtime_error("Pl_RunLength memory limit exceeded");
  83 + }
70 m->out.reserve(len); 84 m->out.reserve(len);
71 for (size_t i = 0; i < len; ++i) { 85 for (size_t i = 0; i < len; ++i) {
72 unsigned char const& ch = data[i]; 86 unsigned char const& ch = data[i];
libqpdf/QPDFWriter.cc
@@ -1877,8 +1877,8 @@ QPDFWriter::generateID() @@ -1877,8 +1877,8 @@ QPDFWriter::generateID()
1877 if (m->deterministic_id_data.empty()) { 1877 if (m->deterministic_id_data.empty()) {
1878 QTC::TC("qpdf", "QPDFWriter deterministic with no data"); 1878 QTC::TC("qpdf", "QPDFWriter deterministic with no data");
1879 throw std::runtime_error("INTERNAL ERROR: QPDFWriter::generateID has no data for " 1879 throw std::runtime_error("INTERNAL ERROR: QPDFWriter::generateID has no data for "
1880 - "deterministic ID. This may happen if deterministic ID and "  
1881 - "file encryption are requested together."); 1880 + "deterministic ID. This may happen if deterministic ID "
  1881 + "and file encryption are requested together.");
1882 } 1882 }
1883 seed += m->deterministic_id_data; 1883 seed += m->deterministic_id_data;
1884 } else { 1884 } else {