Commit 9b28933647f0a3ed535dd488c4526b75d1c10fc6

Authored by Jay Berkenbilt
1 parent 73752683

Check object ownership when adding

When adding a QPDFObjectHandle to an array or dictionary, if possible,
check if the new object belongs to the same QPDF. This makes it much
easier to find incorrect code than waiting for the situation to be
detected when the file is written.
ChangeLog
  1 +2021-11-04 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add an extra check to the library to detect when foreign objects
  4 + are inserted directly (instead of using
  5 + <function>QPDF::copyForeignObject</function>) at the time of
  6 + insertion rather than when the file is written. Catching the error
  7 + sooner makes it much easier to locate the incorrect code.
  8 +
1 2021-11-03 Jay Berkenbilt <ejb@ql.org> 9 2021-11-03 Jay Berkenbilt <ejb@ql.org>
2 10
3 * Bug fix: make overlay/underlay work on a page with no resource 11 * Bug fix: make overlay/underlay work on a page with no resource
include/qpdf/QPDFObjectHandle.hh
@@ -1336,6 +1336,7 @@ class QPDFObjectHandle @@ -1336,6 +1336,7 @@ class QPDFObjectHandle
1336 std::vector<QPDFObjectHandle> arrayOrStreamToStreamArray( 1336 std::vector<QPDFObjectHandle> arrayOrStreamToStreamArray(
1337 std::string const& description, std::string& all_description); 1337 std::string const& description, std::string& all_description);
1338 static void warn(QPDF*, QPDFExc const&); 1338 static void warn(QPDF*, QPDFExc const&);
  1339 + void checkOwnership(QPDFObjectHandle const&) const;
