Commit 466169d0cb2d735fb745026ceafc7333028da045

Authored by m-holger
Committed by GitHub
2 parents f5cac93a 1db68172

Merge pull request #1350 from m-holger/fuzz

Apply sanity checks on SF_FlateLzwDecode parameters
.git-blame-ignore-revs
@@ -18,3 +18,5 @@ d740c6ccced02147f84a39d5e5f0984d12bac6cb @@ -18,3 +18,5 @@ d740c6ccced02147f84a39d5e5f0984d12bac6cb
18 9ae7bdea966102f9621b22192747a891078e7470 18 9ae7bdea966102f9621b22192747a891078e7470
19 # Normalize white space in ChangeLog 19 # Normalize white space in ChangeLog
20 d7b909f97d3effc9540c35b0251bdf1c9abf187c 20 d7b909f97d3effc9540c35b0251bdf1c9abf187c
  21 +# Remove 'this->'
  22 +f5cac93ac63559ddc0aec1b1c61c9e2503c7b8e0
fuzz/CMakeLists.txt
@@ -154,6 +154,7 @@ set(CORPUS_OTHER @@ -154,6 +154,7 @@ set(CORPUS_OTHER
154 389974979.fuzz 154 389974979.fuzz
155 391974927.fuzz 155 391974927.fuzz
156 394129398.fuzz 156 394129398.fuzz
  157 + 394463491.fuzz
157 ) 158 )
158 159
159 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 160 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/qpdf_crypt_fuzzer.cc
@@ -111,7 +111,7 @@ FuzzHelper::doChecks() @@ -111,7 +111,7 @@ FuzzHelper::doChecks()
111 Pl_PNGFilter::setMemoryLimit(1'000'000); 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
112 Pl_RunLength::setMemoryLimit(1'000'000); 112 Pl_RunLength::setMemoryLimit(1'000'000);
113 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
114 - Pl_Flate::setMemoryLimit(200'000); 114 + Pl_Flate::memory_limit(200'000);
115 115
116 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 116 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
117 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 117 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qpdf_crypt_insecure_fuzzer.cc
@@ -111,7 +111,7 @@ FuzzHelper::doChecks() @@ -111,7 +111,7 @@ FuzzHelper::doChecks()
111 Pl_PNGFilter::setMemoryLimit(1'000'000); 111 Pl_PNGFilter::setMemoryLimit(1'000'000);
112 Pl_RunLength::setMemoryLimit(1'000'000); 112 Pl_RunLength::setMemoryLimit(1'000'000);
113 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 113 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
114 - Pl_Flate::setMemoryLimit(200'000); 114 + Pl_Flate::memory_limit(200'000);
115 115
116 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 116 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
117 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 117 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qpdf_extra/394463491.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_fuzzer.cc
@@ -109,7 +109,7 @@ FuzzHelper::doChecks() @@ -109,7 +109,7 @@ FuzzHelper::doChecks()
109 Pl_PNGFilter::setMemoryLimit(1'000'000); 109 Pl_PNGFilter::setMemoryLimit(1'000'000);
110 Pl_RunLength::setMemoryLimit(1'000'000); 110 Pl_RunLength::setMemoryLimit(1'000'000);
111 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 111 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
112 - Pl_Flate::setMemoryLimit(200'000); 112 + Pl_Flate::memory_limit(200'000);
113 113
114 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 114 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
115 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 115 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qpdf_lin_fuzzer.cc
@@ -110,7 +110,7 @@ FuzzHelper::doChecks() @@ -110,7 +110,7 @@ FuzzHelper::doChecks()
110 Pl_PNGFilter::setMemoryLimit(1'000'000); 110 Pl_PNGFilter::setMemoryLimit(1'000'000);
111 Pl_RunLength::setMemoryLimit(1'000'000); 111 Pl_RunLength::setMemoryLimit(1'000'000);
112 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 112 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
113 - Pl_Flate::setMemoryLimit(200'000); 113 + Pl_Flate::memory_limit(200'000);
114 114
115 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 115 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
116 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 116 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qpdf_outlines_fuzzer.cc
@@ -87,7 +87,7 @@ FuzzHelper::doChecks() @@ -87,7 +87,7 @@ FuzzHelper::doChecks()
87 Pl_PNGFilter::setMemoryLimit(1'000'000); 87 Pl_PNGFilter::setMemoryLimit(1'000'000);
88 Pl_RunLength::setMemoryLimit(1'000'000); 88 Pl_RunLength::setMemoryLimit(1'000'000);
89 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 89 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
90 - Pl_Flate::setMemoryLimit(200'000); 90 + Pl_Flate::memory_limit(200'000);
91 91
92 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 92 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
93 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 93 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qpdf_pages_fuzzer.cc
@@ -108,7 +108,7 @@ FuzzHelper::doChecks() @@ -108,7 +108,7 @@ FuzzHelper::doChecks()
108 Pl_PNGFilter::setMemoryLimit(1'000'000); 108 Pl_PNGFilter::setMemoryLimit(1'000'000);
109 Pl_RunLength::setMemoryLimit(1'000'000); 109 Pl_RunLength::setMemoryLimit(1'000'000);
110 Pl_TIFFPredictor::setMemoryLimit(1'000'000); 110 Pl_TIFFPredictor::setMemoryLimit(1'000'000);
111 - Pl_Flate::setMemoryLimit(200'000); 111 + Pl_Flate::memory_limit(200'000);
112 112
113 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 113 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
114 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 114 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
fuzz/qtest/fuzz.test
@@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz');
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 = 91; # increment when adding new files 14 +my $n_qpdf_files = 92; # increment when adding new files
15 15
16 my @fuzzers = ( 16 my @fuzzers = (
17 ['ascii85' => 1], 17 ['ascii85' => 1],
include/qpdf/Pl_Flate.hh
@@ -48,7 +48,9 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline @@ -48,7 +48,9 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline
48 // Limit the memory used. 48 // Limit the memory used.
49 // NB This is a static option affecting all Pl_Flate 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 unsigned long long memory_limit();
  52 + QPDF_DLL
  53 + static void memory_limit(unsigned long long limit);
52 54
53 QPDF_DLL 55 QPDF_DLL
54 void write(unsigned char const* data, size_t len) override; 56 void write(unsigned char const* data, size_t len) override;
libqpdf/Pl_Flate.cc
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 14
15 namespace 15 namespace
16 { 16 {
17 - unsigned long long memory_limit{0}; 17 + unsigned long long memory_limit_{0};
18 } // namespace 18 } // namespace
19 19
20 int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION; 20 int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION;
@@ -80,10 +80,16 @@ Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default) @@ -80,10 +80,16 @@ Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default)
80 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer 80 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
81 } 81 }
82 82
  83 +unsigned long long
  84 +Pl_Flate::memory_limit()
  85 +{
  86 + return memory_limit_;
  87 +}
  88 +
