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 147 369662293.fuzz
148 148 369662293a.fuzz
149 149 376305073.fuzz
  150 + 376305073a.fuzz
150 151 377977949.fuzz
151 152 389339260.fuzz
152 153 389974979.fuzz
153 154 391974927.fuzz
  155 + 394129398.fuzz
154 156 )
155 157  
156 158 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
... ...
fuzz/qpdf_crypt_fuzzer.cc
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFPageObjectHelper.hh>
... ... @@ -108,6 +109,7 @@ FuzzHelper::doChecks()
108 109 Pl_DCT::setScanLimit(50);
109 110  
110 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
  112 + Pl_RunLength::setMemoryLimit(1'000'000);
111 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
112 114 Pl_Flate::setMemoryLimit(200'000);
113 115  
... ...
fuzz/qpdf_crypt_insecure_fuzzer.cc
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFPageObjectHelper.hh>
... ... @@ -108,6 +109,7 @@ FuzzHelper::doChecks()
108 109 Pl_DCT::setScanLimit(50);
109 110  
110 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
  112 + Pl_RunLength::setMemoryLimit(1'000'000);
111 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
112 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 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFPageObjectHelper.hh>
... ... @@ -106,6 +107,7 @@ FuzzHelper::doChecks()
106 107 Pl_DCT::setScanLimit(50);
107 108  
108 109 Pl_PNGFilter::setMemoryLimit(1'000'000);
  110 + Pl_RunLength::setMemoryLimit(1'000'000);
109 111 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
110 112 Pl_Flate::setMemoryLimit(200'000);
111 113  
... ...
fuzz/qpdf_lin_fuzzer.cc
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFPageObjectHelper.hh>
... ... @@ -107,6 +108,7 @@ FuzzHelper::doChecks()
107 108 Pl_DCT::setScanLimit(50);
108 109  
109 110 Pl_PNGFilter::setMemoryLimit(1'000'000);
  111 + Pl_RunLength::setMemoryLimit(1'000'000);
110 112 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
111 113 Pl_Flate::setMemoryLimit(200'000);
112 114  
... ...
fuzz/qpdf_outlines_fuzzer.cc
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFOutlineDocumentHelper.hh>
... ... @@ -84,6 +85,7 @@ FuzzHelper::doChecks()
84 85 Pl_DCT::setScanLimit(50);
85 86  
86 87 Pl_PNGFilter::setMemoryLimit(1'000'000);
  88 + Pl_RunLength::setMemoryLimit(1'000'000);
87 89 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
88 90 Pl_Flate::setMemoryLimit(200'000);
89 91  
... ...
fuzz/qpdf_pages_fuzzer.cc
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/Pl_Discard.hh>
5 5 #include <qpdf/Pl_Flate.hh>
6 6 #include <qpdf/Pl_PNGFilter.hh>
  7 +#include <qpdf/Pl_RunLength.hh>
7 8 #include <qpdf/Pl_TIFFPredictor.hh>
8 9 #include <qpdf/QPDF.hh>
9 10 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
... ... @@ -105,6 +106,7 @@ FuzzHelper::doChecks()
105 106 Pl_DCT::setScanLimit(50);
106 107  
107 108 Pl_PNGFilter::setMemoryLimit(1'000'000);
  109 + Pl_RunLength::setMemoryLimit(1'000'000);
108 110 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
109 111 Pl_Flate::setMemoryLimit(200'000);
110 112  
... ...
fuzz/qtest/fuzz.test
... ... @@ -11,7 +11,7 @@ my $td = new TestDriver(&#39;fuzz&#39;);
11 11  
12 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 16 my @fuzzers = (
17 17 ['ascii85' => 1],
... ...
fuzz/runlength_fuzzer.cc
... ... @@ -25,6 +25,7 @@ FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
25 25 void
26 26 FuzzHelper::doChecks()
27 27 {
  28 + Pl_RunLength::setMemoryLimit(1'000'000);
28 29 Pl_Discard discard;
29 30 Pl_RunLength p("decode", &discard, Pl_RunLength::a_decode);
30 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 46 ~Pl_Flate() override;
47 47  
48 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 50 QPDF_DLL
51 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 32 QPDF_DLL
33 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 40 QPDF_DLL
36 41 void write(unsigned char const* data, size_t len) override;
37 42 QPDF_DLL
... ...
libqpdf/Pl_RunLength.cc
... ... @@ -3,6 +3,11 @@
3 3 #include <qpdf/QTC.hh>
4 4 #include <qpdf/QUtil.hh>
5 5  
  6 +namespace
  7 +{
  8 + unsigned long long memory_limit{0};
  9 +} // namespace
  10 +
6 11 Pl_RunLength::Members::Members(action_e action) :
7 12 action(action)
8 13 {
... ... @@ -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 31 Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default)
21 32 {
22 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 78 void
68 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 84 m->out.reserve(len);
71 85 for (size_t i = 0; i < len; ++i) {
72 86 unsigned char const& ch = data[i];
... ...
libqpdf/QPDFWriter.cc
... ... @@ -1877,8 +1877,8 @@ QPDFWriter::generateID()
1877 1877 if (m->deterministic_id_data.empty()) {
1878 1878 QTC::TC("qpdf", "QPDFWriter deterministic with no data");
1879 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 1883 seed += m->deterministic_id_data;
1884 1884 } else {
... ...