1339 1340
1340 bool initialized; 1341 bool initialized;
1341 1342
libqpdf/QPDFObjectHandle.cc
@@ -864,6 +864,7 @@ QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const&amp; item) @@ -864,6 +864,7 @@ QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const&amp; item)
864 { 864 {
865 if (isArray()) 865 if (isArray())
866 { 866 {
  867 + checkOwnership(item);
867 dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item); 868 dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item);
868 } 869 }
869 else 870 else
@@ -878,6 +879,10 @@ QPDFObjectHandle::setArrayFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; items) @@ -878,6 +879,10 @@ QPDFObjectHandle::setArrayFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; items)
878 { 879 {
879 if (isArray()) 880 if (isArray())
880 { 881 {
  882 + for (auto const& item: items)
  883 + {
  884 + checkOwnership(item);
  885 + }
881 dynamic_cast<QPDF_Array*>(obj.getPointer())->setFromVector(items); 886 dynamic_cast<QPDF_Array*>(obj.getPointer())->setFromVector(items);
882 } 887 }
883 else 888 else
@@ -906,6 +911,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item) @@ -906,6 +911,7 @@ QPDFObjectHandle::appendItem(QPDFObjectHandle const&amp; item)
906 { 911 {
907 if (isArray()) 912 if (isArray())
908 { 913 {
  914 + checkOwnership(item);
909 dynamic_cast<QPDF_Array*>(obj.getPointer())->appendItem(item); 915 dynamic_cast<QPDF_Array*>(obj.getPointer())->appendItem(item);
910 } 916 }
911 else 917 else
@@ -1283,6 +1289,7 @@ QPDFObjectHandle::replaceKey(std::string const&amp; key, @@ -1283,6 +1289,7 @@ QPDFObjectHandle::replaceKey(std::string const&amp; key,
1283 { 1289 {
1284 if (isDictionary()) 1290 if (isDictionary())
1285 { 1291 {
  1292 + checkOwnership(value);
1286 dynamic_cast<QPDF_Dictionary*>( 1293 dynamic_cast<QPDF_Dictionary*>(
1287 obj.getPointer())->replaceKey(key, value); 1294 obj.getPointer())->replaceKey(key, value);
1288 } 1295 }
@@ -1313,6 +1320,7 @@ QPDFObjectHandle::replaceOrRemoveKey(std::string const&amp; key, @@ -1313,6 +1320,7 @@ QPDFObjectHandle::replaceOrRemoveKey(std::string const&amp; key,
1313 { 1320 {
1314 if (isDictionary()) 1321 if (isDictionary())
1315 { 1322 {
  1323 + checkOwnership(value);
1316 dynamic_cast<QPDF_Dictionary*>( 1324 dynamic_cast<QPDF_Dictionary*>(
1317 obj.getPointer())->replaceOrRemoveKey(key, value); 1325 obj.getPointer())->replaceOrRemoveKey(key, value);
1318 } 1326 }
@@ -3270,6 +3278,20 @@ QPDFObjectHandle::isImage(bool exclude_imagemask) @@ -3270,6 +3278,20 @@ QPDFObjectHandle::isImage(bool exclude_imagemask)
3270 } 3278 }
3271 3279
3272 void 3280 void
  3281 +QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const
  3282 +{
  3283 + if ((this->qpdf != nullptr) &&
  3284 + (item.qpdf != nullptr) &&
  3285 + (this->qpdf != item.qpdf))
  3286 + {
  3287 + QTC::TC("qpdf", "QPDFObjectHandle check ownership");
  3288 + throw std::logic_error(
  3289 + "Attempting to add an object from a different QPDF."
  3290 + " Use QPDF::copyForeignObject to add objects from another file.");
  3291 + }
  3292 +}
  3293 +
  3294 +void
3273 QPDFObjectHandle::assertPageObject() 3295 QPDFObjectHandle::assertPageObject()
3274 { 3296 {
3275 if (! isPageObject()) 3297 if (! isPageObject())
manual/qpdf-manual.xml
@@ -5104,6 +5104,16 @@ print &quot;\n&quot;; @@ -5104,6 +5104,16 @@ print &quot;\n&quot;;
5104 receive warnings on certain recoverable conditions. 5104 receive warnings on certain recoverable conditions.
5105 </para> 5105 </para>
5106 </listitem> 5106 </listitem>
  5107 + <listitem>
  5108 + <para>
  5109 + Add an extra check to the library to detect when foreign
  5110 + objects are inserted directly (instead of using
  5111 + <function>QPDF::copyForeignObject</function>) at the time of
  5112 + insertion rather than when the file is written. Catching the
  5113 + error sooner makes it much easier to locate the incorrect
  5114 + code.
  5115 + </para>
  5116 + </listitem>
5107 </itemizedlist> 5117 </itemizedlist>
5108 </listitem> 5118 </listitem>
5109 <listitem> 5119 <listitem>
qpdf/qpdf.cc
@@ -5705,7 +5705,9 @@ static QPDFObjectHandle added_page(QPDF&amp; pdf, QPDFPageObjectHelper page) @@ -5705,7 +5705,9 @@ static QPDFObjectHandle added_page(QPDF&amp; pdf, QPDFPageObjectHelper page)
5705 return added_page(pdf, page.getObjectHandle()); 5705 return added_page(pdf, page.getObjectHandle());
5706 } 5706 }
5707 5707
5708 -static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings) 5708 +static void handle_page_specs(
  5709 + QPDF& pdf, Options& o, bool& warnings,
  5710 + std::vector<PointerHolder<QPDF>>& page_heap)
5709 { 5711 {
5710 // Parse all page specifications and translate them into lists of 5712 // Parse all page specifications and translate them into lists of
5711 // actual pages. 5713 // actual pages.
@@ -5757,7 +5759,6 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings) @@ -5757,7 +5759,6 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
5757 } 5759 }
5758 5760
5759 // Create a QPDF object for each file that we may take pages from. 5761 // Create a QPDF object for each file that we may take pages from.
5760 - std::vector<PointerHolder<QPDF> > page_heap;  
5761 std::map<std::string, QPDF*> page_spec_qpdfs; 5762 std::map<std::string, QPDF*> page_spec_qpdfs;
5762 std::map<std::string, ClosedFileInputSource*> page_spec_cfis; 5763 std::map<std::string, ClosedFileInputSource*> page_spec_cfis;
5763 page_spec_qpdfs[o.infilename] = &pdf; 5764 page_spec_qpdfs[o.infilename] = &pdf;
@@ -6009,7 +6010,7 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings) @@ -6009,7 +6010,7 @@ static void handle_page_specs(QPDF&amp; pdf, Options&amp; o, bool&amp; warnings)
6009 pdf.warn( 6010 pdf.warn(
6010 QPDFExc(qpdf_e_damaged_pdf, pdf.getFilename(), 6011 QPDFExc(qpdf_e_damaged_pdf, pdf.getFilename(),
6011 "", 0, "Exception caught while fixing copied" 6012 "", 0, "Exception caught while fixing copied"
6012 - " annotations. This may be a qpdf bug." + 6013 + " annotations. This may be a qpdf bug. " +
6013 std::string("Exception: ") + e.what())); 6014 std::string("Exception: ") + e.what()));
6014 } 6015 }
6015 } 6016 }
@@ -6649,9 +6650,10 @@ int realmain(int argc, char* argv[]) @@ -6649,9 +6650,10 @@ int realmain(int argc, char* argv[])
6649 } 6650 }
6650 } 6651 }
6651 bool other_warnings = false; 6652 bool other_warnings = false;
  6653 + std::vector<PointerHolder<QPDF>> page_heap;
