Commit bcf81a1423f9a539e4fc05e595ea5f6560590737

Authored by m-holger
Committed by GitHub
2 parents 01353e11 992b7911

Merge pull request #1242 from m-holger/fuzz

Tighten page tree checks
fuzz/CMakeLists.txt
@@ -127,7 +127,9 @@ set(CORPUS_OTHER @@ -127,7 +127,9 @@ set(CORPUS_OTHER
127 69977b.fuzz 127 69977b.fuzz
128 69977c.fuzz 128 69977c.fuzz
129 70055.fuzz 129 70055.fuzz
130 - 4599089157701632.fuzz 130 + 70245.fuzz
  131 + 70306.fuzz
  132 + 4826608268017664.fuzz
131 ) 133 )
132 134
133 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 135 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/json_fuzzer.cc
@@ -33,6 +33,7 @@ FuzzHelper::doChecks() @@ -33,6 +33,7 @@ FuzzHelper::doChecks()
33 std::cerr << "runtime_error parsing json: " << e.what() << std::endl; 33 std::cerr << "runtime_error parsing json: " << e.what() << std::endl;
34 } 34 }
35 QPDF q; 35 QPDF q;
  36 + q.setMaxWarnings(1000);
36 Buffer buf(const_cast<unsigned char*>(data), size); 37 Buffer buf(const_cast<unsigned char*>(data), size);
37 auto is = std::make_shared<BufferInputSource>("json", &buf); 38 auto is = std::make_shared<BufferInputSource>("json", &buf);
38 q.createFromJSON(is); 39 q.createFromJSON(is);
fuzz/qpdf_extra/4826608268017664.fuzz 0 → 100644
No preview for this file type
fuzz/qpdf_extra/70245.fuzz 0 → 100644
fuzz/qpdf_extra/4599089157701632.fuzz renamed to fuzz/qpdf_extra/70306.fuzz
No preview for this file type
fuzz/qtest/fuzz.test
@@ -21,7 +21,7 @@ my @fuzzers = ( @@ -21,7 +21,7 @@ my @fuzzers = (
21 ['pngpredictor' => 1], 21 ['pngpredictor' => 1],
22 ['runlength' => 6], 22 ['runlength' => 6],
23 ['tiffpredictor' => 2], 23 ['tiffpredictor' => 2],
24 - ['qpdf' => 70], # increment when adding new files 24 + ['qpdf' => 72], # increment when adding new files
25 ); 25 );
26 26
27 my $n_tests = 0; 27 my $n_tests = 0;
include/qpdf/QPDF.hh
@@ -1515,6 +1515,7 @@ class QPDF @@ -1515,6 +1515,7 @@ class QPDF
1515 std::set<QPDFObjGen> resolving; 1515 std::set<QPDFObjGen> resolving;
1516 QPDFObjectHandle trailer; 1516 QPDFObjectHandle trailer;
1517 std::vector<QPDFObjectHandle> all_pages; 1517 std::vector<QPDFObjectHandle> all_pages;
  1518 + bool invalid_page_found{false};
1518 std::map<QPDFObjGen, int> pageobj_to_pages_pos; 1519 std::map<QPDFObjGen, int> pageobj_to_pages_pos;
1519 bool pushed_inherited_attributes_to_pages{false}; 1520 bool pushed_inherited_attributes_to_pages{false};
1520 bool ever_pushed_inherited_attributes_to_pages{false}; 1521 bool ever_pushed_inherited_attributes_to_pages{false};
libqpdf/QPDF_json.cc
@@ -233,12 +233,13 @@ provide_data(std::shared_ptr&lt;InputSource&gt; is, qpdf_offset_t start, qpdf_offset_t @@ -233,12 +233,13 @@ 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) : 236 + JSONReactor(QPDF& pdf, std::shared_ptr<InputSource> is, bool must_be_complete, int max_warnings) :
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()), ""))) 241 + QPDFValue::JSON_Descr(std::make_shared<std::string>(is->getName()), ""))),
  242 + max_warnings(max_warnings)
