CVE-2026-11670
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifpdf/pdfium/pdfium_form_filler.cc |
modified |
Files Changed
pdf/pdfium/pdfium_engine.ccpdf/pdfium/pdfium_engine.hpdf/pdfium/pdfium_form_filler.cc
Patch
From 7ef690dafb90e05d20a1a95075c4452297b4de89 Mon Sep 17 00:00:00 2001
From: Lei Zhang <thestig@chromium.org>
Date: Tue, 26 May 2026 12:27:54 -0700
Subject: [PATCH] [PDF] Avoid running PDFium form callbacks in PDFiumEngine dtor
Prevent confusion when PDFiumEngine destruction runs various callbacks
that can then trigger page lookups. Even though PDFiumEngine is trying
to clean up and unload the pages. Set a new boolean member in
PDFiumEngine to indicate destruction is in progress. Check for that in
PDFiumFormFiller and just return early.
Bug: 515469283
Change-Id: I4f7d6c7ad7a0e3cc6760bdac0288f21e3d4e7b95
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7872617
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1636398}
---
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index a397144..2506119 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -845,6 +845,8 @@
}
PDFiumEngine::~PDFiumEngine() {
+ in_dtor_ = true;
+
if (!client_->IsPrintPreview()) {
base::UmaHistogramLongTimes("PDF.EngineLifetime",
base::TimeTicks::Now() - engine_creation_time_);
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 1e9d6cc..5c37e74 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -1485,6 +1485,8 @@
std::map<InkTextId, InkTextData> ink_text_data_;
#endif // BUILDFLAG(ENABLE_PDF_INK2)
+ bool in_dtor_ = false;
+
base::WeakPtrFactory<PDFiumEngine> weak_factory_{this};
// Weak pointers from this factory are used to bind the ContinueFind()
diff --git a/pdf/pdfium/pdfium_form_filler.cc b/pdf/pdfium/pdfium_form_filler.cc
index 5e50b8f..e10291274 100644
--- a/pdf/pdfium/pdfium_form_filler.cc
+++ b/pdf/pdfium/pdfium_form_filler.cc
@@ -131,6 +131,10 @@
double bottom) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
int page_index = engine->GetVisiblePageIndex(page);
if (page_index == -1) {
// This can sometime happen when the page is closed because it went off
@@ -153,6 +157,10 @@
double bottom) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
int page_index = engine->GetVisiblePageIndex(page);
if (page_index == -1)
return;
@@ -207,6 +215,10 @@
void PDFiumFormFiller::Form_OnChange(FPDF_FORMFILLINFO* param) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
engine->EnteredEditMode();
}
@@ -216,6 +228,10 @@
int page_index) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return nullptr;
+ }
+
if (!engine->PageIndexInBounds(page_index))
return nullptr;
return engine->pages_[page_index]->GetPage();
@@ -226,6 +242,10 @@
FPDF_DOCUMENT document) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return nullptr;
+ }
+
int index = engine->last_focused_page_;
if (index == -1) {
index = engine->GetMostVisiblePage();
@@ -248,6 +268,10 @@
FPDF_BYTESTRING named_action) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
std::string action(named_action);
if (action == "Print") {
engine->client_->Print();
@@ -296,6 +320,10 @@
int page_index) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
if (!engine->PageIndexInBounds(page_index))
return;
@@ -315,6 +343,10 @@
FPDF_BYTESTRING uri) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
engine->client_->NavigateTo(std::string(uri),
WindowOpenDisposition::CURRENT_TAB);
}
@@ -327,6 +359,10 @@
int size_of_array) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
engine->ScrollToPage(page_index);
}
@@ -337,6 +373,10 @@
int modifiers) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
bool middle_button =
!!(modifiers & blink::WebInputEvent::Modifiers::kMiddleButtonDown);
bool alt_key = !!(modifiers & blink::WebInputEvent::Modifiers::kAltKey);
@@ -369,6 +409,10 @@
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
engine->client_->Email(to_str, cc_str, bcc_str, subject_str, message_str);
}
@@ -387,6 +431,10 @@
int page) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
engine->ScrollToPage(page);
}
@@ -395,6 +443,10 @@
FPDF_DOCUMENT document) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return -1;
+ }
+
return engine->GetMostVisiblePage();
}
@@ -407,6 +459,10 @@
double* bottom) {
EngineInIsolateScope engine_scope = GetEngineInIsolateScope(param);
PDFiumEngine* engine = engine_scope.engine();
+ if (engine->in_dtor_) {
+ return;
+ }
+
Original Bug Report
Use-After-Free in PDFiumEngine during destruction via re-entrant JavaScript
Project Fortify, 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 (UAF) vulnerability exists in PDFiumEngine when nested JavaScript execution is triggered during the destruction sequence. This allows for re-entrant calls to engine logic after internal state vectors have been destroyed, resulting in heap writes on freed buffers.
Affected files:
pdf/pdfium/pdfium_engine.ccpdf/pdfium/pdfium_engine.hpdf/pdfium/pdfium_page.ccpdf/pdfium/pdfium_form_filler.ccthird_party/pdfium/fpdfsdk/formfiller/cffl_interactiveformfiller.cppthird_party/pdfium/fpdfsdk/cpdfsdk_formfillenvironment.cpp
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in the PDFiumEngine component of the PDF renderer. The issue stems from the order of member destruction in PDFiumEngine and the ability of attacker-controlled JavaScript to trigger re-entrant calls to engine methods after certain member variables have already been destroyed.
Technical Analysis
In pdf/pdfium/pdfium_engine.h, the declaration order of member variables determines their destruction order (reverse declaration order). Specifically:
pages_is declared at line 1205.visible_pages_,pending_pages_, anddeferred_page_unloads_are declared at lines 1208, 1211, and 1217 respectively.
During destruction of a PDFiumEngine object, the vectors at 1208-1217 are destroyed before the pages_ vector. When a std::vector is destroyed in libc++, its backing buffer is deallocated, but its internal pointers (begin, end, cap) remain in the object’s memory as dangling pointers.
Following the destruction of these vectors, the pages_ vector begins its destruction. This involves invoking the destructor for each PDFiumPage element. If a page was re-established and focused during the destructor’s ‘Will Close’ JavaScript phase, its subsequent closure during the destruction of pages_ can trigger a ‘Blur’ (/Bl) JavaScript action.
If this nested JavaScript calls back into the engine (e.g., by querying this.pageNum), it triggers PDFiumEngine::CalculateVisiblePages(). This function attempts to clear() and push_back() into the now-destroyed vectors (visible_pages_, pending_pages_, etc.).
Because the vector’s internal pointers were not nulled during destruction, push_back may perform a placement-new write of a uint32_t (the page index) directly into the freed PartitionAlloc heap slot associated with the vector’s former backing store. This provides a primitive for an arbitrary-value heap write into a freed buffer.
Potential Attack Sequence (Suggested)
- An attacker provides a PDF containing a document ‘Will Close’ action and a form field on a specific page with a ‘Blur’ action.
- When the user closes the PDF,
~PDFiumEngine()executes. - The ‘Will Close’ script runs and re-establishes focus on the form field, forcing the relevant page to be reloaded if it was previously unloaded.
- The
~PDFiumEngine()body completes, and member destruction begins. visible_pages_and related vectors are destroyed and their memory freed.- Destruction of the
pages_vector starts. When the focused page is destroyed, it triggersFPDF_ClosePage. - PDFium initiates the ‘Blur’ action for the focused field.
- The Blur JavaScript queries a property that triggers
CalculateVisiblePages()(e.g.,this.pageNum). CalculateVisiblePages()performs UAF writes into the destroyedvisible_pages_vector.
Impact
This vulnerability could allow an attacker to achieve arbitrary code execution (RCE) within the sandboxed renderer process. By utilizing heap-spraying techniques, an attacker might reclaim the freed vector buffers with controlled objects before the UAF write occurs.
Mitigation Status
This issue is likely not protected by MiraclePtr (BackupRefPtr) because it involves the internal pointers of std::vector within libc++, which are currently outside the scope of MiraclePtr protection.
Suggested Fix
The document’s ‘Will Close’ action (FPDFDOC_AACTION_WC) should be executed at the very beginning of ~PDFiumEngine(), before any pages are unloaded or state is modified. Additionally, explicit steps should be taken to clear all form focus and page views before member destruction begins to ensure no JavaScript can be triggered during the teardown of the pages_ vector.
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
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.