Commit a11549a566ceed28bc9f6ba100b0d3f6ae59a1f4

Authored by Jay Berkenbilt
1 parent 28a9df51

Detect loops in /Pages structure

Pushing inherited objects to pages and getting all pages were both
prone to stack overflow infinite loops if there were loops in the
Pages dictionary. There is a general weakness in the code in that any
part of the code that traverses the Pages structure would be prone to
this and would have to implement its own loop detection. A more robust
fix may provide some general method for handling the Pages structure,
but it's probably not worth doing.

Note: addition of *Internal2 private functions was done rather than
changing signatures of existing methods to avoid breaking
compatibility.
ChangeLog
1 2015-02-21 Jay Berkenbilt <ejb@ql.org> 1 2015-02-21 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Detect loops in Pages structure. Thanks to Gynvael Coldwind and
  4 + Mateusz Jurczyk of the Google Security Team for providing a sample
  5 + file with this problem.
  6 +
3 * Prevent buffer overrun when converting a password to an 7 * Prevent buffer overrun when converting a password to an
4 encryption key. Thanks to Gynvael Coldwind and Mateusz Jurczyk of 8 encryption key. Thanks to Gynvael Coldwind and Mateusz Jurczyk of
5 the Google Security Team for providing a sample file with this 9 the Google Security Team for providing a sample file with this
1 5.2.0 1 5.2.0
2 ===== 2 =====
3 3
  4 + * Before release: remember to bump minor shared library version since
  5 + new methods were added (even though private).
  6 +
4 * Figure out what about a3576a73593987b26cd3eff346f8f7c11f713cbd 7 * Figure out what about a3576a73593987b26cd3eff346f8f7c11f713cbd
5 broke binary compatibility. 8 broke binary compatibility.
6 9
include/qpdf/QPDF.hh
@@ -668,6 +668,9 @@ class QPDF @@ -668,6 +668,9 @@ class QPDF
668 668
669 void getAllPagesInternal(QPDFObjectHandle cur_pages, 669 void getAllPagesInternal(QPDFObjectHandle cur_pages,
670 std::vector<QPDFObjectHandle>& result); 670 std::vector<QPDFObjectHandle>& result);
  671 + void getAllPagesInternal2(QPDFObjectHandle cur_pages,
  672 + std::vector<QPDFObjectHandle>& result,
  673 + std::set<QPDFObjGen>& visited);
671 void insertPage(QPDFObjectHandle newpage, int pos); 674 void insertPage(QPDFObjectHandle newpage, int pos);
672 int findPage(QPDFObjGen const& og); 675 int findPage(QPDFObjGen const& og);
673 int findPage(QPDFObjectHandle& page); 676 int findPage(QPDFObjectHandle& page);
@@ -1023,6 +1026,12 @@ class QPDF @@ -1023,6 +1026,12 @@ class QPDF
1023 std::map<std::string, std::vector<QPDFObjectHandle> >&, 1026 std::map<std::string, std::vector<QPDFObjectHandle> >&,
1024 std::vector<QPDFObjectHandle>& all_pages, 1027 std::vector<QPDFObjectHandle>& all_pages,
1025 bool allow_changes, bool warn_skipped_keys); 1028 bool allow_changes, bool warn_skipped_keys);
  1029 + void pushInheritedAttributesToPageInternal2(
  1030 + QPDFObjectHandle,
  1031 + std::map<std::string, std::vector<QPDFObjectHandle> >&,
  1032 + std::vector<QPDFObjectHandle>& all_pages,
  1033 + bool allow_changes, bool warn_skipped_keys,
  1034 + std::set<QPDFObjGen>& visited);
