Avoid sharing resources dict entries in CPDF_PageContentGenerator

Just as CPDF_PageContentGenerator avoids sharing a resources dictionary
between pages, the entries inside the resources dictionary should not be
shared either. Check the keys in the references dictionary that
CPDF_PageContentGenerator cares about and clone the values if those
objects are shared references.

Bug: 398887937
Change-Id: I349ed19052f82129fafca416e755e18f87b311c1
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130470
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 95d402b..d7cd1ca 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -49,6 +49,12 @@
 
 namespace {
 
+// TODO(thestig): Remove/restore other unused resource types:
+// - ColorSpace
+// - Pattern
+// - Shading
+constexpr const char* kResourceKeys[] = {"ExtGState", "Font", "XObject"};
+
 // Key: The resource type.
 // Value: The resource names of a given type.
 using ResourcesMap = std::map<ByteString, std::set<ByteString>>;
@@ -164,12 +170,6 @@
     RetainPtr<CPDF_Dictionary> resources_dict,
     const ResourcesMap& resources_in_use,
     CPDF_PageObjectHolder::AllRemovedResourcesMap& all_removed_resources_map) {
-  // TODO(thestig): Remove/restore other unused resource types:
-  // - ColorSpace
-  // - Pattern
-  // - Shading
-  static constexpr const char* kResourceKeys[] = {"ExtGState", "Font",
-                                                  "XObject"};
   for (const char* resource_key : kResourceKeys) {
     auto resources_in_use_it = resources_in_use.find(resource_key);
     const std::set<ByteString>* resource_in_use_of_current_type =
@@ -270,6 +270,51 @@
   return false;
 }
 
+void CloneResourcesDictEntries(CPDF_Document* doc,
+                               RetainPtr<CPDF_Dictionary> resources_dict) {
+  struct KeyAndObject {
+    ByteString key;
+    RetainPtr<const CPDF_Object> object;
+  };
+  std::vector<KeyAndObject> entries_to_maybe_clone;
+  {
+    CPDF_DictionaryLocker locker(resources_dict);
+    for (const auto& it : locker) {
+      const ByteString& key = it.first;
+      const RetainPtr<CPDF_Object>& object = it.second;
+      // Clone resource dictionaries that may get modified. While out for
+      // self-referencing loops.
+      if (object != resources_dict &&
+          object->GetType() == CPDF_Object::kReference &&
+          pdfium::Contains(kResourceKeys, key)) {
+        RetainPtr<const CPDF_Object> direct_object = object->GetDirect();
+        if (direct_object) {
+          entries_to_maybe_clone.emplace_back(key, std::move(direct_object));
+        }
+      }
+    }
+  }
+
+  if (entries_to_maybe_clone.empty()) {
+    return;
+  }
+
+  std::set<uint32_t> shared_objects = GetObjectsWithMultipleReferences(doc);
+  if (shared_objects.empty()) {
+    return;
+  }
+
+  // Must modify `resources_dict` after `locker` goes out of scope.
+  for (const auto& entry : entries_to_maybe_clone) {
+    if (pdfium::Contains(shared_objects, entry.object->GetObjNum())) {
+      const uint32_t clone_object_number =
+          doc->AddIndirectObject(entry.object->Clone());
+      resources_dict->SetNewFor<CPDF_Reference>(entry.key, doc,
+                                                clone_object_number);
+    }
+  }
+}
+
 }  // namespace
 
 CPDF_PageContentGenerator::CPDF_PageContentGenerator(
@@ -446,6 +491,10 @@
         pdfium::page_object::kResources, m_pDocument, clone_object_number);
   }
 
+  // Even though `resources` itself is not shared, its dictionary entries may be
+  // shared. Checked for that and clone those as well.
+  CloneResourcesDictEntries(m_pDocument, resources);
+
   ResourcesMap seen_resources;
   for (auto& page_object : m_pageObjects) {
     if (!page_object->IsActive()) {