242 { 243 {
243 for (auto& oc: pdf.m->obj_cache) { 244 for (auto& oc: pdf.m->obj_cache) {
244 if (oc.second.object->getTypeCode() == ::ot_reserved) { 245 if (oc.second.object->getTypeCode() == ::ot_reserved) {
@@ -291,7 +292,8 @@ class QPDF::JSONReactor: public JSON::Reactor @@ -291,7 +292,8 @@ class QPDF::JSONReactor: public JSON::Reactor
291 std::shared_ptr<InputSource> is; 292 std::shared_ptr<InputSource> is;
292 bool must_be_complete{true}; 293 bool must_be_complete{true};
293 std::shared_ptr<QPDFValue::Description> descr; 294 std::shared_ptr<QPDFValue::Description> descr;
294 - bool errors{false}; 295 + int errors{0};
  296 + int max_warnings{0};
295 bool saw_qpdf{false}; 297 bool saw_qpdf{false};
296 bool saw_qpdf_meta{false}; 298 bool saw_qpdf_meta{false};
297 bool saw_objects{false}; 299 bool saw_objects{false};
@@ -314,18 +316,21 @@ class QPDF::JSONReactor: public JSON::Reactor @@ -314,18 +316,21 @@ class QPDF::JSONReactor: public JSON::Reactor
314 void 316 void
315 QPDF::JSONReactor::error(qpdf_offset_t offset, std::string const& msg) 317 QPDF::JSONReactor::error(qpdf_offset_t offset, std::string const& msg)
316 { 318 {
317 - this->errors = true; 319 + ++errors;
318 std::string object = this->cur_object; 320 std::string object = this->cur_object;
319 if (is->getName() != pdf.getFilename()) { 321 if (is->getName() != pdf.getFilename()) {
320 object += " from " + is->getName(); 322 object += " from " + is->getName();
321 } 323 }
322 this->pdf.warn(qpdf_e_json, object, offset, msg); 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 + }
323 } 328 }
324 329
325 bool 330 bool
326 QPDF::JSONReactor::anyErrors() const 331 QPDF::JSONReactor::anyErrors() const
327 { 332 {
328 - return this->errors; 333 + return errors > 0;
329 } 334 }
330 335
331 void 336 void
@@ -820,7 +825,7 @@ QPDF::updateFromJSON(std::shared_ptr&lt;InputSource&gt; is) @@ -820,7 +825,7 @@ QPDF::updateFromJSON(std::shared_ptr&lt;InputSource&gt; is)
820 void 825 void
821 QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete) 826 QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
822 { 827 {
823 - JSONReactor reactor(*this, is, must_be_complete); 828 + JSONReactor reactor(*this, is, must_be_complete, m->max_warnings);
824 try { 829 try {
825 JSON::parse(*is, &reactor); 830 JSON::parse(*is, &reactor);
826 } catch (std::runtime_error& e) { 831 } catch (std::runtime_error& e) {
libqpdf/QPDF_pages.cc
@@ -40,7 +40,7 @@ std::vector&lt;QPDFObjectHandle&gt; const&amp; @@ -40,7 +40,7 @@ std::vector&lt;QPDFObjectHandle&gt; const&amp;
40 QPDF::getAllPages() 40 QPDF::getAllPages()
41 { 41 {
42 // Note that pushInheritedAttributesToPage may also be used to initialize m->all_pages. 42 // Note that pushInheritedAttributesToPage may also be used to initialize m->all_pages.
43 - if (m->all_pages.empty()) { 43 + if (m->all_pages.empty() && !m->invalid_page_found) {
44 m->ever_called_get_all_pages = true; 44 m->ever_called_get_all_pages = true;
45 QPDFObjGen::set visited; 45 QPDFObjGen::set visited;
46 QPDFObjGen::set seen; 46 QPDFObjGen::set seen;
@@ -66,9 +66,15 @@ QPDF::getAllPages() @@ -66,9 +66,15 @@ QPDF::getAllPages()
66 getRoot().replaceKey("/Pages", pages); 66 getRoot().replaceKey("/Pages", pages);
67 } 67 }
68 seen.clear(); 68 seen.clear();
69 - if (pages.hasKey("/Kids")) { 69 + if (!pages.hasKey("/Kids")) {
70 // Ensure we actually found a /Pages object. 70 // Ensure we actually found a /Pages object.
71 - getAllPagesInternal(pages, visited, seen, false); 71 + throw QPDFExc(
  72 + qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array");
  73 + }
  74 + getAllPagesInternal(pages, visited, seen, false);
  75 + if (m->invalid_page_found) {
  76 + flattenPagesTree();
  77 + m->invalid_page_found = false;
72 } 78 }
73 } 79 }
74 return m->all_pages; 80 return m->all_pages;
@@ -100,6 +106,7 @@ QPDF::getAllPagesInternal( @@ -100,6 +106,7 @@ QPDF::getAllPagesInternal(
100 auto kid = kids.getArrayItem(i); 106 auto kid = kids.getArrayItem(i);
101 if (!kid.isDictionary()) { 107 if (!kid.isDictionary()) {
102 kid.warnIfPossible("Pages tree includes non-dictionary object; ignoring"); 108 kid.warnIfPossible("Pages tree includes non-dictionary object; ignoring");
  109 + m->invalid_page_found = true;
103 continue; 110 continue;
104 } 111 }
105 if (kid.hasKey("/Kids")) { 112 if (kid.hasKey("/Kids")) {
@@ -181,7 +188,11 @@ QPDF::flattenPagesTree() @@ -181,7 +188,11 @@ QPDF::flattenPagesTree()
181 pages.replaceKey("/Kids", QPDFObjectHandle::newArray(m->all_pages)); 188 pages.replaceKey("/Kids", QPDFObjectHandle::newArray(m->all_pages));
182 // /Count has not changed 189 // /Count has not changed
183 if (pages.getKey("/Count").getUIntValue() != len) { 190 if (pages.getKey("/Count").getUIntValue() != len) {
184 - throw std::runtime_error("/Count is wrong after flattening pages tree"); 191 + if (m->invalid_page_found && pages.getKey("/Count").getUIntValue() > len) {
  192 + pages.replaceKey("/Count", QPDFObjectHandle::newInteger(toI(len)));
  193 + } else {
  194 + throw std::runtime_error("/Count is wrong after flattening pages tree");
  195 + }
185 } 196 }
186 } 197 }
187 198