From 5508f74603d7a816ab67456939bd3ee57676f842 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 9 Nov 2019 11:29:52 -0500 Subject: [PATCH] Allow /P in encryption dictionary to be positive (fixes #382) Even though this is disallowed by the spec, files like this have been encountered in the wild. --- ChangeLog | 11 +++++++ libqpdf/QPDFPageObjectHelper.cc | 1 + libqpdf/QPDFWriter.cc | 2 +- libqpdf/QPDF_encryption.cc | 4 +-- qpdf/qtest/qpdf.test | 26 +++++++++++++++ qpdf/qtest/qpdf/copied-positive-P.pdf | Bin 0 -> 1480 bytes qpdf/qtest/qpdf/decrypted-positive-P.pdf | Bin 0 -> 865 bytes qpdf/qtest/qpdf/encrypted-positive-P.pdf | 39 +++++++++++++++++++++++ 8 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 qpdf/qtest/qpdf/copied-positive-P.pdf create mode 100644 qpdf/qtest/qpdf/decrypted-positive-P.pdf create mode 100644 qpdf/qtest/qpdf/encrypted-positive-P.pdf diff --git a/ChangeLog b/ChangeLog index d2013565..3e3d7a4a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2019-11-09 Jay Berkenbilt + + * When reading /P from the encryption dictionary, use static_cast + instead of QIntC to convert the value to a signed integer. The + value of /P is a bit field, and PDF files have been found in the + wild where /P is represented as an unsigned integer even though + the spec states that it is a signed 32-bit value. By using + static_cast, we allow qpdf to compensate for writers that + incorrectly represent the correct bit field as an unsigned value. + Fixes #382. + 2019-11-05 Jay Berkenbilt * Add support for pluggable crypto providers, enabling multiple diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index 8ecd0144..d588c322 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -661,6 +661,7 @@ QPDFPageObjectHelper::getFormXObjectForPage(bool handle_transformations) return result; } +// ABI: name should be std:string const& std::string QPDFPageObjectHelper::placeFormXObject( QPDFObjectHandle fo, std::string name, diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index a31f5da9..7b769ae4 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -762,7 +762,7 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) V, encrypt.getKey("/R").getIntValueAsInt(), key_len, - encrypt.getKey("/P").getIntValueAsInt(), + static_cast(encrypt.getKey("/P").getIntValue()), encrypt.getKey("/O").getStringValue(), encrypt.getKey("/U").getStringValue(), OE, diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index 5a6735ee..9880face 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -877,7 +877,7 @@ QPDF::initializeEncryption() int R = encryption_dict.getKey("/R").getIntValueAsInt(); std::string O = encryption_dict.getKey("/O").getStringValue(); std::string U = encryption_dict.getKey("/U").getStringValue(); - int P = encryption_dict.getKey("/P").getIntValueAsInt(); + int P = static_cast(encryption_dict.getKey("/P").getIntValue()); // If supporting new encryption R/V values, remember to update // error message inside this if statement. @@ -1448,7 +1448,7 @@ QPDF::isEncrypted(int& R, int& P, int& V, QPDFObjectHandle Pkey = encrypt.getKey("/P"); QPDFObjectHandle Rkey = encrypt.getKey("/R"); QPDFObjectHandle Vkey = encrypt.getKey("/V"); - P = Pkey.getIntValueAsInt(); + P = static_cast(Pkey.getIntValue()); R = Rkey.getIntValueAsInt(); V = Vkey.getIntValueAsInt(); stream_method = this->m->encp->cf_stream; diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 1df8291e..7ea329c2 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -719,6 +719,32 @@ foreach my $d (@bug_tests) $td->NORMALIZE_NEWLINES); } } +show_ntests(); +# ---------- +$td->notify("--- Positive /P in encryption dictionary ---"); +$n_tests += 4; + +# Files have been seen where /P in the encryption dictionary was an +# unsigned rather than a signed integer. To create +# encrypted-positive-P.pdf, I temporarily modified QPDFWriter.cc to +# introduce this error. + +$td->runtest("decrypt positive P", + {$td->COMMAND => + "qpdf --decrypt --static-id encrypted-positive-P.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "decrypted-positive-P.pdf"}); +$td->runtest("copy encryption positive P", + {$td->COMMAND => + "qpdf --static-id --static-aes-iv" . + " encrypted-positive-P.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "copied-positive-P.pdf"}); + show_ntests(); # ---------- $td->notify("--- Library version ---"); diff --git a/qpdf/qtest/qpdf/copied-positive-P.pdf b/qpdf/qtest/qpdf/copied-positive-P.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c13d9040bde303239d5b7424d0a8753aeb5a75cf GIT binary patch literal 1480 zcmZuxJ!oWA6xMbSE73;r3WgP7efR&}3qv5uWOQ-Y#LR9)!ix9j-0Uoq_rjZ(kXWdt z$d)#4VWExKiD0pf2v!yr7Tbw}VBr=b7HXk*?wia^*!9gYbKm*9=ljmj%JyJ*k-bp% zR=)c4<9~aMP*VGwy?&n*n&t zx@Ps#wZe4UDUq}5u0R~S_4)*(Q2c1dNohI3Slg~a!+BaeFs+Z8fO%$+!x}Z+ff3aO z>Too57(q+)LNWuB^Ernk-#kxeIG(i7V8DrMVF00y2>vou`|S;4Xm*;m4Y-5eqfb6> zhFAUJ+qZ6i`sFv@|McU%cmDYK+xMQh{m-r69{TK)#~=GEzV7b6_}q8DJo~wN`l*L6 z-1+d%BRB8;{d-fLe(*lM+kWxtg*W&|Z~pLZ-~W2)nXi%6v&d)sW*GCVa_KsV#Y-?b zhISkrR|ySn;Lc6?cGX53wMlBj0uf%*gS5MnD&9S&z(5zG@Fi9t_b4 zPFiw(Ug+{%A{)bwfoqJ*K|5f= z&=h>)8Z4s$l2Xj)F(DTv{z7wrP3V%>5Ny!i1_9`vd<=M&Nm%AsMEf0V#A4JQAj2~P z@vm9FTtn9j6YV%$0!&|5>)kem<^Lpsee(ZZDqj2EWF zTceT;!|dXiEgR^hfz+eK^yuOt8WT_?k%f$5N9TuB3mFqQCkkNQ9G0~kHIB}LMEU92F z6h@fHl*k`qu~2Wo)dMmrRiZdHR-Y^5d^G&|o=q(0WovNz>yJ+#*H7+0xU>G%TAPE1 zXTK+{vp?m%^IJE*-@Ln~sa<4V%umB?x@u`^3g!XkC#bSSkU9l*&x!?@ud`y06x|t~ zi(`Tn)4<2eBUKBenbCbiu&%Y6x8|=~O=*ZxOhLF(Fl&Tdl{hyn@C|GGfX6xGFo&(p zCb;^6Vr#P*33SyKgx740(W+dDbNTgL&i!4SDn*u)5L(lblzO@9Z5(H(2(-F`2&Nrh zVCwl6pSxCyfg2>LJ7*|tC-Hn|&RDm@Y}e+Y;}M%6^h4&b!1Wx@Wh`KhAM);OBdI7= OJq8}oAJy)^h>ib2d+> >> /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents 4 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 5 0 R >> /ProcSet 6 0 R >> /Type /Page >> +endobj +4 0 obj +<< /Length 80 /Filter /FlateDecode >> +stream +P옃Пu@BL$:je^~[j};C5 \̉.(doF G*C5{kxQendstream +endobj +5 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> +endobj +6 0 obj +[ /PDF /Text ] +endobj +7 0 obj +<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <1b7cc50a8bd7d4220be2b990df3a3f82bf54a9596ea0d3f0fe308a62a496d7f2853cb41fd7e8f2adfee08f045f4579b0> /OE <968299e6df135ce14da318e851103f18c263082e6ae1380fcbf26e910cef551b> /P 4294967292 /Perms /R 6 /StmF /StdCF /StrF /StdCF /U <16d19f98d4d5144f6e68ec10294e3789d8eab6a8eb6de68231d749c198904a004b8a16d4ceb3553204d16feae9d1b819> /UE <80044f0bc684a6675f129a0cb8a6ed6efa33d890a6195963a75e8b3876ded541> /V 5 >> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000130 00000 n +0000000189 00000 n +0000000332 00000 n +0000000482 00000 n +0000000589 00000 n +0000000619 00000 n +trailer << /Root 1 0 R /Size 8 /ID [<7aed6705f40de848cd4f3ea12c672f33><7aed6705f40de848cd4f3ea12c672f33>] /Encrypt 7 0 R >> +startxref +1174 +%%EOF