1026 void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh); 1035 void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
1027 void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh, 1036 void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
1028 std::set<QPDFObjGen>& visited, bool top); 1037 std::set<QPDFObjGen>& visited, bool top);
libqpdf/QPDF_optimization.cc
@@ -174,6 +174,30 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -174,6 +174,30 @@ QPDF::pushInheritedAttributesToPageInternal(
174 std::vector<QPDFObjectHandle>& pages, 174 std::vector<QPDFObjectHandle>& pages,
175 bool allow_changes, bool warn_skipped_keys) 175 bool allow_changes, bool warn_skipped_keys)
176 { 176 {
  177 + std::set<QPDFObjGen> visited;
  178 + pushInheritedAttributesToPageInternal2(
  179 + cur_pages, key_ancestors, pages, allow_changes,
  180 + warn_skipped_keys, visited);
  181 +}
  182 +
  183 +void
  184 +QPDF::pushInheritedAttributesToPageInternal2(
  185 + QPDFObjectHandle cur_pages,
  186 + std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors,
  187 + std::vector<QPDFObjectHandle>& pages,
  188 + bool allow_changes, bool warn_skipped_keys,
  189 + std::set<QPDFObjGen>& visited)
  190 +{
  191 + QPDFObjGen this_og = cur_pages.getObjGen();
  192 + if (visited.count(this_og) > 0)
  193 + {
  194 + throw QPDFExc(
  195 + qpdf_e_pages, this->file->getName(),
  196 + this->last_object_description, 0,
  197 + "Loop detected in /Pages structure (inherited attributes)");
  198 + }
  199 + visited.insert(this_og);
  200 +
177 // Extract the underlying dictionary object 201 // Extract the underlying dictionary object
178 std::string type = cur_pages.getKey("/Type").getName(); 202 std::string type = cur_pages.getKey("/Type").getName();
179 203
@@ -259,9 +283,9 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -259,9 +283,9 @@ QPDF::pushInheritedAttributesToPageInternal(
259 int n = kids.getArrayNItems(); 283 int n = kids.getArrayNItems();
260 for (int i = 0; i < n; ++i) 284 for (int i = 0; i < n; ++i)
261 { 285 {
262 - pushInheritedAttributesToPageInternal( 286 + pushInheritedAttributesToPageInternal2(
263 kids.getArrayItem(i), key_ancestors, pages, 287 kids.getArrayItem(i), key_ancestors, pages,
264 - allow_changes, warn_skipped_keys); 288 + allow_changes, warn_skipped_keys, visited);
265 } 289 }
266 290
267 // For each inheritable key, pop the stack. If the stack 291 // For each inheritable key, pop the stack. If the stack
@@ -318,6 +342,7 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -318,6 +342,7 @@ QPDF::pushInheritedAttributesToPageInternal(
318 this->file->getLastOffset(), 342 this->file->getLastOffset(),
319 "invalid Type " + type + " in page tree"); 343 "invalid Type " + type + " in page tree");
320 } 344 }
  345 + visited.erase(this_og);
321 } 346 }
322 347
323 void 348 void
libqpdf/QPDF_pages.cc
@@ -56,6 +56,24 @@ void @@ -56,6 +56,24 @@ void
56 QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, 56 QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
57 std::vector<QPDFObjectHandle>& result) 57 std::vector<QPDFObjectHandle>& result)
58 { 58 {
  59 + std::set<QPDFObjGen> visited;
  60 + getAllPagesInternal2(cur_pages, result, visited);
  61 +}
  62 +
  63 +void
  64 +QPDF::getAllPagesInternal2(QPDFObjectHandle cur_pages,
  65 + std::vector<QPDFObjectHandle>& result,
  66 + std::set<QPDFObjGen>& visited)
  67 +{
  68 + QPDFObjGen this_og = cur_pages.getObjGen();
  69 + if (visited.count(this_og) > 0)
  70 + {
  71 + throw QPDFExc(
  72 + qpdf_e_pages, this->file->getName(),
  73 + this->last_object_description, 0,
  74 + "Loop detected in /Pages structure (getAllPages)");
  75 + }
  76 + visited.insert(this_og);
59 std::string type; 77 std::string type;
60 QPDFObjectHandle type_key = cur_pages.getKey("/Type"); 78 QPDFObjectHandle type_key = cur_pages.getKey("/Type");
61 if (type_key.isName()) 79 if (type_key.isName())
@@ -76,7 +94,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, @@ -76,7 +94,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
76 int n = kids.getArrayNItems(); 94 int n = kids.getArrayNItems();
77 for (int i = 0; i < n; ++i) 95 for (int i = 0; i < n; ++i)
78 { 96 {
79 - getAllPagesInternal(kids.getArrayItem(i), result); 97 + getAllPagesInternal2(kids.getArrayItem(i), result, visited);
80 } 98 }
81 } 99 }
82 else if (type == "/Page") 100 else if (type == "/Page")
@@ -90,6 +108,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, @@ -90,6 +108,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
90 this->file->getLastOffset(), 108 this->file->getLastOffset(),
91 "invalid Type " + type + " in page tree"); 109 "invalid Type " + type + " in page tree");
92 } 110 }
  111 + visited.erase(this_og);
