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()) {