Commit 701b518d5c56a1449825a3a37a716c58e05e1c3e

Authored by Jay Berkenbilt
1 parent afe0242b

Detect recursion loops resolving objects (fixes #51)

During parsing of an object, sometimes parts of the object have to be
resolved. An example is stream lengths. If such an object directly or
indirectly points to the object being parsed, it can cause an infinite
loop. Guard against all cases of re-entrant resolution of objects.
ChangeLog
1 2017-07-26 Jay Berkenbilt <ejb@ql.org> 1 2017-07-26 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Detect infinite loops while resolving objects. This could happen
  4 + if something inside an object that had to be resolved during
  5 + parsing, such as a stream length, recursively referenced the
  6 + object being resolved.
  7 +
3 * CVE-2017-9208: Handle references to and appearance of object 0 8 * CVE-2017-9208: Handle references to and appearance of object 0
4 as a special case. Object 0 is not allowed, and qpdf was using it 9 as a special case. Object 0 is not allowed, and qpdf was using it
5 internally to represent direct objects. 10 internally to represent direct objects.
include/qpdf/QPDF.hh
@@ -603,6 +603,25 @@ class QPDF @@ -603,6 +603,25 @@ class QPDF
603 int gen; 603 int gen;
604 }; 604 };
605 605
  606 + class ResolveRecorder
  607 + {
  608 + public:
  609 + ResolveRecorder(QPDF* qpdf, QPDFObjGen const& og) :
  610 + qpdf(qpdf),
  611 + og(og)
  612 + {
  613 + qpdf->resolving.insert(og);
  614 + }
  615 + virtual ~ResolveRecorder()
  616 + {
  617 + this->qpdf->resolving.erase(og);
  618 + }
  619 + private:
  620 + QPDF* qpdf;
  621 + QPDFObjGen og;
  622 + };
  623 + friend class ResolveRecorder;
  624 +
606 void parse(char const* password); 625 void parse(char const* password);
607 void warn(QPDFExc const& e); 626 void warn(QPDFExc const& e);
608 void setTrailer(QPDFObjectHandle obj); 627 void setTrailer(QPDFObjectHandle obj);
@@ -1065,6 +1084,7 @@ class QPDF @@ -1065,6 +1084,7 @@ class QPDF
1065 std::map<QPDFObjGen, QPDFXRefEntry> xref_table; 1084 std::map<QPDFObjGen, QPDFXRefEntry> xref_table;
1066 std::set<int> deleted_objects; 1085 std::set<int> deleted_objects;
1067 std::map<QPDFObjGen, ObjCache> obj_cache; 1086 std::map<QPDFObjGen, ObjCache> obj_cache;
  1087 + std::set<QPDFObjGen> resolving;
1068 QPDFObjectHandle trailer; 1088 QPDFObjectHandle trailer;
1069 std::vector<QPDFObjectHandle> all_pages; 1089 std::vector<QPDFObjectHandle> all_pages;
1070 std::map<QPDFObjGen, int> pageobj_to_pages_pos; 1090 std::map<QPDFObjGen, int> pageobj_to_pages_pos;
libqpdf/QPDF.cc
@@ -1471,6 +1471,21 @@ QPDF::resolve(int objid, int generation) @@ -1471,6 +1471,21 @@ QPDF::resolve(int objid, int generation)
1471 // to insert things into the object cache that don't actually 1471 // to insert things into the object cache that don't actually
1472 // exist in the file. 1472 // exist in the file.
1473 QPDFObjGen og(objid, generation); 1473 QPDFObjGen og(objid, generation);
  1474 + if (this->resolving.count(og))
  1475 + {
  1476 + // This can happen if an object references itself directly or
  1477 + // indirectly in some key that has to be resolved during
  1478 + // object parsing, such as stream length.
  1479 + QTC::TC("qpdf", "QPDF recursion loop in resolve");
  1480 + warn(QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
  1481 + "", this->file->getLastOffset(),
  1482 + "loop detected resolving object " +
  1483 + QUtil::int_to_string(objid) + " " +
  1484 + QUtil::int_to_string(generation)));
  1485 + return new QPDF_Null;
  1486 + }
  1487 + ResolveRecorder rr(this, og);
  1488 +