93 } 112 }
94 113
95 void 114 void
qpdf/qtest/qpdf.test
@@ -199,7 +199,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;, @@ -199,7 +199,7 @@ $td-&gt;runtest(&quot;remove page we don&#39;t have&quot;,
199 show_ntests(); 199 show_ntests();
200 # ---------- 200 # ----------
201 $td->notify("--- Miscellaneous Tests ---"); 201 $td->notify("--- Miscellaneous Tests ---");
202 -$n_tests += 75; 202 +$n_tests += 76;
203 203
204 $td->runtest("qpdf version", 204 $td->runtest("qpdf version",
205 {$td->COMMAND => "qpdf --version"}, 205 {$td->COMMAND => "qpdf --version"},
@@ -566,6 +566,10 @@ $td-&gt;runtest(&quot;ensure arguments to R are direct&quot;, @@ -566,6 +566,10 @@ $td-&gt;runtest(&quot;ensure arguments to R are direct&quot;,
566 {$td->COMMAND => "qpdf --check indirect-r-arg.pdf"}, 566 {$td->COMMAND => "qpdf --check indirect-r-arg.pdf"},
567 {$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 2}, 567 {$td->FILE => "indirect-r-arg.out", $td->EXIT_STATUS => 2},
568 $td->NORMALIZE_NEWLINES); 568 $td->NORMALIZE_NEWLINES);
  569 +$td->runtest("detect loops in pages structure",
  570 + {$td->COMMAND => "qpdf --check pages-loop.pdf"},
  571 + {$td->FILE => "pages-loop.out", $td->EXIT_STATUS => 2},
  572 + $td->NORMALIZE_NEWLINES);
569 573
570 show_ntests(); 574 show_ntests();
571 # ---------- 575 # ----------
qpdf/qtest/qpdf/pages-loop.out 0 โ†’ 100644
  1 +checking pages-loop.pdf
  2 +PDF Version: 1.3
  3 +File is not encrypted
  4 +File is not linearized
  5 +pages-loop.pdf (object 3 0): Loop detected in /Pages structure (getAllPages)
qpdf/qtest/qpdf/pages-loop.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Pages 2 0 R
  9 + /Type /Catalog
  10 +>>
  11 +endobj
  12 +
  13 +%% Original object ID: 2 0
  14 +2 0 obj
  15 +<<
  16 + /Count 1
  17 + /Kids [
  18 + 3 0 R
  19 + 2 0 R
  20 + ]
  21 + /Type /Pages
  22 +>>
  23 +endobj
  24 +
  25 +%% Page 1
  26 +%% Original object ID: 3 0
  27 +3 0 obj
  28 +<<
  29 + /Contents 4 0 R
  30 + /MediaBox [
  31 + 0
  32 + 0
  33 + 612
  34 + 792
  35 + ]
  36 + /Parent 2 0 R
  37 + /Resources <<
  38 + /Font <<
  39 + /F1 6 0 R
  40 + >>
  41 + /ProcSet 7 0 R
  42 + >>
  43 + /Type /Page
  44 +>>
  45 +endobj
  46 +
  47 +%% Contents for page 1
  48 +%% Original object ID: 4 0
  49 +4 0 obj
  50 +<<
  51 + /Length 5 0 R
  52 +>>
  53 +stream
  54 +BT
  55 + /F1 24 Tf
  56 + 72 720 Td
  57 + (Potato) Tj
  58 +ET
  59 +endstream
  60 +endobj
  61 +
  62 +5 0 obj
  63 +44
  64 +endobj
  65 +
  66 +%% Original object ID: 6 0
  67 +6 0 obj
  68 +<<
  69 + /BaseFont /Helvetica
  70 + /Encoding /WinAnsiEncoding
  71 + /Name /F1
  72 + /Subtype /Type1
  73 + /Type /Font
  74 +>>
  75 +endobj
  76 +
  77 +%% Original object ID: 5 0
  78 +7 0 obj
  79 +[
  80 + /PDF
  81 + /Text
  82 +]
  83 +endobj
  84 +
  85 +xref
  86 +0 8
  87 +0000000000 65535 f
  88 +0000000052 00000 n
  89 +0000000133 00000 n
  90 +0000000252 00000 n
  91 +0000000494 00000 n
  92 +0000000593 00000 n
  93 +0000000639 00000 n
  94 +0000000784 00000 n
  95 +trailer <<
  96 + /Root 1 0 R
  97 + /Size 8
  98 + /ID [<395875d4235973eebbade9c7e9e7f857><395875d4235973eebbade9c7e9e7f857>]
  99 +>>
  100 +startxref
  101 +819
  102 +%%EOF