From afe0242b263a9e1a8d51dd81e42ab6de2e5127eb Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Wed, 26 Jul 2017 04:30:32 -0400 Subject: [PATCH] Handle object ID 0 (fixes #99) This is CVE-2017-9208. The QPDF library uses object ID 0 internally as a sentinel to represent a direct object, but prior to this fix, was not blocking handling of 0 0 obj or 0 0 R as a special case. Creating an object in the file with 0 0 obj could cause various infinite loops. The PDF spec doesn't allow for object 0. Having qpdf handle object 0 might be a better fix, but changing all the places in the code that assumes objid == 0 means direct would be risky. --- ChangeLog | 4 ++ libqpdf/QPDF.cc | 8 ++++ libqpdf/QPDFObjectHandle.cc | 10 +++++ qpdf/qpdf.testcov | 3 ++ qpdf/qtest/qpdf.test | 4 +- qpdf/qtest/qpdf/issue-99.out | 4 ++ qpdf/qtest/qpdf/issue-99.pdf | Bin 0 -> 4798 bytes qpdf/qtest/qpdf/issue-99b.out | 5 +++ qpdf/qtest/qpdf/issue-99b.pdf | 79 ++++++++++++++++++++++++++++++++++ 9 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 qpdf/qtest/qpdf/issue-99.out create mode 100644 qpdf/qtest/qpdf/issue-99.pdf create mode 100644 qpdf/qtest/qpdf/issue-99b.out create mode 100644 qpdf/qtest/qpdf/issue-99b.pdf diff --git a/ChangeLog b/ChangeLog index ce0ce1e7..f39c52c3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2017-07-26 Jay Berkenbilt + * CVE-2017-9208: Handle references to and appearance of object 0 + as a special case. Object 0 is not allowed, and qpdf was using it + internally to represent direct objects. + * CVE-2017-9209: Fix infinite loop caused by attempting to reconstruct the xref table while already in the process of reconstructing the xref table. diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index a50c87ad..846f188f 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -1350,6 +1350,14 @@ QPDF::readObjectAtOffset(bool try_recovery, objid = atoi(tobjid.getValue().c_str()); generation = atoi(tgen.getValue().c_str()); + if (objid == 0) + { + QTC::TC("qpdf", "QPDF object id 0"); + throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), + this->last_object_description, offset, + "object with ID 0"); + } + if ((exp_objid >= 0) && (! ((objid == exp_objid) && (generation == exp_generation)))) { diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 687ba439..cd3084cb 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1089,6 +1089,16 @@ QPDFObjectHandle::parseInternal(PointerHolder input, QPDFObjectHandle QPDFObjectHandle::newIndirect(QPDF* qpdf, int objid, int generation) { + if (objid == 0) + { + // Special case: QPDF uses objid 0 as a sentinel for direct + // objects, and the PDF specification doesn't allow for object + // 0. Treat indirect references to object 0 as null so that we + // never create an indirect object with objid 0. + QTC::TC("qpdf", "QPDFObjectHandle indirect with 0 objid"); + return newNull(); + } + return QPDFObjectHandle(qpdf, objid, generation); } diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index d43939ea..f3ddd60d 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -273,3 +273,6 @@ QPDFWriter standard deterministic ID 1 QPDFWriter linearized deterministic ID 1 QPDFWriter deterministic with no data 0 qpdf-c called qpdf_set_deterministic_ID 0 +QPDFObjectHandle indirect with 0 objid 0 +QPDF object id 0 0 +QPDF caught recursive xref reconstruction 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index dd8dad30..c45215fa 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -206,7 +206,7 @@ $td->runtest("remove page we don't have", show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 79; +$n_tests += 81; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -220,6 +220,8 @@ $td->runtest("C API: qpdf version", # Files to reproduce various bugs foreach my $d ( + ["99", "object 0"], + ["99b", "object 0"], ["100","xref reconstruction loop"], ["101", "resolve for exception text"], ) diff --git a/qpdf/qtest/qpdf/issue-99.out b/qpdf/qtest/qpdf/issue-99.out new file mode 100644 index 00000000..89d6e174 --- /dev/null +++ b/qpdf/qtest/qpdf/issue-99.out @@ -0,0 +1,4 @@ +WARNING: issue-99.pdf: file is damaged +WARNING: issue-99.pdf (file position 3526): xref not found +WARNING: issue-99.pdf: Attempting to reconstruct cross-reference table +operation for Dictionary object attempted on object of wrong type diff --git a/qpdf/qtest/qpdf/issue-99.pdf b/qpdf/qtest/qpdf/issue-99.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3f370176d0c36ef6a4861555f012079757ca257e GIT binary patch literal 4798 zcmeI0c~}$I7Qh2Vkx>K-S`dV36$KNVOg0DvlqAHkxCKQj=F^Z25X?d+6TziI6&H2{ zSzI2aiv6_WhE<_ec?z|qO0^0qRlrxI?pUe#s(!WaP69DROW*sRub+Rs`(-kjn=|*E zd*_^Ue`maA3q`)@6c*%FUs->w{z(1D5Cf*e+PEZ$$3r49O#+b!GeM685h+y!h9e@C zjKGALT&uu>gCQLehso4X>WtnTXIkWlUYc{3r1sQd=bjqt(Uk4uY;g$FD0Bo)3V~nb zxDYv+Aqj4^&%pzhJZ;a353kXwel#8h(&>G8`c5=V6p%fqo9B?*R96n8tn}LT9q+szKUodD4T7-}e`X&1SOUco+f} zk&VFsPC+(UW9p-$+?V=LCV1o)a!d|bG>blz9bwr9bt_BZ|6jk!D=@gzzsZ*W<+jED z(zH6Ffb^EB6#nkh&&K^zm{&K|bn!V*Si`ugt zT~@A2#uIts+rPZ`*%u#Byd%c$dW+rlEOK@QWm=#ZM9^1@%DKQT|1scd5zhb5ia^iXO=)Ns$gA7 zT2T};%9WWRDG#o=8Z8d}LVfh=x6s$L;|1ubJ3s5Bg^wY>p7mVPii_`AiI^A(Tse|_d1_cv$_6^cD%Q@^pe?lXY37y2ZPsT>I=pd*_v%^kFB*cQ606b<=LfzT7hcQSH)qB)aUDZ7h^}e4gw26QjP#-TR{-huY=n zvgvV``dY7+!M@iHdA-C%{SXwvKk0VID2~(4NGheBYCc-Ei zMZ}mgA(4QYD8g3~y4e^m(5jQQ8j^ne`*{wagFE1;mw^soMvkcwFxd6TUARvrV4*NI z%SaZ0Lte%(qb&mCDa?4{1Hdo=P(Ox|Xkm_FD9Ju14ofm{*pydum@b(lIr*|b;IVgyJnE+ea=^gEg z(=)=SS-)N2Xm_mO=Zl)#>IWXoz^x^YCo&upzuP-R^CVR8;9+ii+7Zf9>GIgN(y$#1 z#uPsfczG5CWt=XDDgbaW&ldpv(zHE%*-U~B7r&4f9@Zk3lVJzi~f+;>s-- zFYK4z=T$p9bYza#CliGdTgnn8<=hk3E4%WVsY_{>!otPzF}~;XK33SJa}wRttE%y- z_#`AKVRDdXi)Y99MGMyk7mV1`Qx`FW<-4w2P8)YF=7LYPRMnIrJN}kOu|vt0vh13@ zvE}O+uG?C2kNuR?(7nI6)~43C*_}F~iUr-Wp*A1*?9z@Q+42(qt@SP&q`9T%9I~5c zmQQ@=+QGB0MDj@sQ)npxs`7)u%g07H>Uj1n`AE;0=Q#a$>xisO*6$mJFsnL6J9|do z4excUa=%>EvM6-8CgyS0;j}T4!y9s;9$Vcl%%y1@logWEl$%d$OLjI0#&(RAD&zswYah9D`9NBGGCIFca_r zR)uNewXi`yc_B=Tg?YkIF2|4Q&la&+qRP=toEQ6YFQ@aH0A7zwp< zK_Vt!qSLEkgvF0$p(x4>31P6gLXm(=7omI(#}VW<4xhyk8nz%ph7+kc77sDm3=ZVw I6*^1wFRwpQ#Q*>R literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/issue-99b.out b/qpdf/qtest/qpdf/issue-99b.out new file mode 100644 index 00000000..355701be --- /dev/null +++ b/qpdf/qtest/qpdf/issue-99b.out @@ -0,0 +1,5 @@ +WARNING: issue-99b.pdf: file is damaged +WARNING: issue-99b.pdf (object 1 0, file position 9): object with ID 0 +WARNING: issue-99b.pdf: Attempting to reconstruct cross-reference table +WARNING: issue-99b.pdf: object 1 0 not found in file after regenerating cross reference table +operation for Dictionary object attempted on object of wrong type diff --git a/qpdf/qtest/qpdf/issue-99b.pdf b/qpdf/qtest/qpdf/issue-99b.pdf new file mode 100644 index 00000000..fcf275f8 --- /dev/null +++ b/qpdf/qtest/qpdf/issue-99b.pdf @@ -0,0 +1,79 @@ +%PDF-1.3 +0 0 obj +<< + /Type /Catalog + /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 612 792] + /Contents 4 0 R + /Resources << + /ProcSet 5 0 R + /Font << + /F1 6 0 R + >> + >> +>> +endobj + +4 0 obj +<< + /Length 44 +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +5 0 obj +[ + /PDF + /Text +] +endobj + +6 0 obj +<< + /Type /Font + /Subtype /Type1 + /Name /F1 + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding +>> +endobj + +xref +0 7 +0000000000 65535 f +0000000009 00000 n +0000000063 00000 n +0000000135 00000 n +0000000307 00000 n +0000000403 00000 n +0000000438 00000 n +trailer << + /Size 7 + /Root 1 0 R +>> +startxref +556 +%%EOF