Restrict the object types that FPDF_CopyViewerPreferences() copies.
Prevent FPDF_CopyViewerPreferences() from copying bad data. In the
malformed PDF in the bug, the /ViewerPreferences pointed to the catalog,
so FPDF_CopyViewerPreferences() tried to copy the entire (large) PDF
without using references.
Bug: chromium:1404202
Change-Id: Ife065c8bbeafee808d5402dba9dfb07bd4bcd269
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/103131
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_ppo.cpp b/fpdfsdk/fpdf_ppo.cpp
index f0f62b8..ad8b900 100644
--- a/fpdfsdk/fpdf_ppo.cpp
+++ b/fpdfsdk/fpdf_ppo.cpp
@@ -682,6 +682,33 @@
dest(), pStream->GetObjNum());
}
+// Make sure arrays only contain objects of basic types.
+bool IsValidViewerPreferencesArray(const CPDF_Array* array) {
+ CPDF_ArrayLocker locker(array);
+ for (const auto& obj : locker) {
+ if (obj->IsArray() || obj->IsDictionary() || obj->IsReference() ||
+ obj->IsStream()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsValidViewerPreferencesObject(const CPDF_Object* obj) {
+ // Per spec, there are no valid entries of these types.
+ if (obj->IsDictionary() || obj->IsNull() || obj->IsReference() ||
+ obj->IsStream()) {
+ return false;
+ }
+
+ const CPDF_Array* array = obj->AsArray();
+ if (!array) {
+ return true;
+ }
+
+ return IsValidViewerPreferencesArray(array);
+}
+
} // namespace
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
@@ -836,6 +863,14 @@
if (!pDstDict)
return false;
- pDstDict->SetFor("ViewerPreferences", pPrefDict->CloneDirectObject());
+ auto cloned_dict = pdfium::MakeRetain<CPDF_Dictionary>();
+ CPDF_DictionaryLocker locker(pPrefDict);
+ for (const auto& it : locker) {
+ if (IsValidViewerPreferencesObject(it.second)) {
+ cloned_dict->SetFor(it.first, it.second->Clone());
+ }
+ }
+
+ pDstDict->SetFor("ViewerPreferences", std::move(cloned_dict));
return true;
}
diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp
index 5b5beb0..6f0a2b1 100644
--- a/fpdfsdk/fpdf_ppo_embeddertest.cpp
+++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp
@@ -6,7 +6,13 @@
#include "core/fpdfapi/page/cpdf_form.h"
#include "core/fpdfapi/page/cpdf_formobject.h"
+#include "core/fpdfapi/parser/cpdf_array.h"
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_document.h"
+#include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
+#include "core/fpdfapi/parser/cpdf_string.h"
#include "core/fxge/cfx_defaultrenderdevice.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"
@@ -402,6 +408,46 @@
FPDF_CloseDocument(output_doc);
}
+TEST_F(FPDFPPOEmbedderTest, CopyViewerPrefTypes) {
+ ASSERT_TRUE(OpenDocument("viewer_pref_types.pdf"));
+
+ ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+ ASSERT_TRUE(output_doc);
+ EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc.get(), document()));
+
+ // Peek under the hook to check the result.
+ const CPDF_Document* output_doc_impl =
+ CPDFDocumentFromFPDFDocument(output_doc.get());
+ RetainPtr<const CPDF_Dictionary> prefs =
+ output_doc_impl->GetRoot()->GetDictFor("ViewerPreferences");
+ ASSERT_TRUE(prefs);
+ EXPECT_EQ(6u, prefs->size());
+
+ RetainPtr<const CPDF_Object> bool_obj = prefs->GetObjectFor("Bool");
+ ASSERT_TRUE(bool_obj);
+ EXPECT_TRUE(bool_obj->IsBoolean());
+
+ RetainPtr<const CPDF_Number> num_obj = prefs->GetNumberFor("Num");
+ ASSERT_TRUE(num_obj);
+ EXPECT_TRUE(num_obj->IsInteger());
+ EXPECT_EQ(1, num_obj->GetInteger());
+
+ RetainPtr<const CPDF_String> str_obj = prefs->GetStringFor("Str");
+ ASSERT_TRUE(str_obj);
+ EXPECT_EQ("str", str_obj->GetString());
+
+ EXPECT_EQ("name", prefs->GetNameFor("Name"));
+
+ RetainPtr<const CPDF_Array> empty_array_obj =
+ prefs->GetArrayFor("EmptyArray");
+ ASSERT_TRUE(empty_array_obj);
+ EXPECT_TRUE(empty_array_obj->IsEmpty());
+
+ RetainPtr<const CPDF_Array> good_array_obj = prefs->GetArrayFor("GoodArray");
+ ASSERT_TRUE(good_array_obj);
+ EXPECT_EQ(4u, good_array_obj->size());
+}
+
TEST_F(FPDFPPOEmbedderTest, BadIndices) {
ASSERT_TRUE(OpenDocument("hello_world.pdf"));
diff --git a/testing/resources/viewer_pref_types.in b/testing/resources/viewer_pref_types.in
new file mode 100644
index 0000000..11b8034
--- /dev/null
+++ b/testing/resources/viewer_pref_types.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+ /Type /Catalog
+ /Pages 2 0 R
+ /ViewerPreferences <<
+ /Bool true
+ /Num 1
+ /Str (str)
+ /Name /name
+ /Null null
+ /Ref 3 0 R
+ /EmptyArray []
+ /GoodArray [true 1 (str) /name]
+ /BadArray1 [true []]
+ /BadArray2 [1 <<>>]
+ /BadArray3 [/name 3 0 R]
+ /Dict <<
+ >>
+ >>
+>>
+endobj
+{{object 2 0}} <<
+ /Type /Pages
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+ /Type /Page
+ /Parent 2 0 R
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/viewer_pref_types.pdf b/testing/resources/viewer_pref_types.pdf
new file mode 100644
index 0000000..f74ca6f
--- /dev/null
+++ b/testing/resources/viewer_pref_types.pdf
@@ -0,0 +1,46 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+ /ViewerPreferences <<
+ /Bool true
+ /Num 1
+ /Str (str)
+ /Name /name
+ /Null null
+ /Ref 3 0 R
+ /EmptyArray []
+ /GoodArray [true 1 (str) /name]
+ /BadArray1 [true []]
+ /BadArray2 [1 <<>>]
+ /BadArray3 [/name 3 0 R]
+ /Dict <<
+ >>
+ >>
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /Count 1
+ /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+>>
+endobj
+xref
+0 4
+0000000000 65535 f
+0000000015 00000 n
+0000000337 00000 n
+0000000400 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 4
+>>
+startxref
+451
+%%EOF