CVE-2017-18183: 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 in the QPDFWriter::enqueueObject() function in libqpdf/QPDFWriter.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: C++
Files changed: libqpdf/QPDFWriter.cc, qpdf/qpdf.testcov, qpdf/qtest/qpdf.test, qpdf/qtest/qpdf/issue-143.out
Commit date: 2017-08-12 08:36:36 -0400
Commit message: Fix infinite loop in QPDFWriter (fixes #143)

Diff: diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index de4fa349..345613ad 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -1052,10 +1052,13 @@ QPDFWriter::enqueueObject(QPDFObjectHandle object)
 	    {
 		// This is in an object stream.  Don't process it
 		// here.  Instead, enqueue the object stream.  Object
 		// streams always have generation 0.
 		int stream_id = this->object_to_object_stream[og];
+                // Detect loops by storing invalid object ID 0, which
+                // will get overwritten later.
+                obj_renumber[og] = 0;
 		enqueueObject(this->pdf.getObjectByID(stream_id, 0));
 	    }
 	    else
 	    {
 		object_queue.push_back(object);
@@ -1077,10 +1080,16 @@ QPDFWriter::enqueueObject(QPDFObjectHandle object)
 		    // reserve next object ID for length
 		    ++next_objid;
 		}
 	    }
 	}
+        else if (obj_renumber[og] == 0)
+        {
+            // This can happen if a specially constructed file
+            // indicates that an object stream is inside itself.
+            QTC::TC("qpdf", "QPDFWriter ignore self-referential object stream");
+        }
     }
     else if (object.isArray())
     {
 	int n = object.getArrayNItems();
 	for (int i = 0; i < n; ++i)
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 0038aa34..f5b628b9 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -292,5 +292,6 @@ QPDFTokenizer allowing bad token 0
 QPDF ignore first space in xref entry 0
 QPDF ignore first extra space in xref entry 0
 QPDF ignore second extra space in xref entry 0
 QPDF ignore length error xref entry 0
 QPDF_encryption pad short parameter 0
+QPDFWriter ignore self-referential object stream 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 06f298e0..1aaeb8d4 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -218,10 +218,11 @@ my @bug_tests = (
     ["119", "other infinite loop", 3],
     ["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],
     );
 $n_tests += scalar(@bug_tests);
 foreach my $d (@bug_tests)
 {
     my ($n, $description, $exit_status) = @$d;
diff --git a/qpdf/qtest/qpdf/issue-143.out b/qpdf/qtest/qpdf/issue-143.out
new file mode 100644
index 00000000..2bf1e40d
--- /dev/null
+++ b/qpdf/qtest/qpdf/issue-143.out
@@ -0,0 +1,19 @@
+WARNING: issue-143.pdf: can't find PDF header
+WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): stream keyword not followed by proper line terminator
+WARNING: issue-143.pdf (xref stream: object 3 0, file position 607): stream dictionary lacks /Length key
+WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): attempting to recover stream length
+WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): recovered stream length: 36
+WARNING: issue-143.pdf (xref stream: object 3 0, file position 694): expected endobj
+WARNING: issue-143.pdf: file is damaged
+WARNING: issue-143.pdf (object 1 0, file position 48): expected n n obj
+WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table
+WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake1
+WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake2
+WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake3
+WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake4
+WARNING: issue-143.pdf (object 1 0, file position 21): stream dictionary lacks /Length key
+WARNING: issue-143.pdf (object 1 0, file position 84): attempting to recover stream length
+WARNING: issue-143.pdf (object 1 0, file position 84): recovered stream length: 606
+WARNING: issue-143.pdf (object 1 0, file position 694): expected endobj
+WARNING: object stream 1 (file position 33): expected dictionary key but found non-name object; inserting key /QPDFFake1
+qpdf: operation succeeded with warnings; resulting file may have some problems
diff --git a/qpdf/qtest/qpdf/issue-143.pdf b/qpdf/qtest/qpdf/issue-143.pdf
new file mode 100644
index 00000000..b4546036
Binary files /dev/null and b/qpdf/qtest/qpdf/issue-143.pdf differ

Fixed code:                 // Detect loops by storing invalid object ID 0, which
                // will get overwritten later.
                obj_renumber[og] = 0;
        else if (obj_renumber[og] == 0)
        {
            // This can happen if a specially constructed file
            // indicates that an object stream is inside itself.
            QTC::TC("qpdf", "QPDFWriter ignore self-referential object stream");
        }
QPDFWriter ignore self-referential object stream 0
    ["143", "self-referential ostream", 3],
WARNING: issue-143.pdf: can't find PDF header
WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): stream keyword not followed by proper line terminator
WARNING: issue-143.pdf (xref stream: object 3 0, file position 607): stream dictionary lacks /Length key
WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): attempting to recover stream length
WARNING: issue-143.pdf (xref stream: object 3 0, file position 654): recovered stream length: 36
WARNING: issue-143.pdf (xref stream: object 3 0, file position 694): expected endobj
WARNING: issue-143.pdf: file is damaged
WARNING: issue-143.pdf (object 1 0, file position 48): expected n n obj
WARNING: issue-143.pdf: Attempting to reconstruct cross-reference table
WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake1
WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake2
WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake3
WARNING: issue-143.pdf (file position 24): expected dictionary key but found non-name object; inserting key /QPDFFake4
WARNING: issue-143.pdf (object 1 0, file position 21): stream dictionary lacks /Length key
WARNING: issue-143.pdf (object 1 0, file position 84): attempting to recover stream length
WARNING: issue-143.pdf (object 1 0, file position 84): recovered stream length: 606
WARNING: issue-143.pdf (object 1 0, file position 694): expected endobj
WARNING: object stream 1 (file position 33): expected dictionary key but found non-name object; inserting key /QPDFFake1
qpdf: operation succeeded with warnings; resulting file may have some problems