6652 if (! o.page_specs.empty()) 6654 if (! o.page_specs.empty())
6653 { 6655 {
6654 - handle_page_specs(pdf, o, other_warnings); 6656 + handle_page_specs(pdf, o, other_warnings, page_heap);
6655 } 6657 }
6656 if (! o.rotations.empty()) 6658 if (! o.rotations.empty())
6657 { 6659 {
qpdf/qpdf.testcov
@@ -597,3 +597,4 @@ QPDFWriter exclude from object stream 0 @@ -597,3 +597,4 @@ QPDFWriter exclude from object stream 0
597 check unclosed --pages 1 597 check unclosed --pages 1
598 QPDF_pages findPage not found 0 598 QPDF_pages findPage not found 0
599 qpdf overlay page with no resources 0 599 qpdf overlay page with no resources 0
  600 +QPDFObjectHandle check ownership 0
qpdf/qtest/qpdf/foreign-in-write.out
1 logic error: QPDFObjectHandle from different QPDF found while writing. Use QPDF::copyForeignObject to add objects from another file. 1 logic error: QPDFObjectHandle from different QPDF found while writing. Use QPDF::copyForeignObject to add objects from another file.
  2 +logic error: Attempting to add an object from a different QPDF. Use QPDF::copyForeignObject to add objects from another file.
2 test 29 done 3 test 29 done
qpdf/test_driver.cc
@@ -1274,6 +1274,18 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1274,6 +1274,18 @@ void runtest(int n, char const* filename1, char const* arg2)
1274 { 1274 {
1275 std::cout << "logic error: " << e.what() << std::endl; 1275 std::cout << "logic error: " << e.what() << std::endl;
1276 } 1276 }
  1277 +
  1278 + // Detect adding a foreign object
  1279 + auto root1 = pdf.getRoot();
  1280 + auto root2 = other.getRoot();
  1281 + try
  1282 + {
  1283 + root1.replaceKey("/Oops", root2);
  1284 + }
  1285 + catch (std::logic_error const& e)
  1286 + {
  1287 + std::cout << "logic error: " << e.what() << std::endl;
  1288 + }
1277 } 1289 }
1278 else if (n == 30) 1290 else if (n == 30)
1279 { 1291 {