M78: Verify font resource dictionaries.

Font dictionaries are required to have a /Type /Font entry, and font
resource dictionaries should be made up of valid font dictionaries.
Verify this in places that use font resource dictionaries, to prevent
accidentally treating other objects as font resource dictionaries.

Add ValidateDictType() and ValidateResourceDict() helper functions, with
unit tests, to do this validation.

Bug: chromium:1013868
Change-Id: I6ae2b60d35ea396f47cd5a9c80e8f138ef9d264b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/61450
Commit-Queue: Lei Zhang <thestig@chromium.org>
Commit-Queue: dsinclair <dsinclair@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
Reviewed-by: Chris Palmer <palmer@chromium.org>
(cherry picked from commit 94e600d98301661502135a89db1fb0cc7caae5e9)
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/61690
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/parser/fpdf_parser_utility.cpp b/core/fpdfapi/parser/fpdf_parser_utility.cpp
index 053ee05..af58e8d 100644
--- a/core/fpdfapi/parser/fpdf_parser_utility.cpp
+++ b/core/fpdfapi/parser/fpdf_parser_utility.cpp
@@ -9,6 +9,7 @@
 #include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_boolean.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_name.h"
 #include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
 #include "core/fpdfapi/parser/cpdf_stream.h"
@@ -162,6 +163,31 @@
   return ret;
 }
 
