Commit 1e2c50c0f65f8cf08d726e830275ece84a65d77b

Authored by m-holger
1 parent c1b45018

Refactor AcroForm field traversal for robustness and add new fuzz case

- Modify `traverseField` to return `bool` for identifying invalid fields.
- Add `bad_fields` set to track and handle problematic fields.
- Improve error handling for invalid parent entries.
fuzz/CMakeLists.txt
... ... @@ -161,6 +161,7 @@ set(CORPUS_OTHER
161 161 433311400.fuzz
162 162 440599107.fuzz
163 163 440747125.fuzz
  164 + 464655077.fuzz
164 165 4720043549327360.fuzz
165 166 4797504999981056.fuzz
166 167 4876793183272960.fuzz
... ...
fuzz/qpdf_extra/464655077.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 = 107; # increment when adding new files
  14 +my $n_qpdf_files = 108; # increment when adding new files
15 15  
16 16 my @fuzzers = (
17 17 ['ascii85' => 1],
... ...
include/qpdf/QPDFAcroFormDocumentHelper.hh
... ... @@ -226,7 +226,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
226 226  
227 227 private:
228 228 void analyze();
229   - void traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth);
  229 + bool traverseField(QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth);
230 230 QPDFObjectHandle getOrCreateAcroForm();
231 231 void adjustInheritedFields(
232 232 QPDFObjectHandle obj,
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -31,6 +31,7 @@ class QPDFAcroFormDocumentHelper::Members
31 31 std::map<QPDFObjGen, FieldData> field_to;
32 32 std::map<QPDFObjGen, QPDFFormFieldObjectHelper> annotation_to_field;
33 33 std::map<std::string, std::set<QPDFObjGen>> name_to_fields;
  34 + std::set<QPDFObjGen> bad_fields;
34 35 };
35 36  
36 37 QPDFAcroFormDocumentHelper::QPDFAcroFormDocumentHelper(QPDF& qpdf) :
... ... @@ -312,35 +313,36 @@ QPDFAcroFormDocumentHelper::analyze()
312 313 }
313 314 }
314 315  
315   -void
  316 +bool
316 317 QPDFAcroFormDocumentHelper::traverseField(
317 318 QPDFObjectHandle field, QPDFObjectHandle const& parent, int depth)
318 319 {
319 320 if (depth > 100) {
320 321 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that
321 322 // could cause stack overflow.
322   - return;
  323 + return false;
323 324 }
324 325 if (!field.indirect()) {
325 326 field.warn(
326 327 "encountered a direct object as a field or annotation while traversing /AcroForm; "
327 328 "ignoring field or annotation");
328   - return;
  329 + return false;
329 330 }
330 331 if (field == parent) {
331 332 field.warn("loop detected while traversing /AcroForm");
332   - return;
  333 + return false;
333 334 }
334 335 if (!field.isDictionary()) {
335 336 field.warn(
336 337 "encountered a non-dictionary as a field or annotation while traversing /AcroForm; "
337 338 "ignoring field or annotation");
338   - return;
  339 + return false;
339 340 }
340 341 QPDFObjGen og(field.getObjGen());
341   - if (m->field_to.contains(og) || m->annotation_to_field.contains(og)) {
  342 + if (m->field_to.contains(og) || m->annotation_to_field.contains(og) ||
  343 + m->bad_fields.contains(og)) {
342 344 field.warn("loop detected while traversing /AcroForm");
343   - return;
  345 + return false;
344 346 }
345 347  
346 348 // A dictionary encountered while traversing the /AcroForm field may be a form field, an
... ... @@ -357,6 +359,13 @@ QPDFAcroFormDocumentHelper::traverseField(
357 359 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1);
358 360 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1));
359 361  
  362 + if (!is_field && !is_annotation) {
  363 + field.warn(
  364 + "encountered an object that is neither field nor annotation while traversing "
  365 + "/AcroForm");
  366 + return false;
  367 + }
  368 +
360 369 if (is_annotation) {
361 370 QPDFObjectHandle our_field = (is_field ? field : parent);
362 371 m->field_to[our_field.getObjGen()].annotations.emplace_back(field);
... ... @@ -365,9 +374,15 @@ QPDFAcroFormDocumentHelper::traverseField(
365 374  
366 375 if (is_field && depth != 0 && field["/Parent"] != parent) {
367 376 for (auto const& kid: Array(field["/Parent"]["/Kids"])) {
  377 + if (kid == field) {
  378 + field.warn("while traversing /AcroForm found field with two parents");
  379 + return true;
  380 + }
  381 + }
  382 + for (auto const& kid: Array(field["/Parent"]["/Kids"])) {
368 383 if (kid == parent) {
369 384 field.warn("loop detected while traversing /AcroForm");
370   - return;
  385 + return false;
371 386 }
372 387 }
373 388 field.warn("encountered invalid /Parent entry while traversing /AcroForm; correcting");
... ... @@ -387,8 +402,15 @@ QPDFAcroFormDocumentHelper::traverseField(
387 402 }
388 403  
389 404 for (auto const& kid: Kids) {
390   - traverseField(kid, field, 1 + depth);
  405 + if (m->bad_fields.contains(kid)) {
  406 + continue;
  407 + }
  408 +
  409 + if (!traverseField(kid, field, 1 + depth)) {
  410 + m->bad_fields.insert(kid);
  411 + }
391 412 }
  413 + return true;
392 414 }
393 415  
394 416 bool
... ...