Commit b73f6d7951731624362d7729003455108f124b8a

Authored by m-holger
Committed by GitHub
2 parents d285dab8 9f9f01d5

Merge pull request #1498 from m-holger/fuzz

Refactor `NNTree` to encapsulate warning and error handling
fuzz/CMakeLists.txt
@@ -160,6 +160,7 @@ set(CORPUS_OTHER @@ -160,6 +160,7 @@ set(CORPUS_OTHER
160 411312393.fuzz 160 411312393.fuzz
161 433311400.fuzz 161 433311400.fuzz
162 5109284021272576.fuzz 162 5109284021272576.fuzz
  163 + 6489005569146880.fuzz
163 ) 164 )
164 165
165 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 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,7 +11,7 @@ my $td = new TestDriver('fuzz');
11 11
12 my $qpdf_corpus = $ENV{'QPDF_FUZZ_CORPUS'} || die "must set QPDF_FUZZ_CORPUS"; 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 my @fuzzers = ( 16 my @fuzzers = (
17 ['ascii85' => 1], 17 ['ascii85' => 1],
include/qpdf/QPDF.hh
@@ -791,6 +791,8 @@ class QPDF @@ -791,6 +791,8 @@ class QPDF
791 class Pipe; 791 class Pipe;
792 class JobSetter; 792 class JobSetter;
793 793
  794 + inline bool reconstructed_xref() const;
  795 +
794 // For testing only -- do not add to DLL 796 // For testing only -- do not add to DLL
795 static bool test_json_validators(); 797 static bool test_json_validators();
796 798
libqpdf/NNTree.cc
1 #include <qpdf/NNTree.hh> 1 #include <qpdf/NNTree.hh>
2 2
  3 +#include <qpdf/QPDF_private.hh>
3 #include <qpdf/QTC.hh> 4 #include <qpdf/QTC.hh>
4 #include <qpdf/QUtil.hh> 5 #include <qpdf/QUtil.hh>
5 6
@@ -15,14 +16,17 @@ get_description(QPDFObjectHandle&amp; node) @@ -15,14 +16,17 @@ get_description(QPDFObjectHandle&amp; node)
15 return result; 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 qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); 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 throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); 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,7 +60,7 @@ NNTreeIterator::updateIValue(bool allow_invalid)
56 ivalue.first = items.getArrayItem(item_number); 60 ivalue.first = items.getArrayItem(item_number);
57 ivalue.second = items.getArrayItem(1 + item_number); 61 ivalue.second = items.getArrayItem(1 + item_number);
58 } else { 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 if (!okay) { 66 if (!okay) {
@@ -90,8 +94,7 @@ NNTreeIterator::getNextKid(PathElement&amp; pe, bool backward) @@ -90,8 +94,7 @@ NNTreeIterator::getNextKid(PathElement&amp; pe, bool backward)
90 found = true; 94 found = true;
91 } else { 95 } else {
92 QTC::TC("qpdf", "NNTree skip invalid kid"); 96 QTC::TC("qpdf", "NNTree skip invalid kid");
93 - warn(  
94 - impl.qpdf, 97 + impl.warn(
95 pe.node, 98 pe.node,
96 ("skipping over invalid kid at index " + std::to_string(pe.kid_number))); 99 ("skipping over invalid kid at index " + std::to_string(pe.kid_number)));
97 } 100 }
@@ -138,13 +141,10 @@ NNTreeIterator::increment(bool backward) @@ -138,13 +141,10 @@ NNTreeIterator::increment(bool backward)
138 items = node.getKey(impl.details.itemsKey()); 141 items = node.getKey(impl.details.itemsKey());
139 if (item_number + 1 >= items.getArrayNItems()) { 142 if (item_number + 1 >= items.getArrayNItems()) {
140 QTC::TC("qpdf", "NNTree skip item at end of short items"); 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 } else if (!impl.details.keyValid(items.getArrayItem(item_number))) { 145 } else if (!impl.details.keyValid(items.getArrayItem(item_number))) {
143 QTC::TC("qpdf", "NNTree skip invalid key"); 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 } else { 148 } else {
149 found_valid_key = true; 149 found_valid_key = true;
150 } 150 }
@@ -207,7 +207,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite @@ -207,7 +207,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
207 } 207 }
208 } else { 208 } else {
209 QTC::TC("qpdf", "NNTree unable to determine limits"); 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 if (!changed || parent == path.begin()) { 213 if (!changed || parent == path.begin()) {
@@ -396,10 +396,10 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) @@ -396,10 +396,10 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value)
396 396
397 auto items = node.getKey(impl.details.itemsKey()); 397 auto items = node.getKey(impl.details.itemsKey());
398 if (!items.isArray()) { 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 if (items.getArrayNItems() < item_number + 2) { 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 items.insertItem(item_number + 2, key); 404 items.insertItem(item_number + 2, key);
405 items.insertItem(item_number + 3, value); 405 items.insertItem(item_number + 3, value);
@@ -419,7 +419,7 @@ NNTreeIterator::remove() @@ -419,7 +419,7 @@ NNTreeIterator::remove()
419 auto items = node.getKey(impl.details.itemsKey()); 419 auto items = node.getKey(impl.details.itemsKey());
420 int nitems = items.getArrayNItems(); 420 int nitems = items.getArrayNItems();
421 if (item_number + 2 > nitems) { 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 items.eraseItem(item_number); 425 items.eraseItem(item_number);
@@ -596,14 +596,14 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -596,14 +596,14 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
596 while (!failed) { 596 while (!failed) {
597 if (!seen.add(a_node)) { 597 if (!seen.add(a_node)) {
598 QTC::TC("qpdf", "NNTree deepen: loop"); 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 failed = true; 600 failed = true;
601 break; 601 break;
602 } 602 }
603 603
604 if (!a_node.isDictionary()) { 604 if (!a_node.isDictionary()) {
605 QTC::TC("qpdf", "NNTree node is not a dictionary"); 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 failed = true; 607 failed = true;
608 break; 608 break;
609 } 609 }
@@ -622,8 +622,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -622,8 +622,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
622 if (!next.isIndirect()) { 622 if (!next.isIndirect()) {
623 if (impl.auto_repair) { 623 if (impl.auto_repair) {
624 QTC::TC("qpdf", "NNTree fix indirect kid"); 624 QTC::TC("qpdf", "NNTree fix indirect kid");
625 - warn(  
626 - impl.qpdf, 625 + impl.warn(
627 a_node, 626 a_node,
628 ("converting kid number " + std::to_string(kid_number) + 627 ("converting kid number " + std::to_string(kid_number) +
629 " to an indirect object")); 628 " to an indirect object"));
@@ -631,8 +630,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -631,8 +630,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
631 kids.setArrayItem(kid_number, next); 630 kids.setArrayItem(kid_number, next);
632 } else { 631 } else {
633 QTC::TC("qpdf", "NNTree warn indirect kid"); 632 QTC::TC("qpdf", "NNTree warn indirect kid");
634 - warn(  
635 - impl.qpdf, 633 + impl.warn(
636 a_node, 634 a_node,
637 ("kid number " + std::to_string(kid_number) + 635 ("kid number " + std::to_string(kid_number) +
638 " is not an indirect object")); 636 " is not an indirect object"));
@@ -645,8 +643,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -645,8 +643,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
645 break; 643 break;
646 } else { 644 } else {
647 QTC::TC("qpdf", "NNTree deepen: invalid node"); 645 QTC::TC("qpdf", "NNTree deepen: invalid node");
648 - warn(  
649 - impl.qpdf, 646 + impl.warn(
650 a_node, 647 a_node,
651 ("name/number tree node has neither non-empty " + impl.details.itemsKey() + 648 ("name/number tree node has neither non-empty " + impl.details.itemsKey() +
652 " nor /Kids")); 649 " nor /Kids"));
@@ -712,7 +709,7 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) @@ -712,7 +709,7 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node)
712 } 709 }
713 } else { 710 } else {
714 QTC::TC("qpdf", "NNTree missing limits"); 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 return result; 714 return result;
718 } 715 }
@@ -782,7 +779,7 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle&amp; key, QPDFObjectHandle&amp; items, int i @@ -782,7 +779,7 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle&amp; key, QPDFObjectHandle&amp; items, int i
782 if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) && 779 if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) &&
783 details.keyValid(items.getArrayItem(2 * idx))))) { 780 details.keyValid(items.getArrayItem(2 * idx))))) {
784 QTC::TC("qpdf", "NNTree item is wrong type"); 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 return details.compareKeys(key, items.getArrayItem(2 * idx)); 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,7 +790,7 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle&amp; key, QPDFObjectHandle&amp; kids, int idx
793 if (!(kids.isArray() && (idx < kids.getArrayNItems()) && 790 if (!(kids.isArray() && (idx < kids.getArrayNItems()) &&
794 kids.getArrayItem(idx).isDictionary())) { 791 kids.getArrayItem(idx).isDictionary())) {
795 QTC::TC("qpdf", "NNTree kid is invalid"); 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 return withinLimits(key, kids.getArrayItem(idx)); 795 return withinLimits(key, kids.getArrayItem(idx));
799 } 796 }
@@ -819,7 +816,7 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -819,7 +816,7 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
819 } catch (QPDFExc& e) { 816 } catch (QPDFExc& e) {
820 if (auto_repair) { 817 if (auto_repair) {
821 QTC::TC("qpdf", "NNTree repair"); 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 repair(); 820 repair();
824 return findInternal(key, return_prev_if_not_found); 821 return findInternal(key, return_prev_if_not_found);
825 } else { 822 } else {
@@ -859,7 +856,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -859,7 +856,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
859 while (true) { 856 while (true) {
860 if (!seen.add(node)) { 857 if (!seen.add(node)) {
861 QTC::TC("qpdf", "NNTree loop in find"); 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 auto kids = node.getKey("/Kids"); 862 auto kids = node.getKey("/Kids");
@@ -877,17 +874,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -877,17 +874,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
877 int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); 874 int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid);
878 if (idx == -1) { 875 if (idx == -1) {
879 QTC::TC("qpdf", "NNTree -1 in binary search"); 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 result.addPathElement(node, idx); 879 result.addPathElement(node, idx);
887 node = kids.getArrayItem(idx); 880 node = kids.getArrayItem(idx);
888 } else { 881 } else {
889 QTC::TC("qpdf", "NNTree bad node during find"); 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,7 +897,7 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value)
904 } 897 }
905 if (!(items.isArray())) { 898 if (!(items.isArray())) {
906 QTC::TC("qpdf", "NNTree no valid items node in insertFirst"); 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 items.insertItem(0, key); 902 items.insertItem(0, key);
910 items.insertItem(1, value); 903 items.insertItem(1, value);
libqpdf/qpdf/NNTree.hh
@@ -120,12 +120,15 @@ class NNTreeImpl @@ -120,12 +120,15 @@ class NNTreeImpl
120 int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); 120 int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item));
121 int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); 121 int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
122 int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); 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 NNTreeDetails const& details; 126 NNTreeDetails const& details;
125 QPDF& qpdf; 127 QPDF& qpdf;
126 int split_threshold{32}; 128 int split_threshold{32};
127 QPDFObjectHandle oh; 129 QPDFObjectHandle oh;
128 - bool auto_repair; 130 + bool auto_repair{true};
  131 + size_t error_count{0};
129 }; 132 };
130 133
131 #endif // NNTREE_HH 134 #endif // NNTREE_HH
libqpdf/qpdf/QPDF_private.hh
@@ -581,4 +581,10 @@ class QPDF::ResolveRecorder @@ -581,4 +581,10 @@ class QPDF::ResolveRecorder
581 std::set<QPDFObjGen>::const_iterator iter; 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 #endif // QPDF_PRIVATE_HH 590 #endif // QPDF_PRIVATE_HH