Commit 40f601dfa7657b503df23b6f03d37d5ba925cb75

Authored by m-holger
1 parent 33b639a0

Refine QPDFParser error handling

#1349 introduced a limit on the maximum size of arrays and dictionaries
contained in objects that generate errors during parsing, and #1354
reduced that limit to 5000 objects. However, the limit was only imposed
once a further error was encountered.

Stop adding objects to containers once the limit is reached.

Fixes oss-fuzz issue 398060137
fuzz/CMakeLists.txt
@@ -155,6 +155,7 @@ set(CORPUS_OTHER @@ -155,6 +155,7 @@ set(CORPUS_OTHER
155 391974927.fuzz 155 391974927.fuzz
156 394129398.fuzz 156 394129398.fuzz
157 394463491.fuzz 157 394463491.fuzz
  158 + 398060137.fuzz
158 ) 159 )
159 160
160 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus) 161 set(CORPUS_DIR ${CMAKE_CURRENT_BINARY_DIR}/qpdf_corpus)
fuzz/qpdf_extra/398060137.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 = 92; # increment when adding new files 14 +my $n_qpdf_files = 93; # increment when adding new files
15 15
16 my @fuzzers = ( 16 my @fuzzers = (
17 ['ascii85' => 1], 17 ['ascii85' => 1],
libqpdf/QPDFParser.cc
@@ -215,6 +215,11 @@ QPDFParser::parseRemainder(bool content_stream) @@ -215,6 +215,11 @@ QPDFParser::parseRemainder(bool content_stream)
215 continue; 215 continue;
216 216
217 case QPDFTokenizer::tt_array_close: 217 case QPDFTokenizer::tt_array_close:
  218 + if (bad_count && !max_bad_count) {
  219 + // Trigger warning.
  220 + (void)tooManyBadTokens();
  221 + return {QPDF_Null::create()};
  222 + }
218 if (frame->state == st_array) { 223 if (frame->state == st_array) {
219 auto object = QPDF_Array::create(std::move(frame->olist), frame->null_count > 100); 224 auto object = QPDF_Array::create(std::move(frame->olist), frame->null_count > 100);
220 setDescription(object, frame->offset - 1); 225 setDescription(object, frame->offset - 1);
@@ -239,6 +244,11 @@ QPDFParser::parseRemainder(bool content_stream) @@ -239,6 +244,11 @@ QPDFParser::parseRemainder(bool content_stream)
239 continue; 244 continue;
240 245
241 case QPDFTokenizer::tt_dict_close: 246 case QPDFTokenizer::tt_dict_close:
  247 + if (bad_count && !max_bad_count) {
  248 + // Trigger warning.
  249 + (void)tooManyBadTokens();
  250 + return {QPDF_Null::create()};
  251 + }
242 if (frame->state <= st_dictionary_value) { 252 if (frame->state <= st_dictionary_value) {
243 // Attempt to recover more or less gracefully from invalid dictionaries. 253 // Attempt to recover more or less gracefully from invalid dictionaries.
244 auto& dict = frame->dict; 254 auto& dict = frame->dict;
@@ -419,6 +429,12 @@ template &lt;typename T, typename... Args&gt; @@ -419,6 +429,12 @@ template &lt;typename T, typename... Args&gt;
419 void 429 void
420 QPDFParser::addScalar(Args&&... args) 430 QPDFParser::addScalar(Args&&... args)
421 { 431 {
  432 + if (bad_count && (frame->olist.size() > 5'000 || frame->dict.size() > 5'000)) {
  433 + // Stop adding scalars. We are going to abort when the close token or a bad token is
  434 + // encountered.
  435 + max_bad_count = 0;
  436 + return;
  437 + }
422 auto obj = T::create(args...); 438 auto obj = T::create(args...);
423 obj->setDescription(context, description, input.getLastOffset()); 439 obj->setDescription(context, description, input.getLastOffset());
424 add(std::move(obj)); 440 add(std::move(obj));
libqpdf/qpdf/QPDFParser.hh
@@ -83,7 +83,8 @@ class QPDFParser @@ -83,7 +83,8 @@ class QPDFParser
83 83
84 std::vector<StackFrame> stack; 84 std::vector<StackFrame> stack;
85 StackFrame* frame; 85 StackFrame* frame;
86 - // Number of recent bad tokens. 86 + // Number of recent bad tokens. This will always be > 0 once a bad token has been encountered as
  87 + // it only gets incremented or reset when a bad token is encountered.
87 int bad_count{0}; 88 int bad_count{0};
88 // Number of bad tokens (remaining) before giving up. 89 // Number of bad tokens (remaining) before giving up.
89 int max_bad_count{15}; 90 int max_bad_count{15};