Commit 5be057caf0417ff9b748c5d6ee459e7756ea84df

Authored by m-holger
Committed by GitHub
2 parents 9ac50650 30e187b4

Merge pull request #1247 from m-holger/fuzz

Adjust fuzzer warning and memory limits
fuzz/CMakeLists.txt
@@ -99,6 +99,7 @@ set(CORPUS_OTHER @@ -99,6 +99,7 @@ set(CORPUS_OTHER
99 16301.fuzz 99 16301.fuzz
100 16953.fuzz 100 16953.fuzz
101 17630.fuzz 101 17630.fuzz
  102 + 17630a.fuzz
102 18241.fuzz 103 18241.fuzz
103 18247.fuzz 104 18247.fuzz
104 23172.fuzz 105 23172.fuzz
@@ -129,7 +130,6 @@ set(CORPUS_OTHER @@ -129,7 +130,6 @@ set(CORPUS_OTHER
129 70055.fuzz 130 70055.fuzz
130 70245.fuzz 131 70245.fuzz
131 70306.fuzz 132 70306.fuzz
132 - 4826608268017664.fuzz  
133 ) 133 )
134 134
135 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 135 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/qpdf_extra/4826608268017664.fuzz renamed to fuzz/qpdf_extra/17630a.fuzz
No preview for this file type
fuzz/qpdf_fuzzer.cc
@@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
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_PNGFilter.hh>
  6 +#include <qpdf/Pl_TIFFPredictor.hh>
5 #include <qpdf/QPDF.hh> 7 #include <qpdf/QPDF.hh>
6 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 8 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
7 #include <qpdf/QPDFOutlineDocumentHelper.hh> 9 #include <qpdf/QPDFOutlineDocumentHelper.hh>
@@ -57,7 +59,7 @@ FuzzHelper::getQpdf() @@ -57,7 +59,7 @@ FuzzHelper::getQpdf()
57 auto is = 59 auto is =
58 std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer)); 60 std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer));
59 auto qpdf = QPDF::create(); 61 auto qpdf = QPDF::create();
60 - qpdf->setMaxWarnings(20); 62 + qpdf->setMaxWarnings(500);
61 qpdf->processInputSource(is); 63 qpdf->processInputSource(is);
62 return qpdf; 64 return qpdf;
63 } 65 }
@@ -179,6 +181,9 @@ FuzzHelper::doChecks() @@ -179,6 +181,9 @@ FuzzHelper::doChecks()
179 // occur legitimately and therefore must be allowed during normal operations. 181 // occur legitimately and therefore must be allowed during normal operations.
180 Pl_DCT::setMemoryLimit(1'000'000'000); 182 Pl_DCT::setMemoryLimit(1'000'000'000);
181 183
  184 + Pl_PNGFilter::setMemoryLimit(1'000'000'000);
  185 + Pl_TIFFPredictor::setMemoryLimit(1'000'000'000);
  186 +
182 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without 187 // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
183 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts. 188 // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
184 Pl_DCT::setThrowOnCorruptData(true); 189 Pl_DCT::setThrowOnCorruptData(true);
include/qpdf/QPDF.hh
@@ -228,9 +228,9 @@ class QPDF @@ -228,9 +228,9 @@ class QPDF
228 QPDF_DLL 228 QPDF_DLL
229 void setSuppressWarnings(bool); 229 void setSuppressWarnings(bool);
230 230
231 - // Set the maximum number of warnings to output. Subsequent warnings are suppressed. 231 + // Set the maximum number of warnings. A QPDFExc is thrown if the limit is exceeded.
232 QPDF_DLL 232 QPDF_DLL
233 - void setMaxWarnings(int); 233 + void setMaxWarnings(size_t);
234 234
235 // By default, QPDF will try to recover if it finds certain types of errors in PDF files. If 235 // By default, QPDF will try to recover if it finds certain types of errors in PDF files. If
236 // turned off, it will throw an exception on the first such problem it finds without attempting 236 // turned off, it will throw an exception on the first such problem it finds without attempting
@@ -1501,7 +1501,7 @@ class QPDF @@ -1501,7 +1501,7 @@ class QPDF
1501 bool provided_password_is_hex_key{false}; 1501 bool provided_password_is_hex_key{false};
1502 bool ignore_xref_streams{false}; 1502 bool ignore_xref_streams{false};
1503 bool suppress_warnings{false}; 1503 bool suppress_warnings{false};
1504 - int max_warnings{0}; 1504 + size_t max_warnings{0};
1505 bool attempt_recovery{true}; 1505 bool attempt_recovery{true};
1506 bool check_mode{false}; 1506 bool check_mode{false};
1507 std::shared_ptr<EncryptionParameters> encp; 1507 std::shared_ptr<EncryptionParameters> encp;
libqpdf/Pl_PNGFilter.cc
@@ -7,6 +7,11 @@ @@ -7,6 +7,11 @@
7 #include <cstring> 7 #include <cstring>
8 #include <stdexcept> 8 #include <stdexcept>
9 9
  10 +namespace
  11 +{
  12 + unsigned long long memory_limit{0};
  13 +} // namespace
  14 +
10 static int 15 static int
11 abs_diff(int a, int b) 16 abs_diff(int a, int b)
12 { 17 {
@@ -41,6 +46,9 @@ Pl_PNGFilter::Pl_PNGFilter( @@ -41,6 +46,9 @@ Pl_PNGFilter::Pl_PNGFilter(
41 if ((bpr == 0) || (bpr > (UINT_MAX - 1))) { 46 if ((bpr == 0) || (bpr > (UINT_MAX - 1))) {
42 throw std::runtime_error("PNGFilter created with invalid columns value"); 47 throw std::runtime_error("PNGFilter created with invalid columns value");
43 } 48 }
  49 + if (memory_limit > 0 && bpr > (memory_limit / 2U)) {
  50 + throw std::runtime_error("PNGFilter memory limit exceeded");
  51 + }
44 this->bytes_per_row = bpr & UINT_MAX; 52 this->bytes_per_row = bpr & UINT_MAX;
45 this->buf1 = QUtil::make_shared_array<unsigned char>(this->bytes_per_row + 1); 53 this->buf1 = QUtil::make_shared_array<unsigned char>(this->bytes_per_row + 1);
46 this->buf2 = QUtil::make_shared_array<unsigned char>(this->bytes_per_row + 1); 54 this->buf2 = QUtil::make_shared_array<unsigned char>(this->bytes_per_row + 1);
@@ -54,6 +62,12 @@ Pl_PNGFilter::Pl_PNGFilter( @@ -54,6 +62,12 @@ Pl_PNGFilter::Pl_PNGFilter(
54 } 62 }
55 63
56 void 64 void
  65 +Pl_PNGFilter::setMemoryLimit(unsigned long long limit)
  66 +{
  67 + memory_limit = limit;
  68 +}
  69 +
  70 +void
57 Pl_PNGFilter::write(unsigned char const* data, size_t len) 71 Pl_PNGFilter::write(unsigned char const* data, size_t len)
58 { 72 {
59 size_t left = this->incoming - this->pos; 73 size_t left = this->incoming - this->pos;
libqpdf/Pl_TIFFPredictor.cc
@@ -7,6 +7,11 @@ @@ -7,6 +7,11 @@
7 #include <climits> 7 #include <climits>
8 #include <stdexcept> 8 #include <stdexcept>
9 9
  10 +namespace
  11 +{
  12 + unsigned long long memory_limit{0};
  13 +} // namespace
  14 +
10 Pl_TIFFPredictor::Pl_TIFFPredictor( 15 Pl_TIFFPredictor::Pl_TIFFPredictor(
11 char const* identifier, 16 char const* identifier,
12 Pipeline* next, 17 Pipeline* next,
@@ -31,10 +36,19 @@ Pl_TIFFPredictor::Pl_TIFFPredictor( @@ -31,10 +36,19 @@ Pl_TIFFPredictor::Pl_TIFFPredictor(
31 if ((bpr == 0) || (bpr > (UINT_MAX - 1))) { 36 if ((bpr == 0) || (bpr > (UINT_MAX - 1))) {
32 throw std::runtime_error("TIFFPredictor created with invalid columns value"); 37 throw std::runtime_error("TIFFPredictor created with invalid columns value");
33 } 38 }
  39 + if (memory_limit > 0 && bpr > (memory_limit / 2U)) {
  40 + throw std::runtime_error("TIFFPredictor memory limit exceeded");
  41 + }
34 this->bytes_per_row = bpr & UINT_MAX; 42 this->bytes_per_row = bpr & UINT_MAX;
35 } 43 }
36 44
37 void 45 void
  46 +Pl_TIFFPredictor::setMemoryLimit(unsigned long long limit)
  47 +{
  48 + memory_limit = limit;
  49 +}
  50 +
  51 +void
38 Pl_TIFFPredictor::write(unsigned char const* data, size_t len) 52 Pl_TIFFPredictor::write(unsigned char const* data, size_t len)
39 { 53 {
40 auto end = data + len; 54 auto end = data + len;
libqpdf/QPDF.cc
@@ -332,7 +332,7 @@ QPDF::setSuppressWarnings(bool val) @@ -332,7 +332,7 @@ QPDF::setSuppressWarnings(bool val)
332 } 332 }
333 333
334 void 334 void
335 -QPDF::setMaxWarnings(int val) 335 +QPDF::setMaxWarnings(size_t val)
336 { 336 {
337 m->max_warnings = val; 337 m->max_warnings = val;
338 } 338 }
@@ -504,13 +504,11 @@ QPDF::inParse(bool v) @@ -504,13 +504,11 @@ QPDF::inParse(bool v)
504 void 504 void
505 QPDF::warn(QPDFExc const& e) 505 QPDF::warn(QPDFExc const& e)
506 { 506 {
  507 + if (m->max_warnings > 0 && m->warnings.size() >= m->max_warnings) {
  508 + stopOnError("Too many warnings - file is too badly damaged");
  509 + }
507 m->warnings.push_back(e); 510 m->warnings.push_back(e);
508 if (!m->suppress_warnings) { 511 if (!m->suppress_warnings) {
509 - if (m->max_warnings > 0 && m->warnings.size() > 20) {  
510 - *m->log->getWarn() << "WARNING: too many warnings - additional warnings suppressed\n";  
511 - m->suppress_warnings = true;  
512 - return;  
513 - }  
514 *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n"; 512 *m->log->getWarn() << "WARNING: " << m->warnings.back().what() << "\n";
515 } 513 }
516 } 514 }
libqpdf/QPDF_json.cc
@@ -233,13 +233,12 @@ provide_data(std::shared_ptr&lt;InputSource&gt; is, qpdf_offset_t start, qpdf_offset_t @@ -233,13 +233,12 @@ provide_data(std::shared_ptr&lt;InputSource&gt; is, qpdf_offset_t start, qpdf_offset_t
233 class QPDF::JSONReactor: public JSON::Reactor 233 class QPDF::JSONReactor: public JSON::Reactor
234 { 234 {
235 public: 235 public:
236 - JSONReactor(QPDF& pdf, std::shared_ptr<InputSource> is, bool must_be_complete, int max_warnings) : 236 + JSONReactor(QPDF& pdf, std::shared_ptr<InputSource> is, bool must_be_complete) :
237 pdf(pdf), 237 pdf(pdf),
238 is(is), 238 is(is),
239 must_be_complete(must_be_complete), 239 must_be_complete(must_be_complete),
240 descr(std::make_shared<QPDFValue::Description>( 240 descr(std::make_shared<QPDFValue::Description>(
241 - QPDFValue::JSON_Descr(std::make_shared<std::string>(is->getName()), ""))),  
242 - max_warnings(max_warnings) 241 + QPDFValue::JSON_Descr(std::make_shared<std::string>(is->getName()), "")))
243 { 242 {
244 for (auto& oc: pdf.m->obj_cache) { 243 for (auto& oc: pdf.m->obj_cache) {
245 if (oc.second.object->getTypeCode() == ::ot_reserved) { 244 if (oc.second.object->getTypeCode() == ::ot_reserved) {
@@ -292,8 +291,7 @@ class QPDF::JSONReactor: public JSON::Reactor @@ -292,8 +291,7 @@ class QPDF::JSONReactor: public JSON::Reactor
292 std::shared_ptr<InputSource> is; 291 std::shared_ptr<InputSource> is;
293 bool must_be_complete{true}; 292 bool must_be_complete{true};
294 std::shared_ptr<QPDFValue::Description> descr; 293 std::shared_ptr<QPDFValue::Description> descr;
295 - int errors{0};  
296 - int max_warnings{0}; 294 + bool errors{false};
297 bool saw_qpdf{false}; 295 bool saw_qpdf{false};
298 bool saw_qpdf_meta{false}; 296 bool saw_qpdf_meta{false};
299 bool saw_objects{false}; 297 bool saw_objects{false};
@@ -316,21 +314,18 @@ class QPDF::JSONReactor: public JSON::Reactor @@ -316,21 +314,18 @@ class QPDF::JSONReactor: public JSON::Reactor
316 void 314 void
317 QPDF::JSONReactor::error(qpdf_offset_t offset, std::string const& msg) 315 QPDF::JSONReactor::error(qpdf_offset_t offset, std::string const& msg)
318 { 316 {
319 - ++errors; 317 + errors = true;
320 std::string object = this->cur_object; 318 std::string object = this->cur_object;
321 if (is->getName() != pdf.getFilename()) { 319 if (is->getName() != pdf.getFilename()) {
322 object += " from " + is->getName(); 320 object += " from " + is->getName();
323 } 321 }
324 - this->pdf.warn(qpdf_e_json, object, offset, msg);  
325 - if (max_warnings > 0 && errors >= max_warnings) {  
326 - throw std::runtime_error("errors found in JSON");  
327 - } 322 + pdf.warn(qpdf_e_json, object, offset, msg);
328 } 323 }
329 324
330 bool 325 bool
331 QPDF::JSONReactor::anyErrors() const 326 QPDF::JSONReactor::anyErrors() const
332 { 327 {
333 - return errors > 0; 328 + return errors;
334 } 329 }
335 330
336 void 331 void
@@ -825,7 +820,7 @@ QPDF::updateFromJSON(std::shared_ptr&lt;InputSource&gt; is) @@ -825,7 +820,7 @@ QPDF::updateFromJSON(std::shared_ptr&lt;InputSource&gt; is)
825 void 820 void
826 QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete) 821 QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
827 { 822 {
828 - JSONReactor reactor(*this, is, must_be_complete, m->max_warnings); 823 + JSONReactor reactor(*this, is, must_be_complete);
829 try { 824 try {
830 JSON::parse(*is, &reactor); 825 JSON::parse(*is, &reactor);
831 } catch (std::runtime_error& e) { 826 } catch (std::runtime_error& e) {
libqpdf/qpdf/Pl_PNGFilter.hh
@@ -24,6 +24,10 @@ class Pl_PNGFilter: public Pipeline @@ -24,6 +24,10 @@ class Pl_PNGFilter: public Pipeline
24 unsigned int bits_per_sample = 8); 24 unsigned int bits_per_sample = 8);
25 ~Pl_PNGFilter() override = default; 25 ~Pl_PNGFilter() override = default;
26 26
  27 + // Limit the memory used.
  28 + // NB This is a static option affecting all Pl_PNGFilter instances.
  29 + static void setMemoryLimit(unsigned long long limit);
  30 +
27 void write(unsigned char const* data, size_t len) override; 31 void write(unsigned char const* data, size_t len) override;
28 void finish() override; 32 void finish() override;
29 33
libqpdf/qpdf/Pl_TIFFPredictor.hh
@@ -22,6 +22,10 @@ class Pl_TIFFPredictor: public Pipeline @@ -22,6 +22,10 @@ class Pl_TIFFPredictor: public Pipeline
22 unsigned int bits_per_sample = 8); 22 unsigned int bits_per_sample = 8);
23 ~Pl_TIFFPredictor() override = default; 23 ~Pl_TIFFPredictor() override = default;
24 24
  25 + // Limit the memory used.
  26 + // NB This is a static option affecting all Pl_TIFFPredictor instances.
  27 + static void setMemoryLimit(unsigned long long limit);
  28 +
25 void write(unsigned char const* data, size_t len) override; 29 void write(unsigned char const* data, size_t len) override;
26 void finish() override; 30 void finish() override;
27 31