83 void 89 void
84 -Pl_Flate::setMemoryLimit(unsigned long long limit) 90 +Pl_Flate::memory_limit(unsigned long long limit)
85 { 91 {
86 - memory_limit = limit; 92 + memory_limit_ = limit;
87 } 93 }
88 94
89 void 95 void
@@ -197,9 +203,9 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush) @@ -197,9 +203,9 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
197 } 203 }
198 uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out); 204 uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out);
199 if (ready > 0) { 205 if (ready > 0) {
200 - if (memory_limit && m->action != a_deflate) { 206 + if (memory_limit_ && m->action != a_deflate) {
201 m->written += ready; 207 m->written += ready;
202 - if (m->written > memory_limit) { 208 + if (m->written > memory_limit_) {
203 throw std::runtime_error("PL_Flate memory limit exceeded"); 209 throw std::runtime_error("PL_Flate memory limit exceeded");
204 } 210 }
205 } 211 }
@@ -220,7 +226,7 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush) @@ -220,7 +226,7 @@ Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
220 void 226 void
221 Pl_Flate::finish() 227 Pl_Flate::finish()
222 { 228 {
223 - if (m->written > memory_limit) { 229 + if (m->written > memory_limit_) {
224 throw std::runtime_error("PL_Flate memory limit exceeded"); 230 throw std::runtime_error("PL_Flate memory limit exceeded");
225 } 231 }
226 try { 232 try {
libqpdf/QPDFJob_argv.cc
@@ -117,8 +117,10 @@ ArgParser::argZopfli() @@ -117,8 +117,10 @@ ArgParser::argZopfli()
117 logger->info("Set the environment variable QPDF_ZOPFLI to activate.\n"); 117 logger->info("Set the environment variable QPDF_ZOPFLI to activate.\n");
118 logger->info("* QPDF_ZOPFLI=disabled or QPDF_ZOPFLI not set: don't use zopfli.\n"); 118 logger->info("* QPDF_ZOPFLI=disabled or QPDF_ZOPFLI not set: don't use zopfli.\n");
119 logger->info("* QPDF_ZOPFLI=force: use zopfli, and fail if not available.\n"); 119 logger->info("* QPDF_ZOPFLI=force: use zopfli, and fail if not available.\n");
120 - logger->info("* QPDF_ZOPFLI=silent: use zopfli if available and silently fall back if not.\n");  
121 - logger->info("* QPDF_ZOPFLI= any other value: use zopfli if available, and warn if not.\n"); 120 + logger->info(
  121 + "* QPDF_ZOPFLI=silent: use zopfli if available and silently fall back if not.\n");
  122 + logger->info(
  123 + "* QPDF_ZOPFLI= any other value: use zopfli if available, and warn if not.\n");
122 } 124 }
123 } else { 125 } else {
124 logger->error("zopfli support is not enabled\n"); 126 logger->error("zopfli support is not enabled\n");
libqpdf/SF_FlateLzwDecode.cc
@@ -7,17 +7,6 @@ @@ -7,17 +7,6 @@
7 #include <qpdf/QIntC.hh> 7 #include <qpdf/QIntC.hh>
8 #include <qpdf/QTC.hh> 8 #include <qpdf/QTC.hh>
9 9
10 -SF_FlateLzwDecode::SF_FlateLzwDecode(bool lzw) :  
11 - lzw(lzw),  
12 - // Initialize values to their defaults as per the PDF spec  
13 - predictor(1),  
14 - columns(1),  
15 - colors(1),  
16 - bits_per_component(8),  
17 - early_code_change(true)  
18 -{  
19 -}  
20 -  
21 bool 10 bool
22 SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) 11 SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms)
23 { 12 {
@@ -25,78 +14,83 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms) @@ -25,78 +14,83 @@ SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms)
25 return true; 14 return true;
26 } 15 }
27 16
28 - bool filterable = true; 17 + auto memory_limit = Pl_Flate::memory_limit();
  18 +
