Keep track of objects in name tree traversals.

All the objects in a name trees should be distinct. Check to make sure
that is the case, to avoid type confusion or self references.

Change-Id: I8253774c860013df1cbe3cec476077fefffb7e6e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/97290
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/cpdf_nametree.cpp b/core/fpdfdoc/cpdf_nametree.cpp
index 1146465..36baa01 100644
--- a/core/fpdfdoc/cpdf_nametree.cpp
+++ b/core/fpdfdoc/cpdf_nametree.cpp
@@ -166,6 +166,29 @@
   return false;
 }
 
+bool IsTraversedObject(const CPDF_Object* obj,
+                       std::set<uint32_t>* seen_obj_nums) {
+  uint32_t obj_num = obj->GetObjNum();
+  if (!obj_num)
+    return false;
+
+  bool inserted = seen_obj_nums->insert(obj_num).second;
+  return !inserted;
+}
+
+bool IsArrayWithTraversedObject(const CPDF_Array* array,
+                                std::set<uint32_t>* seen_obj_nums) {
+  if (IsTraversedObject(array, seen_obj_nums))
+    return true;
+
+  CPDF_ArrayLocker locker(array);
+  for (const auto& item : locker) {
+    if (IsTraversedObject(item.Get(), seen_obj_nums))
+      return true;
+  }
+  return false;
+}
+
 // Search for |csName| in the tree with root |pNode|. If successful, return the
 // value that |csName| points to; |nIndex| will be the index of |csName|,
 // |ppFind| will be the leaf array that |csName| is found in, and |pFindIndex|
@@ -178,12 +201,18 @@
     int nLevel,
     size_t* nIndex,
     RetainPtr<CPDF_Array>* ppFind,
-    int* pFindIndex) {
+    int* pFindIndex,
+    std::set<uint32_t>* seen_obj_nums) {
   if (nLevel > kNameTreeMaxRecursion)
     return nullptr;
 
   RetainPtr<CPDF_Array> pLimits = pNode->GetMutableArrayFor("Limits");
   RetainPtr<CPDF_Array> pNames = pNode->GetMutableArrayFor("Names");
+  if (pNames && IsArrayWithTraversedObject(pNames.Get(), seen_obj_nums))
+    pNames.Reset();
+  if (pLimits && IsArrayWithTraversedObject(pLimits.Get(), seen_obj_nums))
+    pLimits.Reset();
+
   if (pLimits) {
     WideString csLeft;
     WideString csRight;
@@ -227,16 +256,16 @@
 
   // Search through the node's children.
   RetainPtr<CPDF_Array> pKids = pNode->GetMutableArrayFor("Kids");
-  if (!pKids)
+  if (!pKids || IsTraversedObject(pKids.Get(), seen_obj_nums))
     return nullptr;
 
   for (size_t i = 0; i < pKids->size(); i++) {
     RetainPtr<CPDF_Dictionary> pKid = pKids->GetMutableDictAt(i);
-    if (!pKid)
+    if (!pKid || IsTraversedObject(pKid.Get(), seen_obj_nums))
       continue;
 
     RetainPtr<const CPDF_Object> pFound = SearchNameNodeByNameInternal(
-        pKid, csName, nLevel + 1, nIndex, ppFind, pFindIndex);
+        pKid, csName, nLevel + 1, nIndex, ppFind, pFindIndex, seen_obj_nums);
     if (pFound)
       return pFound;
   }
@@ -251,8 +280,9 @@
     RetainPtr<CPDF_Array>* ppFind,
     int* pFindIndex) {
   size_t nIndex = 0;
+  std::set<uint32_t> seen_obj_nums;
   return SearchNameNodeByNameInternal(pNode, csName, 0, &nIndex, ppFind,
-                                      pFindIndex);
+                                      pFindIndex, &seen_obj_nums);
 }
 
 struct IndexSearchResult {