+bool ValidateDictType(const CPDF_Dictionary* dict, const ByteString& type) {
+  ASSERT(dict);
+  ASSERT(!type.IsEmpty());
+  const CPDF_Name* name_obj = ToName(dict->GetObjectFor("Type"));
+  return name_obj && name_obj->GetString() == type;
+}
+
+bool ValidateDictAllResourcesOfType(const CPDF_Dictionary* dict,
+                                    const ByteString& type) {
+  if (!dict)
+    return false;
+
+  CPDF_DictionaryLocker locker(dict);
+  for (const auto& it : locker) {
+    const CPDF_Dictionary* entry = ToDictionary(it.second.Get()->GetDirect());
+    if (!entry || !ValidateDictType(entry, type))
+      return false;
+  }
+  return true;
+}
+
+bool ValidateFontResourceDict(const CPDF_Dictionary* dict) {
+  return ValidateDictAllResourcesOfType(dict, "Font");
+}
+
 std::ostream& operator<<(std::ostream& buf, const CPDF_Object* pObj) {
   if (!pObj) {
     buf << " null";
diff --git a/core/fpdfapi/parser/fpdf_parser_utility.h b/core/fpdfapi/parser/fpdf_parser_utility.h
index 29dd246..75f4076 100644
--- a/core/fpdfapi/parser/fpdf_parser_utility.h
+++ b/core/fpdfapi/parser/fpdf_parser_utility.h
@@ -55,6 +55,17 @@
 std::vector<float> ReadArrayElementsToVector(const CPDF_Array* pArray,
                                              size_t nCount);
 
+// Returns true if |dict| has a /Type name entry that matches |type|.
+bool ValidateDictType(const CPDF_Dictionary* dict, const ByteString& type);
+
+// Returns true if |dict| is non-null and all entries in |dict| are dictionaries
+// of |type|.
+bool ValidateDictAllResourcesOfType(const CPDF_Dictionary* dict,
+                                    const ByteString& type);
+
+// Shorthand for ValidateDictAllResourcesOfType(dict, "Font").
+bool ValidateFontResourceDict(const CPDF_Dictionary* dict);
+
 std::ostream& operator<<(std::ostream& buf, const CPDF_Object* pObj);
 
 #endif  // CORE_FPDFAPI_PARSER_FPDF_PARSER_UTILITY_H_
diff --git a/core/fpdfapi/parser/fpdf_parser_utility_unittest.cpp b/core/fpdfapi/parser/fpdf_parser_utility_unittest.cpp
index e3b4e9d..098c3bd 100644
--- a/core/fpdfapi/parser/fpdf_parser_utility_unittest.cpp
+++ b/core/fpdfapi/parser/fpdf_parser_utility_unittest.cpp
@@ -4,6 +4,14 @@
 
 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
 
+#include "core/fpdfapi/page/cpdf_docpagedata.h"
+#include "core/fpdfapi/page/cpdf_pagemodule.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_reference.h"
+#include "core/fpdfapi/parser/cpdf_string.h"
+#include "core/fpdfapi/render/cpdf_docrenderdata.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 TEST(fpdf_parser_utility, PDF_NameDecode) {
@@ -24,3 +32,77 @@
   EXPECT_EQ("#C2", PDF_NameEncode("\xc2"));
   EXPECT_EQ("f#C2#A5", PDF_NameEncode("f\xc2\xa5"));
 }
+
+TEST(fpdf_parser_utility, ValidateDictType) {
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+
+  // No type.
+  EXPECT_FALSE(ValidateDictType(dict.Get(), "foo"));
+  EXPECT_FALSE(ValidateDictType(dict.Get(), "bar"));
+
+  // Add the wrong object type.
+  dict->SetNewFor<CPDF_String>("Type", L"foo");
+  EXPECT_FALSE(ValidateDictType(dict.Get(), "foo"));
+  EXPECT_FALSE(ValidateDictType(dict.Get(), "bar"));
+
+  // Add the correct object type.
+  dict->SetNewFor<CPDF_Name>("Type", "foo");
+  EXPECT_TRUE(ValidateDictType(dict.Get(), "foo"));
+  EXPECT_FALSE(ValidateDictType(dict.Get(), "bar"));
+}
+
+TEST(fpdf_parser_utility, ValidateDictAllResourcesOfType) {
+  CPDF_PageModule::Create();
+
+  {
+    // Direct dictionary.
+    auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+
+    // Empty dict is ok.
+    EXPECT_TRUE(ValidateDictAllResourcesOfType(dict.Get(), "foo"));
+    EXPECT_TRUE(ValidateDictAllResourcesOfType(dict.Get(), "bar"));
+
+    // nullptr is not.
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(nullptr, "foo"));
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(nullptr, "bar"));
+
+    // Add two correct dictionary entries and one string entry.
+    CPDF_Dictionary* new_dict = dict->SetNewFor<CPDF_Dictionary>("f1");
+    new_dict->SetNewFor<CPDF_Name>("Type", "foo");
+    new_dict = dict->SetNewFor<CPDF_Dictionary>("f2");
+    new_dict->SetNewFor<CPDF_Name>("Type", "foo");
+    dict->SetNewFor<CPDF_String>("f3", L"foo");
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "foo"));
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "bar"));
+
+    // Change the string entry to a dictionary, but with the wrong /Type.
+    new_dict = dict->SetNewFor<CPDF_Dictionary>("f3");
+    new_dict->SetNewFor<CPDF_Name>("Type", "bar");
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "foo"));
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "bar"));
+
+    // Change the /Type to match.
+    new_dict->SetNewFor<CPDF_Name>("Type", "foo");
+    EXPECT_TRUE(ValidateDictAllResourcesOfType(dict.Get(), "foo"));
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "bar"));
+  }
+
+  {
+    // Indirect dictionary.
+    auto doc = pdfium::MakeUnique<CPDF_Document>(
+        pdfium::MakeUnique<CPDF_DocRenderData>(),
+        pdfium::MakeUnique<CPDF_DocPageData>());
+
+    auto dict = doc->New<CPDF_Dictionary>();
+
+    // Add a correct dictionary entry.
+    CPDF_Dictionary* new_dict = doc->NewIndirect<CPDF_Dictionary>();
+    new_dict->SetNewFor<CPDF_Name>("Type", "foo");
+    dict->SetNewFor<CPDF_Reference>("f1", doc.get(), new_dict->GetObjNum());
+
+    EXPECT_TRUE(ValidateDictAllResourcesOfType(dict.Get(), "foo"));
+    EXPECT_FALSE(ValidateDictAllResourcesOfType(dict.Get(), "bar"));
+  }
+
+  CPDF_PageModule::Destroy();
+}
diff --git a/core/fpdfdoc/cpdf_formcontrol.cpp b/core/fpdfdoc/cpdf_formcontrol.cpp
index 7e8c190..3b7b9b3 100644
--- a/core/fpdfdoc/cpdf_formcontrol.cpp
+++ b/core/fpdfdoc/cpdf_formcontrol.cpp
@@ -16,6 +16,7 @@
 #include "core/fpdfapi/parser/cpdf_name.h"
 #include "core/fpdfapi/parser/cpdf_stream.h"
 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
