CVE-2017-18186: Loop with Unreachable Exit Condition ('Infinite Loop') (CWE-835)
value: An issue was discovered in QPDF before 7.0.0. There is an infinite loop due to looping xref tables in QPDF.cc.
CWE description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVSS v3 base score: 5.5
Published: 2018-02-13T19:29Z
Repository: https://github.com/qpdf/qpdf
Language: Other
Files changed: ChangeLog, libqpdf/QPDF.cc, qpdf/qtest/qpdf.test, qpdf/qtest/qpdf/issue-149.out, qpdf/qtest/qpdf/issue-149.pdf
Commit date: 2017-08-25 19:58:31 -0400
Commit message: Detect xref pointer infinite loop (fixes #149)

Diff: diff --git a/ChangeLog b/ChangeLog
index 510c6145..bd0acc2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
+2017-08-25  Jay Berkenbilt  <ejb@ql.org>
+
+	* Detect infinite loop while finding additional xref tables. Fixes
+	#149.
+
 2017-08-22  Jay Berkenbilt  <ejb@ql.org>
 
 	* 7.0.b1: release
 
 	* Convert all README files to markdown. Names changed as follows:
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 27efdd55..86e798ee 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -489,12 +489,14 @@ QPDF::reconstruct_xref(QPDFExc& e)
 
 void
 QPDF::read_xref(qpdf_offset_t xref_offset)
 {
     std::map<int, int> free_table;
+    std::set<qpdf_offset_t> visited;
     while (xref_offset)
     {
+        visited.insert(xref_offset);
         char buf[7];
         memset(buf, 0, sizeof(buf));
 	this->m->file->seek(xref_offset, SEEK_SET);
 	this->m->file->read(buf, sizeof(buf) - 1);
         // The PDF spec says xref must be followed by a line
@@ -518,10 +520,14 @@ QPDF::read_xref(qpdf_offset_t xref_offset)
 	}
 	else
 	{
 	    xref_offset = read_xrefStream(xref_offset);
 	}
+        if (visited.count(xref_offset) != 0)
+        {
+            xref_offset = 0;
+        }
     }
 
     if (! this->m->trailer.isInitialized())
     {
         throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(), "", 0,
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 232e421e..9ecf0005 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -219,10 +219,11 @@ my @bug_tests = (
     ["120", "other infinite loop", 3],
     ["106", "zlib data error", 3],
     ["141a", "/W entry size 0", 2],
     ["141b", "/W entry size 0", 2],
     ["143", "self-referential ostream", 3],
+    ["149", "xref prev pointer loop", 3],
     );
 $n_tests += scalar(@bug_tests);
 foreach my $d (@bug_tests)
 {
     my ($n, $description, $exit_status) = @$d;
diff --git a/qpdf/qtest/qpdf/issue-149.out b/qpdf/qtest/qpdf/issue-149.out
new file mode 100644
index 00000000..5c473389
--- /dev/null
+++ b/qpdf/qtest/qpdf/issue-149.out
@@ -0,0 +1,2 @@
+WARNING: issue-149.pdf: reported number of objects (11) inconsistent with actual number of objects (7)
+qpdf: operation succeeded with warnings; resulting file may have some problems
diff --git a/qpdf/qtest/qpdf/issue-149.pdf b/qpdf/qtest/qpdf/issue-149.pdf
new file mode 100644
index 00000000..9a2f8a88
--- /dev/null
+++ b/qpdf/qtest/qpdf/issue-149.pdf
@@ -0,0 +1,120 @@
+%PDF-1.7
+%Á∑·∂
+% vim: set tw=76 ts=2 et :
+% convert with:
+% ./fixlengths < hello.txt > hello.pdf ; ./genxref hello.pdf
+1 0 obj
+  <<  /Type /Catalog
+      /Outlines 6 0 R
+      /Pages 2 0 R
+  >>
+endobj
+
+2 0 obj
+  <<  /Type /Pages
+      /Kids [3 0 R]
+      /Count 1
+  >>
+endobj
+
+3 0 obj
+  <<  /Type /Page
+      /Parent 2 0 R
+      /MediaBox [0 0 595.276 841.89]
+      /Contents 4 0 R
+      /Resources <<
+        /ProcSet [/PDF /Text]
+        /Font << /F1 5 0 R >>
+        >>
+  >>
+endobj
+
+4 0 obj
+  <<  /Length 36  >>
+stream
+  BT 80 100 Td /F1 48 Tf (foo) Tj ET
+endstream
+endobj
+
+5 0 obj
+  <<  /Type /Font
+      /Subtype /Type1
+      /Name /F1
+      /BaseFont /Helvetica
+      /Encoding /MacRomanEncoding
+  >>
+endobj
+
+6 0 obj
+  <<  /Type /Outlines
+      /Count 0
+  >>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000125 00000 n
+0000000208 00000 n
+0000000283 00000 n
+0000000492 00000 n
+0000000583 00000 n
+0000000721 00000 n
+
+trailer
+  <<  /Size 7
+      /Prev 1516
+      /Root 1 0 R
+  >>
+startxref
+779%%EOF
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+1 1 obj
+  <<  /Type /Catalog
+      /Outlines 6 0 R
+      /Pages 2 1 R
+  >>
+endobj
+
+2 1 obj
+  <<  /Type /Pages
+      /Kids [3 1 R]
+      /Count 1
+  >>
+endobj
+
+3 1 obj
+  <<  /Type /Page
+      /Parent 2 1 R
+      /MediaBox [0 0 595.276 841.89]
+      /Contents 4 1 R
+      /Resources <<
+        /ProcSet [/PDF /Text]
+        /Font << /F1 5 0 R >>
+        >>
+  >>
+endobj
+
+4 1 obj
+  <<  /Length 36  >>
+stream
+  BT 80 100 Td /F1 48 Tf (bar) Tj ET
+endstream
+endobj
+
+xref
+1 4
+0000001058 00001 n
+0000001141 00001 n
+0000001216 00001 n
+0000001425 00001 n
+
+trailer
+  <<  /Size 11
+      /Prev 779
+      /Root 1 1 R
+  >>
+startxref
+1516
+%%EOF

Fixed code: 2017-08-25  Jay Berkenbilt  <ejb@ql.org>

	* Detect infinite loop while finding additional xref tables. Fixes
	#149.

    std::set<qpdf_offset_t> visited;
        visited.insert(xref_offset);
        if (visited.count(xref_offset) != 0)
        {
            xref_offset = 0;
        }
    ["149", "xref prev pointer loop", 3],
WARNING: issue-149.pdf: reported number of objects (11) inconsistent with actual number of objects (7)
qpdf: operation succeeded with warnings; resulting file may have some problems
%PDF-1.7
%Á∑·∂
% vim: set tw=76 ts=2 et :
% convert with:
% ./fixlengths < hello.txt > hello.pdf ; ./genxref hello.pdf
1 0 obj
  <<  /Type /Catalog
      /Outlines 6 0 R
      /Pages 2 0 R
  >>
endobj

2 0 obj
  <<  /Type /Pages
      /Kids [3 0 R]
      /Count 1
  >>
endobj

3 0 obj
  <<  /Type /Page
      /Parent 2 0 R
      /MediaBox [0 0 595.276 841.89]
      /Contents 4 0 R
      /Resources <<
        /ProcSet [/PDF /Text]
        /Font << /F1 5 0 R >>
        >>
  >>
endobj

4 0 obj
  <<  /Length 36  >>
stream
  BT 80 100 Td /F1 48 Tf (foo) Tj ET
endstream
endobj

5 0 obj
  <<  /Type /Font
      /Subtype /Type1
      /Name /F1
      /BaseFont /Helvetica
      /Encoding /MacRomanEncoding
  >>
endobj

6 0 obj
  <<  /Type /Outlines
      /Count 0
  >>
endobj

xref
0 7
0000000000 65535 f
0000000125 00000 n
0000000208 00000 n
0000000283 00000 n
0000000492 00000 n
0000000583 00000 n
0000000721 00000 n

trailer
  <<  /Size 7
      /Prev 1516
      /Root 1 0 R
  >>
startxref
779%%EOF

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1 1 obj
  <<  /Type /Catalog
      /Outlines 6 0 R
      /Pages 2 1 R
  >>
endobj

2 1 obj
  <<  /Type /Pages
      /Kids [3 1 R]
      /Count 1
  >>
endobj

3 1 obj
  <<  /Type /Page
      /Parent 2 1 R
      /MediaBox [0 0 595.276 841.89]
      /Contents 4 1 R
      /Resources <<
        /ProcSet [/PDF /Text]
        /Font << /F1 5 0 R >>
        >>
  >>
endobj

4 1 obj
  <<  /Length 36  >>
stream
  BT 80 100 Td /F1 48 Tf (bar) Tj ET
endstream
endobj

xref
1 4
0000001058 00001 n
0000001141 00001 n
0000001216 00001 n
0000001425 00001 n

trailer
  <<  /Size 11
      /Prev 779
      /Root 1 1 R
  >>
startxref
1516
%%EOF