Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in PDFium
DescriptionUse after free in PDFium
ComponentPDFium
Bug ClassUAF
Tracker522299155
Fix commit7c53431c7e35 (pdfium) +10/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • core/fpdfdoc/cpdf_nametree.cpp
From 7c53431c7e353de36f70df5fddd0e397ad9d79cf Mon Sep 17 00:00:00 2001
From: Tom Sepez <tsepez@google.com>
Date: Wed, 10 Jun 2026 13:27:30 -0700
Subject: [PATCH] Reduce object churn in CPDF_NameTree::GetNodeLimits.

Swap the existing CPDF_Object elements in-place within the limits array
instead of recreating CPDF_String objects. This avoids unnecessary
object destruction and creation, and prevents raw pointers to these
objects from becoming stale.

TAG=agy
CONV=0a224b86-8e8f-46de-aa9b-83554f270ca5

Fixed: 522299155
Change-Id: Ib7615898a5fb489cb7755f1e3f1d04f95715e08a
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/149550
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
---

diff --git a/core/fpdfdoc/cpdf_nametree.cpp b/core/fpdfdoc/cpdf_nametree.cpp
index 13cc7bd..ee5b05b 100644
--- a/core/fpdfdoc/cpdf_nametree.cpp
+++ b/core/fpdfdoc/cpdf_nametree.cpp
@@ -32,14 +32,20 @@
 };
 
 std::pair<WideString, WideString> GetNodeLimits(CPDF_Array* limits) {
-  WideString left = limits->GetUnicodeTextAt(0);
-  WideString right = limits->GetUnicodeTextAt(1);
+  while (limits->size() < 2) {
+    limits->AppendNew<CPDF_String>("");
+  }
+
+  RetainPtr<CPDF_Object> obj0 = limits->GetMutableObjectAt(0);
+  RetainPtr<CPDF_Object> obj1 = limits->GetMutableObjectAt(1);
+  WideString left = obj0->GetUnicodeText();
+  WideString right = obj1->GetUnicodeText();
 
   // If the lower limit is greater than the upper limit, swap them.
   if (left.Compare(right) > 0) {
     std::swap(left, right);
-    limits->SetNewAt<CPDF_String>(0, left.AsStringView());
-    limits->SetNewAt<CPDF_String>(1, right.AsStringView());
+    limits->SetAt(0, std::move(obj1));
+    limits->SetAt(1, std::move(obj0));
   }
 
   return {std::move(left), std::move(right)};
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free in PDFium PopulateAnnotationLinks via aliased Limits array

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free exists in PDFium when rendering pages with a shared array acting as both an /Annots array and a Name Tree /Limits array. During link enumeration, a name lookup can trigger an in-place mutation of the aliased array, destroying inline dictionaries while a raw pointer is still held. Subsequent access to this dangling pointer results in a Use-After-Free.

Affected files:

  • third_party/pdfium/core/fpdfdoc/cpdf_nametree.cpp

Estimated timestamp from git blame: 2017-07-24

Summary

A potential Use-After-Free (UAF) vulnerability exists in Chromium’s PDFium component. The issue occurs due to an unsafe in-place mutation of a Name Tree node’s /Limits array during name lookups. If this array is aliased with a page’s /Annots array, the mutation can destroy an inline annotation dictionary while a raw pointer to it (FPDF_LINK) is actively being held by PDFiumPage::PopulateAnnotationLinks(). Dereferencing this pointer leads to a UAF.

Vulnerability Details

In third_party/pdfium/core/fpdfdoc/cpdf_nametree.cpp, the SearchNameNodeByNameInternal function traverses Name Tree nodes during lookups. As part of this traversal, it calls GetNodeLimits to evaluate the node’s bounds. If GetNodeLimits determines that the lower limit is greater than the upper limit (via string comparison), it swaps the elements and replaces them with new CPDF_String objects using SetNewAt:

std::pair<WideString, WideString> GetNodeLimits(CPDF_Array* limits) {
  WideString left = limits->GetUnicodeTextAt(0);
  WideString right = limits->GetUnicodeTextAt(1);
  // If the lower limit is greater than the upper limit, swap them.
  if (left.Compare(right) > 0) {
    std::swap(left, right);
    limits->SetNewAt<CPDF_String>(0, left.AsStringView());
    limits->SetNewAt<CPDF_String>(1, right.AsStringView());
  }
  return {std::move(left), std::move(right)};
}

If the replaced elements are inline objects, the SetNewAt assignment drops their only RetainPtr reference count to zero, causing immediate destruction of the objects.

Concurrently, PDFiumPage::PopulateAnnotationLinks() iterates over the /Annots array using FPDFLink_Enumerate(). FPDFLink_Enumerate yields raw pointers to the annotation dictionaries via the FPDF_LINK type, which is effectively a void* and is not protected by MiraclePtr/raw_ptr. Inside this enumeration loop, PopulateAnnotationLinks calls GetLinkTarget(), which may trigger a Name Tree lookup if the link destination is a named string.

Potential Exploitation Steps

While we do not have a working Proof of Concept, an attacker could potentially trigger this vulnerability via the following steps:

  1. Construct a malicious PDF with an indirect Array object (e.g., 1 0 R).
  2. Populate the array with two inline elements: a string and a dictionary (e.g., [ (Z_String) << /Subtype /Link /Dest (SomeDest) /Rect [0 0 100 100] >> ]).
  3. Alias the array by setting it as both the page’s /Annots array and a /Dests Name Tree node’s /Limits array.
  4. When Chromium processes the page, PopulateAnnotationLinks enumerates the array. It skips the string and yields a raw FPDF_LINK to the inline dictionary.
  5. PopulateAnnotationLinks calls GetLinkTarget, which extracts (SomeDest) and triggers a Name Tree lookup.
  6. The lookup traverses the aliased node. GetNodeLimits fetches the elements. GetUnicodeTextAt(0) returns "Z_String", while GetUnicodeTextAt(1) evaluates the dictionary and returns "" (as dictionaries are not strings).
  7. GetNodeLimits evaluates "Z_String" > "" as true. It calls SetNewAt, replacing the dictionary with a new CPDF_String.
  8. The inline dictionary is immediately destroyed as its refcount hits 0.
  9. Execution returns to PopulateAnnotationLinks, which calls FPDFLink_GetAnnotRect(link_annot, ...) using the now-dangling FPDF_LINK pointer.
  10. FPDFLink_GetAnnotRect dereferences the freed memory, leading to a UAF and potentially Arbitrary Code Execution within the Renderer process.

Suggested Fix

To fix this vulnerability, GetNodeLimits should not perform in-place mutation of the /Limits array during read-only lookup operations. Alternatively, the enumeration APIs (FPDFLink_Enumerate and the consumers like PopulateAnnotationLinks) should hold strong references (RetainPtr<CPDF_Dictionary>) to the objects they are processing across calls that might mutate the PDF structure.

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker