Commit bcf81a1423f9a539e4fc05e595ea5f6560590737
Committed by
GitHub
Merge pull request #1242 from m-holger/fuzz
Tighten page tree checks
Showing
9 changed files
with
32 additions
and
12 deletions
fuzz/CMakeLists.txt
fuzz/json_fuzzer.cc
| ... | ... | @@ -33,6 +33,7 @@ FuzzHelper::doChecks() |
| 33 | 33 | std::cerr << "runtime_error parsing json: " << e.what() << std::endl; |
| 34 | 34 | } |
| 35 | 35 | QPDF q; |
| 36 | + q.setMaxWarnings(1000); | |
| 36 | 37 | Buffer buf(const_cast<unsigned char*>(data), size); |
| 37 | 38 | auto is = std::make_shared<BufferInputSource>("json", &buf); |
| 38 | 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
include/qpdf/QPDF.hh
| ... | ... | @@ -1515,6 +1515,7 @@ class QPDF |
| 1515 | 1515 | std::set<QPDFObjGen> resolving; |
| 1516 | 1516 | QPDFObjectHandle trailer; |
| 1517 | 1517 | std::vector<QPDFObjectHandle> all_pages; |
| 1518 | + bool invalid_page_found{false}; | |
| 1518 | 1519 | std::map<QPDFObjGen, int> pageobj_to_pages_pos; |
| 1519 | 1520 | bool pushed_inherited_attributes_to_pages{false}; |
| 1520 | 1521 | bool ever_pushed_inherited_attributes_to_pages{false}; | ... | ... |
libqpdf/QPDF_json.cc
| ... | ... | @@ -233,12 +233,13 @@ provide_data(std::shared_ptr<InputSource> is, qpdf_offset_t start, qpdf_offset_t |
| 233 | 233 | class QPDF::JSONReactor: public JSON::Reactor |
| 234 | 234 | { |
| 235 | 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 | 237 | pdf(pdf), |
| 238 | 238 | is(is), |
| 239 | 239 | must_be_complete(must_be_complete), |
| 240 | 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 | 244 | for (auto& oc: pdf.m->obj_cache) { |
| 244 | 245 | if (oc.second.object->getTypeCode() == ::ot_reserved) { |
| ... | ... | @@ -291,7 +292,8 @@ class QPDF::JSONReactor: public JSON::Reactor |
| 291 | 292 | std::shared_ptr<InputSource> is; |
| 292 | 293 | bool must_be_complete{true}; |
| 293 | 294 | std::shared_ptr<QPDFValue::Description> descr; |
| 294 | - bool errors{false}; | |
| 295 | + int errors{0}; | |
| 296 | + int max_warnings{0}; | |
| 295 | 297 | bool saw_qpdf{false}; |
| 296 | 298 | bool saw_qpdf_meta{false}; |
| 297 | 299 | bool saw_objects{false}; |
| ... | ... | @@ -314,18 +316,21 @@ class QPDF::JSONReactor: public JSON::Reactor |
| 314 | 316 | void |
| 315 | 317 | QPDF::JSONReactor::error(qpdf_offset_t offset, std::string const& msg) |
| 316 | 318 | { |
| 317 | - this->errors = true; | |
| 319 | + ++errors; | |
| 318 | 320 | std::string object = this->cur_object; |
| 319 | 321 | if (is->getName() != pdf.getFilename()) { |
| 320 | 322 | object += " from " + is->getName(); |
| 321 | 323 | } |
| 322 | 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 | 330 | bool |
| 326 | 331 | QPDF::JSONReactor::anyErrors() const |
| 327 | 332 | { |
| 328 | - return this->errors; | |
| 333 | + return errors > 0; | |
| 329 | 334 | } |
| 330 | 335 | |
| 331 | 336 | void |
| ... | ... | @@ -820,7 +825,7 @@ QPDF::updateFromJSON(std::shared_ptr<InputSource> is) |
| 820 | 825 | void |
| 821 | 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 | 829 | try { |
| 825 | 830 | JSON::parse(*is, &reactor); |
| 826 | 831 | } catch (std::runtime_error& e) { | ... | ... |
libqpdf/QPDF_pages.cc
| ... | ... | @@ -40,7 +40,7 @@ std::vector<QPDFObjectHandle> const& |
| 40 | 40 | QPDF::getAllPages() |
| 41 | 41 | { |
| 42 | 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 | 44 | m->ever_called_get_all_pages = true; |
| 45 | 45 | QPDFObjGen::set visited; |
| 46 | 46 | QPDFObjGen::set seen; |
| ... | ... | @@ -66,9 +66,15 @@ QPDF::getAllPages() |
| 66 | 66 | getRoot().replaceKey("/Pages", pages); |
| 67 | 67 | } |
| 68 | 68 | seen.clear(); |
| 69 | - if (pages.hasKey("/Kids")) { | |
| 69 | + if (!pages.hasKey("/Kids")) { | |
| 70 | 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 | 80 | return m->all_pages; |
| ... | ... | @@ -100,6 +106,7 @@ QPDF::getAllPagesInternal( |
| 100 | 106 | auto kid = kids.getArrayItem(i); |
| 101 | 107 | if (!kid.isDictionary()) { |
| 102 | 108 | kid.warnIfPossible("Pages tree includes non-dictionary object; ignoring"); |
| 109 | + m->invalid_page_found = true; | |
| 103 | 110 | continue; |
| 104 | 111 | } |
| 105 | 112 | if (kid.hasKey("/Kids")) { |
| ... | ... | @@ -181,7 +188,11 @@ QPDF::flattenPagesTree() |
| 181 | 188 | pages.replaceKey("/Kids", QPDFObjectHandle::newArray(m->all_pages)); |
| 182 | 189 | // /Count has not changed |
| 183 | 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 | ... | ... |