Commit 9f9f01d523dc2708ecd89f87c1b34c21f65d2cc2

Authored by m-holger
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.
fuzz/CMakeLists.txt
... ... @@ -160,6 +160,7 @@ set(CORPUS_OTHER
160 160 411312393.fuzz
161 161 433311400.fuzz
162 162 5109284021272576.fuzz
  163 + 6489005569146880.fuzz
163 164 )
164 165  
165 166 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
... ...
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
... ... @@ -791,6 +791,8 @@ class QPDF
791 791 class Pipe;
792 792 class JobSetter;
793 793  
  794 + inline bool reconstructed_xref() const;
  795 +
794 796 // For testing only -- do not add to DLL
795 797 static bool test_json_validators();
796 798  
... ...
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&amp; 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&amp; 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&lt;PathElement&gt;::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&amp; key, QPDFObjectHandle&amp; 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&amp; key, QPDFObjectHandle&amp; 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
... ... @@ -581,4 +581,10 @@ class QPDF::ResolveRecorder
581 581 std::set<QPDFObjGen>::const_iterator iter;
582 582 };
583 583  
  584 +inline bool
  585 +QPDF::reconstructed_xref() const
  586 +{
  587 + return m->reconstructed_xref;
  588 +}
  589 +
584 590 #endif // QPDF_PRIVATE_HH
... ...