1474 if (! this->obj_cache.count(og)) 1489 if (! this->obj_cache.count(og))
1475 { 1490 {
1476 if (! this->xref_table.count(og)) 1491 if (! this->xref_table.count(og))
qpdf/qpdf.testcov
@@ -276,3 +276,4 @@ qpdf-c called qpdf_set_deterministic_ID 0 @@ -276,3 +276,4 @@ qpdf-c called qpdf_set_deterministic_ID 0
276 QPDFObjectHandle indirect with 0 objid 0 276 QPDFObjectHandle indirect with 0 objid 0
277 QPDF object id 0 0 277 QPDF object id 0 0
278 QPDF caught recursive xref reconstruction 0 278 QPDF caught recursive xref reconstruction 0
  279 +QPDF recursion loop in resolve 0
qpdf/qtest/qpdf.test
@@ -206,7 +206,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;, @@ -206,7 +206,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;,
206 show_ntests(); 206 show_ntests();
207 # ---------- 207 # ----------
208 $td->notify("--- Miscellaneous Tests ---"); 208 $td->notify("--- Miscellaneous Tests ---");
209 -$n_tests += 81; 209 +$n_tests += 82;
210 210
211 $td->runtest("qpdf version", 211 $td->runtest("qpdf version",
212 {$td->COMMAND => "qpdf --version"}, 212 {$td->COMMAND => "qpdf --version"},
@@ -220,6 +220,7 @@ $td-&gt;runtest(&quot;C API: qpdf version&quot;, @@ -220,6 +220,7 @@ $td-&gt;runtest(&quot;C API: qpdf version&quot;,
220 220
221 # Files to reproduce various bugs 221 # Files to reproduce various bugs
222 foreach my $d ( 222 foreach my $d (
  223 + ["51", "resolve loop"],
223 ["99", "object 0"], 224 ["99", "object 0"],
224 ["99b", "object 0"], 225 ["99b", "object 0"],
225 ["100","xref reconstruction loop"], 226 ["100","xref reconstruction loop"],
qpdf/qtest/qpdf/issue-51.out 0 → 100644
  1 +WARNING: issue-51.pdf: reported number of objects (0) inconsistent with actual number of objects (9)
  2 +WARNING: issue-51.pdf (object 7 0, file position 553): expected endobj
  3 +WARNING: issue-51.pdf (object 1 0, file position 359): expected endobj
  4 +WARNING: issue-51.pdf (file position 70): loop detected resolving object 2 0
  5 +WARNING: issue-51.pdf (object 2 0, file position 71): attempting to recover stream length
  6 +issue-51.pdf (object 2 0, file position 71): unable to recover stream data
qpdf/qtest/qpdf/issue-51.pdf 0 → 100644
  1 +%PDF-100000000000002 0 obj
  2 +<</Length 2 0 R/000000/00000000000>>
  3 +stream
  4 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 0 obj
  5 +<</0000000000000000 0 0 R/000000000 0 0 R/00000000[00000000000]/00000<</0/00000000000000000000000000000000>>/00000000 2 0 R>>000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007 0 obj
  6 +<</0000/0000000/00000 0 0 R
  7 +/0000000000[1 0 R 0000 null null 0]
  8 +/0000(00000)
  9 +>>0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000xref
  10 +0 9
  11 +0000000000 00000 f
  12 +0000000200 00000 n
  13 +0000000009 00000 n
  14 +0000000000 00000 n
  15 +0000000000 00000 n
  16 +0000000000 00000 n
  17 +0000000000 00000 n
  18 +0000000400 00000 n
  19 +0000000000 00000 n
  20 +trailer<</Size 0/Root 7 0 R>>startxref
  21 +740
  22 +%%EOF
0 \ No newline at end of file 23 \ No newline at end of file