+#include "core/fpdfapi/parser/fpdf_parser_utility.h"
 #include "core/fpdfdoc/cpdf_interactiveform.h"
 
 namespace {
@@ -220,7 +221,7 @@
   CPDF_Object* pObj = FPDF_GetFieldAttr(m_pWidgetDict.Get(), "DR");
   if (CPDF_Dictionary* pDict = ToDictionary(pObj)) {
     CPDF_Dictionary* pFonts = pDict->GetDictFor("Font");
-    if (pFonts) {
+    if (ValidateFontResourceDict(pFonts)) {
       CPDF_Dictionary* pElement = pFonts->GetDictFor(*csFontNameTag);
       if (pElement) {
         auto* pData = CPDF_DocPageData::FromDocument(m_pForm->GetDocument());
@@ -241,7 +242,7 @@
     return nullptr;
 
   CPDF_Dictionary* pFonts = pDict->GetDictFor("Font");
-  if (!pFonts)
+  if (!ValidateFontResourceDict(pFonts))
     return nullptr;
 
   CPDF_Dictionary* pElement = pFonts->GetDictFor(*csFontNameTag);
diff --git a/core/fpdfdoc/cpdf_formfield.cpp b/core/fpdfdoc/cpdf_formfield.cpp
index 7b1b6ff..9d71bf3 100644
--- a/core/fpdfdoc/cpdf_formfield.cpp
+++ b/core/fpdfdoc/cpdf_formfield.cpp
@@ -22,6 +22,7 @@
 #include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fpdfapi/parser/cpdf_string.h"
 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
+#include "core/fpdfapi/parser/fpdf_parser_utility.h"
 #include "core/fpdfdoc/cpdf_defaultappearance.h"
 #include "core/fpdfdoc/cpdf_formcontrol.h"
 #include "core/fpdfdoc/cpdf_interactiveform.h"
@@ -901,7 +902,7 @@
     return;
 
   CPDF_Dictionary* pFont = pDR->GetDictFor("Font");
-  if (!pFont)
+  if (!ValidateFontResourceDict(pFont))
     return;
 
   CPDF_DefaultAppearance appearance(DA);
diff --git a/core/fpdfdoc/cpdf_interactiveform.cpp b/core/fpdfdoc/cpdf_interactiveform.cpp
index eeaac49..15a5e26 100644
--- a/core/fpdfdoc/cpdf_interactiveform.cpp
+++ b/core/fpdfdoc/cpdf_interactiveform.cpp
@@ -130,7 +130,7 @@
     return nullptr;
 
   CPDF_Dictionary* pFonts = pDR->GetDictFor("Font");
-  if (!pFonts)
+  if (!ValidateFontResourceDict(pFonts))
     return nullptr;
 
   CPDF_Dictionary* pElement = pFonts->GetDictFor(csAlias);
@@ -152,7 +152,7 @@
     return nullptr;
 
   CPDF_Dictionary* pFonts = pDR->GetDictFor("Font");
-  if (!pFonts)
+  if (!ValidateFontResourceDict(pFonts))
     return nullptr;
 
   CPDF_DictionaryLocker locker(pFonts);
@@ -193,7 +193,7 @@
     return false;
 
   CPDF_Dictionary* pFonts = pDR->GetDictFor("Font");
-  if (!pFonts)
+  if (!ValidateFontResourceDict(pFonts))
     return false;
 
   CPDF_DictionaryLocker locker(pFonts);
@@ -227,7 +227,7 @@
     return false;
 
   CPDF_Dictionary* pFonts = pDR->GetDictFor("Font");
-  if (!pFonts)
+  if (!ValidateFontResourceDict(pFonts))
     return false;
 
   if (csFontName.GetLength() > 0)
diff --git a/core/fpdfdoc/cpvt_fontmap.cpp b/core/fpdfdoc/cpvt_fontmap.cpp
index 038a6f4..b21bd0d 100644
--- a/core/fpdfdoc/cpvt_fontmap.cpp
+++ b/core/fpdfdoc/cpvt_fontmap.cpp
@@ -10,6 +10,7 @@
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
+#include "core/fpdfapi/parser/fpdf_parser_utility.h"
 #include "core/fpdfdoc/cpdf_interactiveform.h"
 #include "core/fxcrt/fx_codepage.h"
 #include "third_party/base/logging.h"
@@ -40,7 +41,8 @@
     return nullptr;
 
   CPDF_Dictionary* pFontList = pResDict->GetDictFor("Font");
-  if (pFontList && !pFontList->KeyExist(*sSysFontAlias)) {
+  if (ValidateFontResourceDict(pFontList) &&
+      !pFontList->KeyExist(*sSysFontAlias)) {
     pFontList->SetNewFor<CPDF_Reference>(*sSysFontAlias, pDoc,
                                          pPDFFont->GetFontDict()->GetObjNum());
   }
diff --git a/core/fpdfdoc/cpvt_generateap.cpp b/core/fpdfdoc/cpvt_generateap.cpp
index 0c2143b..b66fc3c 100644
--- a/core/fpdfdoc/cpvt_generateap.cpp
+++ b/core/fpdfdoc/cpvt_generateap.cpp
@@ -945,7 +945,7 @@
     return;
 
   CPDF_Dictionary* pDRFontDict = pDRDict->GetDictFor("Font");
-  if (!pDRFontDict)
+  if (!ValidateFontResourceDict(pDRFontDict))
     return;
 
   CPDF_Dictionary* pFontDict = pDRFontDict->GetDictFor(font_name);
@@ -1069,13 +1069,15 @@
   }
   CPDF_Dictionary* pStreamDict = pNormalStream->GetDict();
   if (pStreamDict) {
-    pStreamDict->SetMatrixFor("Matrix", matrix);
-    pStreamDict->SetRectFor("BBox", rcBBox);
     CPDF_Dictionary* pStreamResList = pStreamDict->GetDictFor("Resources");
     if (pStreamResList) {
       CPDF_Dictionary* pStreamResFontList = pStreamResList->GetDictFor("Font");
-      if (!pStreamResFontList)
+      if (pStreamResFontList) {
+        if (!ValidateFontResourceDict(pStreamResFontList))
+          return;
+      } else {
         pStreamResFontList = pStreamResList->SetNewFor<CPDF_Dictionary>("Font");
+      }
       if (!pStreamResFontList->KeyExist(font_name)) {
         pStreamResFontList->SetNewFor<CPDF_Reference>(font_name, pDoc,
                                                       pFontDict->GetObjNum());
@@ -1083,6 +1085,8 @@
     } else {
       pStreamDict->SetFor("Resources", pFormDict->GetDictFor("DR")->Clone());
     }
+    pStreamDict->SetMatrixFor("Matrix", matrix);
+    pStreamDict->SetRectFor("BBox", rcBBox);
   }
   switch (type) {
     case CPVT_GenerateAP::kTextField: {
@@ -1306,28 +1310,33 @@
     }
   }
 
-  if (pNormalStream) {
-    pNormalStream->SetDataFromStringstreamAndRemoveFilter(&sAppStream);
-    pStreamDict = pNormalStream->GetDict();
-    if (pStreamDict) {
-      pStreamDict->SetMatrixFor("Matrix", matrix);
-      pStreamDict->SetRectFor("BBox", rcBBox);
-      CPDF_Dictionary* pStreamResList = pStreamDict->GetDictFor("Resources");
-      if (pStreamResList) {
-        CPDF_Dictionary* pStreamResFontList =
-            pStreamResList->GetDictFor("Font");
-        if (!pStreamResFontList) {
-          pStreamResFontList =
-              pStreamResList->SetNewFor<CPDF_Dictionary>("Font");
-        }
-        if (!pStreamResFontList->KeyExist(font_name)) {
-          pStreamResFontList->SetNewFor<CPDF_Reference>(font_name, pDoc,
-                                                        pFontDict->GetObjNum());
-        }
-      } else {
-        pStreamDict->SetFor("Resources", pFormDict->GetDictFor("DR")->Clone());
-      }
-    }
+  if (!pNormalStream)
+    return;
+
+  pNormalStream->SetDataFromStringstreamAndRemoveFilter(&sAppStream);
+  pStreamDict = pNormalStream->GetDict();
+  if (!pStreamDict)
+    return;
+
+  pStreamDict->SetMatrixFor("Matrix", matrix);
+  pStreamDict->SetRectFor("BBox", rcBBox);
+  CPDF_Dictionary* pStreamResList = pStreamDict->GetDictFor("Resources");
+  if (!pStreamResList) {
+    pStreamDict->SetFor("Resources", pFormDict->GetDictFor("DR")->Clone());
+    return;
+  }
+
+  CPDF_Dictionary* pStreamResFontList = pStreamResList->GetDictFor("Font");
+  if (pStreamResFontList) {
+    if (!ValidateFontResourceDict(pStreamResFontList))
+      return;
+  } else {
+    pStreamResFontList = pStreamResList->SetNewFor<CPDF_Dictionary>("Font");
+  }
+
+  if (!pStreamResFontList->KeyExist(font_name)) {
+    pStreamResFontList->SetNewFor<CPDF_Reference>(font_name, pDoc,
+                                                  pFontDict->GetObjNum());
   }
 }