29 std::set<std::string> keys = decode_parms.getKeys(); 19 std::set<std::string> keys = decode_parms.getKeys();
30 for (auto const& key: keys) { 20 for (auto const& key: keys) {
31 QPDFObjectHandle value = decode_parms.getKey(key); 21 QPDFObjectHandle value = decode_parms.getKey(key);
32 if (key == "/Predictor") { 22 if (key == "/Predictor") {
33 if (value.isInteger()) { 23 if (value.isInteger()) {
34 - this->predictor = value.getIntValueAsInt();  
35 - if (!((this->predictor == 1) || (this->predictor == 2) ||  
36 - ((this->predictor >= 10) && (this->predictor <= 15)))) {  
37 - filterable = false; 24 + predictor = value.getIntValueAsInt();
  25 + if (!(predictor == 1 || predictor == 2 || (predictor >= 10 && predictor <= 15))) {
  26 + return false;
38 } 27 }
39 } else { 28 } else {
40 - filterable = false; 29 + return false;
41 } 30 }
42 - } else if ((key == "/Columns") || (key == "/Colors") || (key == "/BitsPerComponent")) { 31 + } else if (key == "/Columns" || key == "/Colors" || key == "/BitsPerComponent") {
43 if (value.isInteger()) { 32 if (value.isInteger()) {
44 int val = value.getIntValueAsInt(); 33 int val = value.getIntValueAsInt();
  34 + if (memory_limit && static_cast<unsigned int>(val) > memory_limit) {
  35 + QPDFLogger::defaultLogger()->warn(
  36 + "SF_FlateLzwDecode parameter exceeds PL_Flate memory limit\n");
  37 + return false;
  38 + }
45 if (key == "/Columns") { 39 if (key == "/Columns") {
46 - this->columns = val; 40 + columns = val;
47 } else if (key == "/Colors") { 41 } else if (key == "/Colors") {
48 - this->colors = val; 42 + colors = val;
49 } else if (key == "/BitsPerComponent") { 43 } else if (key == "/BitsPerComponent") {
50 - this->bits_per_component = val; 44 + bits_per_component = val;
51 } 45 }
52 } else { 46 } else {
53 - filterable = false; 47 + return false;
54 } 48 }
55 } else if (lzw && (key == "/EarlyChange")) { 49 } else if (lzw && (key == "/EarlyChange")) {
56 if (value.isInteger()) { 50 if (value.isInteger()) {
57 int earlychange = value.getIntValueAsInt(); 51 int earlychange = value.getIntValueAsInt();
58 - this->early_code_change = (earlychange == 1);  
59 - if (!((earlychange == 0) || (earlychange == 1))) {  
60 - filterable = false; 52 + early_code_change = (earlychange == 1);
  53 + if (!(earlychange == 0 || earlychange == 1)) {
  54 + return false;
61 } 55 }
62 } else { 56 } else {
63 - filterable = false; 57 + return false;
64 } 58 }
65 } 59 }
66 } 60 }
67 61
68 - if ((this->predictor > 1) && (this->columns == 0)) {  
69 - filterable = false; 62 + if (predictor > 1 && columns == 0) {
  63 + return false;
70 } 64 }
71 65
72 - return filterable; 66 + return true;
73 } 67 }
74 68
75 Pipeline* 69 Pipeline*
76 SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) 70 SF_FlateLzwDecode::getDecodePipeline(Pipeline* next)
77 { 71 {
78 std::shared_ptr<Pipeline> pipeline; 72 std::shared_ptr<Pipeline> pipeline;
79 - if ((this->predictor >= 10) && (this->predictor <= 15)) { 73 + if (predictor >= 10 && predictor <= 15) {
80 QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter"); 74 QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter");
81 pipeline = std::make_shared<Pl_PNGFilter>( 75 pipeline = std::make_shared<Pl_PNGFilter>(
82 "png decode", 76 "png decode",
83 next, 77 next,
84 Pl_PNGFilter::a_decode, 78 Pl_PNGFilter::a_decode,
85 - QIntC::to_uint(this->columns),  
86 - QIntC::to_uint(this->colors),  
87 - QIntC::to_uint(this->bits_per_component));  
88 - this->pipelines.push_back(pipeline); 79 + QIntC::to_uint(columns),
  80 + QIntC::to_uint(colors),
  81 + QIntC::to_uint(bits_per_component));
  82 + pipelines.push_back(pipeline);
89 next = pipeline.get(); 83 next = pipeline.get();
90 - } else if (this->predictor == 2) { 84 + } else if (predictor == 2) {
91 QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor"); 85 QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor");
92 pipeline = std::make_shared<Pl_TIFFPredictor>( 86 pipeline = std::make_shared<Pl_TIFFPredictor>(
93 "tiff decode", 87 "tiff decode",
94 next, 88 next,
95 Pl_TIFFPredictor::a_decode, 89 Pl_TIFFPredictor::a_decode,
96 - QIntC::to_uint(this->columns),  
97 - QIntC::to_uint(this->colors),  
98 - QIntC::to_uint(this->bits_per_component));  
99 - this->pipelines.push_back(pipeline); 90 + QIntC::to_uint(columns),
  91 + QIntC::to_uint(colors),
  92 + QIntC::to_uint(bits_per_component));
  93 + pipelines.push_back(pipeline);
100 next = pipeline.get(); 94 next = pipeline.get();
101 } 95 }
102 96
@@ -105,7 +99,7 @@ SF_FlateLzwDecode::getDecodePipeline(Pipeline* next) @@ -105,7 +99,7 @@ SF_FlateLzwDecode::getDecodePipeline(Pipeline* next)
105 } else { 99 } else {
106 pipeline = std::make_shared<Pl_Flate>("stream inflate", next, Pl_Flate::a_inflate); 100 pipeline = std::make_shared<Pl_Flate>("stream inflate", next, Pl_Flate::a_inflate);
107 } 101 }
108 - this->pipelines.push_back(pipeline); 102 + pipelines.push_back(pipeline);
109 return pipeline.get(); 103 return pipeline.get();
110 } 104 }
111 105
libqpdf/qpdf/SF_FlateLzwDecode.hh
@@ -5,25 +5,29 @@ @@ -5,25 +5,29 @@
5 #ifndef SF_FLATELZWDECODE_HH 5 #ifndef SF_FLATELZWDECODE_HH
6 # define SF_FLATELZWDECODE_HH 6 # define SF_FLATELZWDECODE_HH
7 7
8 -class SF_FlateLzwDecode: public QPDFStreamFilter 8 +class SF_FlateLzwDecode final: public QPDFStreamFilter
9 { 9 {
10 public: 10 public:
11 - SF_FlateLzwDecode(bool lzw);  
12 - ~SF_FlateLzwDecode() override = default; 11 + SF_FlateLzwDecode(bool lzw) :
  12 + lzw(lzw)
  13 + {
  14 + }
  15 + ~SF_FlateLzwDecode() final = default;
13 16
14 - bool setDecodeParms(QPDFObjectHandle decode_parms) override;  
15 - Pipeline* getDecodePipeline(Pipeline* next) override; 17 + bool setDecodeParms(QPDFObjectHandle decode_parms) final;
  18 + Pipeline* getDecodePipeline(Pipeline* next) final;
16 19
17 static std::shared_ptr<QPDFStreamFilter> flate_factory(); 20 static std::shared_ptr<QPDFStreamFilter> flate_factory();
18 static std::shared_ptr<QPDFStreamFilter> lzw_factory(); 21 static std::shared_ptr<QPDFStreamFilter> lzw_factory();
19 22
20 private: 23 private:
21 - bool lzw;  
22 - int predictor;  
23 - int columns;  
24 - int colors;  
25 - int bits_per_component;  
26 - bool early_code_change; 24 + bool lzw{};
  25 + // Defaults as per the PDF spec
  26 + int predictor{1};
  27 + int columns{1};
  28 + int colors{1};
  29 + int bits_per_component{8};
  30 + bool early_code_change{true};
27 std::vector<std::shared_ptr<Pipeline>> pipelines; 31 std::vector<std::shared_ptr<Pipeline>> pipelines;
28 }; 32 };
29 33