CVE-2026-17875
Overview
Files Changed
core/fpdfdoc/cpdf_nametree.cpp
Patch
From 7c53431c7e353de36f70df5fddd0e397ad9d79cf Mon Sep 17 00:00:00 2001 From: Tom Sepez <[email protected]> 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 <[email protected]> Commit-Queue: Tom Sepez <[email protected]> --- 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)};
Original Bug Report
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:
- Construct a malicious PDF with an indirect Array object (e.g.,
1 0 R). - 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] >> ]). - Alias the array by setting it as both the page’s
/Annotsarray and a/DestsName Tree node’s/Limitsarray. - When Chromium processes the page,
PopulateAnnotationLinksenumerates the array. It skips the string and yields a rawFPDF_LINKto the inline dictionary. PopulateAnnotationLinkscallsGetLinkTarget, which extracts(SomeDest)and triggers a Name Tree lookup.- The lookup traverses the aliased node.
GetNodeLimitsfetches the elements.GetUnicodeTextAt(0)returns"Z_String", whileGetUnicodeTextAt(1)evaluates the dictionary and returns""(as dictionaries are not strings). GetNodeLimitsevaluates"Z_String" > ""as true. It callsSetNewAt, replacing the dictionary with a newCPDF_String.- The inline dictionary is immediately destroyed as its refcount hits 0.
- Execution returns to
PopulateAnnotationLinks, which callsFPDFLink_GetAnnotRect(link_annot, ...)using the now-danglingFPDF_LINKpointer. FPDFLink_GetAnnotRectdereferences 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.