Commit 9f9f01d523dc2708ecd89f87c1b34c21f65d2cc2
1 parent
d285dab8
Refactor `NNTree` to encapsulate warning and error handling into `NNTreeImpl`, a…
…dding error counting and leveraging `reconstructed_xref` for improved robustness, limiting the acceptable number of errors for damaged files.
Showing
7 changed files
with
43 additions
and
38 deletions
fuzz/CMakeLists.txt
fuzz/qpdf_extra/6489005569146880.fuzz
0 → 100644
No preview for this file type
fuzz/qtest/fuzz.test
| ... | ... | @@ -11,7 +11,7 @@ my $td = new TestDriver('fuzz'); |
| 11 | 11 | |
| 12 | 12 | my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; |
| 13 | 13 | |
| 14 | -my $n_qpdf_files = 97; # increment when adding new files | |
| 14 | +my $n_qpdf_files = 98; # increment when adding new files | |
| 15 | 15 | |
| 16 | 16 | my @fuzzers = ( |
| 17 | 17 | ['ascii85' => 1], | ... | ... |
include/qpdf/QPDF.hh
libqpdf/NNTree.cc
| 1 | 1 | #include <qpdf/NNTree.hh> |
| 2 | 2 | |
| 3 | +#include <qpdf/QPDF_private.hh> | |
| 3 | 4 | #include <qpdf/QTC.hh> |
| 4 | 5 | #include <qpdf/QUtil.hh> |
| 5 | 6 | |
| ... | ... | @@ -15,14 +16,17 @@ get_description(QPDFObjectHandle& node) |
| 15 | 16 | return result; |
| 16 | 17 | } |
| 17 | 18 | |
| 18 | -static void | |
| 19 | -warn(QPDF& qpdf, QPDFObjectHandle& node, std::string const& msg) | |
| 19 | +void | |
| 20 | +NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg) | |
| 20 | 21 | { |
| 21 | 22 | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); |
| 23 | + if (++error_count > 5 && qpdf.reconstructed_xref()) { | |
| 24 | + error(node, "too many errors - giving up"); | |
| 25 | + } | |
| 22 | 26 | } |
| 23 | 27 | |
| 24 | -static void | |
| 25 | -error(QPDF& qpdf, QPDFObjectHandle& node, std::string const& msg) | |
| 28 | +void | |
| 29 | +NNTreeImpl::error(QPDFObjectHandle& node, std::string const& msg) | |
| 26 | 30 | { |
| 27 | 31 | throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); |
| 28 | 32 | } |
| ... | ... | @@ -56,7 +60,7 @@ NNTreeIterator::updateIValue(bool allow_invalid) |
| 56 | 60 | ivalue.first = items.getArrayItem(item_number); |
| 57 | 61 | ivalue.second = items.getArrayItem(1 + item_number); |
| 58 | 62 | } else { |
| 59 | - error(impl.qpdf, node, "update ivalue: items array is too short"); | |
| 63 | + impl.error(node, "update ivalue: items array is too short"); | |
| 60 | 64 | } |
| 61 | 65 | } |
| 62 | 66 | if (!okay) { |
| ... | ... | @@ -90,8 +94,7 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward) |
| 90 | 94 | found = true; |
| 91 | 95 | } else { |
| 92 | 96 | QTC::TC("qpdf", "NNTree skip invalid kid"); |
| 93 | - warn( | |
| 94 | - impl.qpdf, | |
| 97 | + impl.warn( | |
| 95 | 98 | pe.node, |
| 96 | 99 | ("skipping over invalid kid at index " + std::to_string(pe.kid_number))); |
| 97 | 100 | } |
| ... | ... | @@ -138,13 +141,10 @@ NNTreeIterator::increment(bool backward) |
| 138 | 141 | items = node.getKey(impl.details.itemsKey()); |
| 139 | 142 | if (item_number + 1 >= items.getArrayNItems()) { |
| 140 | 143 | QTC::TC("qpdf", "NNTree skip item at end of short items"); |
| 141 | - warn(impl.qpdf, node, "items array doesn't have enough elements"); | |
| 144 | + impl.warn(node, "items array doesn't have enough elements"); | |
| 142 | 145 | } else if (!impl.details.keyValid(items.getArrayItem(item_number))) { |
| 143 | 146 | QTC::TC("qpdf", "NNTree skip invalid key"); |
| 144 | - warn( | |
| 145 | - impl.qpdf, | |
| 146 | - node, | |
| 147 | - ("item " + std::to_string(item_number) + " has the wrong type")); | |
| 147 | + impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); | |
| 148 | 148 | } else { |
| 149 | 149 | found_valid_key = true; |
| 150 | 150 | } |
| ... | ... | @@ -207,7 +207,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite |
| 207 | 207 | } |
| 208 | 208 | } else { |
| 209 | 209 | QTC::TC("qpdf", "NNTree unable to determine limits"); |
| 210 | - warn(impl.qpdf, a_node, "unable to determine limits"); | |
| 210 | + impl.warn(a_node, "unable to determine limits"); | |
| 211 | 211 | } |
| 212 | 212 | |
| 213 | 213 | if (!changed || parent == path.begin()) { |
| ... | ... | @@ -396,10 +396,10 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) |
| 396 | 396 | |
| 397 | 397 | auto items = node.getKey(impl.details.itemsKey()); |
| 398 | 398 | if (!items.isArray()) { |
| 399 | - error(impl.qpdf, node, "node contains no items array"); | |
| 399 | + impl.error(node, "node contains no items array"); | |
| 400 | 400 | } |
| 401 | 401 | if (items.getArrayNItems() < item_number + 2) { |
| 402 | - error(impl.qpdf, node, "insert: items array is too short"); | |
| 402 | + impl.error(node, "insert: items array is too short"); | |
| 403 | 403 | } |
| 404 | 404 | items.insertItem(item_number + 2, key); |
| 405 | 405 | items.insertItem(item_number + 3, value); |
| ... | ... | @@ -419,7 +419,7 @@ NNTreeIterator::remove() |
| 419 | 419 | auto items = node.getKey(impl.details.itemsKey()); |
| 420 | 420 | int nitems = items.getArrayNItems(); |
| 421 | 421 | if (item_number + 2 > nitems) { |
| 422 | - error(impl.qpdf, node, "found short items array while removing an item"); | |
| 422 | + impl.error(node, "found short items array while removing an item"); | |
| 423 | 423 | } |
| 424 | 424 | |
| 425 | 425 | items.eraseItem(item_number); |
| ... | ... | @@ -596,14 +596,14 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) |
| 596 | 596 | while (!failed) { |
| 597 | 597 | if (!seen.add(a_node)) { |
| 598 | 598 | QTC::TC("qpdf", "NNTree deepen: loop"); |
| 599 | - warn(impl.qpdf, a_node, "loop detected while traversing name/number tree"); | |
| 599 | + impl.warn(a_node, "loop detected while traversing name/number tree"); | |
| 600 | 600 | failed = true; |
| 601 | 601 | break; |
| 602 | 602 | } |
| 603 | 603 | |
| 604 | 604 | if (!a_node.isDictionary()) { |
| 605 | 605 | QTC::TC("qpdf", "NNTree node is not a dictionary"); |
| 606 | - warn(impl.qpdf, a_node, "non-dictionary node while traversing name/number tree"); | |
| 606 | + impl.warn(a_node, "non-dictionary node while traversing name/number tree"); | |
| 607 | 607 | failed = true; |
| 608 | 608 | break; |
| 609 | 609 | } |
| ... | ... | @@ -622,8 +622,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) |
| 622 | 622 | if (!next.isIndirect()) { |
| 623 | 623 | if (impl.auto_repair) { |
| 624 | 624 | QTC::TC("qpdf", "NNTree fix indirect kid"); |
| 625 | - warn( | |
| 626 | - impl.qpdf, | |
| 625 | + impl.warn( | |
| 627 | 626 | a_node, |
| 628 | 627 | ("converting kid number " + std::to_string(kid_number) + |
| 629 | 628 | " to an indirect object")); |
| ... | ... | @@ -631,8 +630,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) |
| 631 | 630 | kids.setArrayItem(kid_number, next); |
| 632 | 631 | } else { |
| 633 | 632 | QTC::TC("qpdf", "NNTree warn indirect kid"); |
| 634 | - warn( | |
| 635 | - impl.qpdf, | |
| 633 | + impl.warn( | |
| 636 | 634 | a_node, |
| 637 | 635 | ("kid number " + std::to_string(kid_number) + |
| 638 | 636 | " is not an indirect object")); |
| ... | ... | @@ -645,8 +643,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) |
| 645 | 643 | break; |
| 646 | 644 | } else { |
| 647 | 645 | QTC::TC("qpdf", "NNTree deepen: invalid node"); |
| 648 | - warn( | |
| 649 | - impl.qpdf, | |
| 646 | + impl.warn( | |
| 650 | 647 | a_node, |
| 651 | 648 | ("name/number tree node has neither non-empty " + impl.details.itemsKey() + |
| 652 | 649 | " nor /Kids")); |
| ... | ... | @@ -712,7 +709,7 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) |
| 712 | 709 | } |
| 713 | 710 | } else { |
| 714 | 711 | QTC::TC("qpdf", "NNTree missing limits"); |
| 715 | - error(qpdf, node, "node is missing /Limits"); | |
| 712 | + error(node, "node is missing /Limits"); | |
| 716 | 713 | } |
| 717 | 714 | return result; |
| 718 | 715 | } |
| ... | ... | @@ -782,7 +779,7 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int i |
| 782 | 779 | if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) && |
| 783 | 780 | details.keyValid(items.getArrayItem(2 * idx))))) { |
| 784 | 781 | QTC::TC("qpdf", "NNTree item is wrong type"); |
| 785 | - error(qpdf, oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); | |
| 782 | + error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); | |
| 786 | 783 | } |
| 787 | 784 | return details.compareKeys(key, items.getArrayItem(2 * idx)); |
| 788 | 785 | } |
| ... | ... | @@ -793,7 +790,7 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx |
| 793 | 790 | if (!(kids.isArray() && (idx < kids.getArrayNItems()) && |
| 794 | 791 | kids.getArrayItem(idx).isDictionary())) { |
| 795 | 792 | QTC::TC("qpdf", "NNTree kid is invalid"); |
| 796 | - error(qpdf, oh, "invalid kid at index " + std::to_string(idx)); | |
| 793 | + error(oh, "invalid kid at index " + std::to_string(idx)); | |
| 797 | 794 | } |
| 798 | 795 | return withinLimits(key, kids.getArrayItem(idx)); |
| 799 | 796 | } |
| ... | ... | @@ -819,7 +816,7 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) |
| 819 | 816 | } catch (QPDFExc& e) { |
| 820 | 817 | if (auto_repair) { |
| 821 | 818 | QTC::TC("qpdf", "NNTree repair"); |
| 822 | - warn(qpdf, oh, std::string("attempting to repair after error: ") + e.what()); | |
| 819 | + warn(oh, std::string("attempting to repair after error: ") + e.what()); | |
| 823 | 820 | repair(); |
| 824 | 821 | return findInternal(key, return_prev_if_not_found); |
| 825 | 822 | } else { |
| ... | ... | @@ -859,7 +856,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) |
| 859 | 856 | while (true) { |
| 860 | 857 | if (!seen.add(node)) { |
| 861 | 858 | QTC::TC("qpdf", "NNTree loop in find"); |
| 862 | - error(qpdf, node, "loop detected in find"); | |
| 859 | + error(node, "loop detected in find"); | |
| 863 | 860 | } |
| 864 | 861 | |
| 865 | 862 | auto kids = node.getKey("/Kids"); |
| ... | ... | @@ -877,17 +874,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) |
| 877 | 874 | int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); |
| 878 | 875 | if (idx == -1) { |
| 879 | 876 | QTC::TC("qpdf", "NNTree -1 in binary search"); |
| 880 | - error( | |
| 881 | - qpdf, | |
| 882 | - node, | |
| 883 | - "unexpected -1 from binary search of kids;" | |
| 884 | - " limits may by wrong"); | |
| 877 | + error(node, "unexpected -1 from binary search of kids; limits may by wrong"); | |
| 885 | 878 | } |
| 886 | 879 | result.addPathElement(node, idx); |
| 887 | 880 | node = kids.getArrayItem(idx); |
| 888 | 881 | } else { |
| 889 | 882 | QTC::TC("qpdf", "NNTree bad node during find"); |
| 890 | - error(qpdf, node, "bad node during find"); | |
| 883 | + error(node, "bad node during find"); | |
| 891 | 884 | } |
| 892 | 885 | } |
| 893 | 886 | |
| ... | ... | @@ -904,7 +897,7 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) |
| 904 | 897 | } |
| 905 | 898 | if (!(items.isArray())) { |
| 906 | 899 | QTC::TC("qpdf", "NNTree no valid items node in insertFirst"); |
| 907 | - error(qpdf, oh, "unable to find a valid items node"); | |
| 900 | + error(oh, "unable to find a valid items node"); | |
| 908 | 901 | } |
| 909 | 902 | items.insertItem(0, key); |
| 910 | 903 | items.insertItem(1, value); | ... | ... |
libqpdf/qpdf/NNTree.hh
| ... | ... | @@ -120,12 +120,15 @@ class NNTreeImpl |
| 120 | 120 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); |
| 121 | 121 | int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| 122 | 122 | int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| 123 | + void warn(QPDFObjectHandle& node, std::string const& msg); | |
| 124 | + void error(QPDFObjectHandle& node, std::string const& msg); | |
| 123 | 125 | |
| 124 | 126 | NNTreeDetails const& details; |
| 125 | 127 | QPDF& qpdf; |
| 126 | 128 | int split_threshold{32}; |
| 127 | 129 | QPDFObjectHandle oh; |
| 128 | - bool auto_repair; | |
| 130 | + bool auto_repair{true}; | |
| 131 | + size_t error_count{0}; | |
| 129 | 132 | }; |
| 130 | 133 | |
| 131 | 134 | #endif // NNTREE_HH | ... | ... |
libqpdf/qpdf/QPDF_private.hh