Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in PDF
DescriptionUse after free in PDF
ComponentPDF
Bug ClassUAF
Tracker504417768
Fix commit1a3af3572688 (chromium/src) +30/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
for
pdf/pdfium/pdfium_engine.cc
modified

Files Changed

  • pdf/pdfium/pdfium_engine.cc
  • pdf/pdfium/pdfium_engine.h
From 1a3af35726889f16fb6b62d395660ec2d1d337dc Mon Sep 17 00:00:00 2001
From: Lei Zhang <[email protected]>
Date: Wed, 20 May 2026 13:22:44 -0700
Subject: [PATCH] [PDF Ink Signatures] Cancel painting when modifying PDFium pages

In PDFiumEngine, when methods like ApplyStroke() and DiscardText()
modify the list of FPDF_PAGEOBJECTs in a page, that may interact badly
with progressive rendering. It can be very complicated to build a
queuing mechanism for these modifications, so avoid doing that and
cancel the progressive paints instead. Do this by calling CancelPaint().

Unlike existing CancelPaint() calls, which presumably can already
somehow trigger page invalidation, these new CancelPaint() calls should
restart progressive paint after the FPDF_PAGEOBJECT modification takes
place. To support this, change CancelPaint() to return the list of rects
that were pending for painting. Then invalidate those rects to let
progressive paint restart.

Bug: 504417768
Change-Id: I678ab56b1127227ccfb88c3f37ca2dc9160869b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7861421
Reviewed-by: Andy Phan <[email protected]>
Commit-Queue: Lei Zhang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633790}
---

diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index bf6a9ed..ddac0bee8 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -3723,12 +3723,15 @@
   MaybeRequestPendingThumbnail(page_index);
 }
 
-void PDFiumEngine::CancelPaints() {
+std::vector<gfx::Rect> PDFiumEngine::CancelPaints() {
+  std::vector<gfx::Rect> canceled_rects;
   for (const auto& paint : progressive_paints_) {
     FPDF_RenderPage_Close(pages_[paint.page_index()]->GetPage());
+    canceled_rects.push_back(paint.rect());
   }
 
   progressive_paints_.clear();
+  return canceled_rects;
 }
 
 void PDFiumEngine::FillPageSides(int progressive_index) {
@@ -5144,6 +5147,8 @@
 }
 
 void PDFiumEngine::DiscardText(InkTextId id) {
+  std::vector<gfx::Rect> canceled_rects = CancelPaints();
+
   auto it = ink_text_data_.find(id);
   CHECK(it != ink_text_data_.end());
 
@@ -5164,6 +5169,10 @@
   if (!PageStillHasEdits(page_index)) {
     edited_pages_unload_preventers_.erase(page_index);
   }
+
+  for (const gfx::Rect& rect : canceled_rects) {
+    client_->Invalidate(rect);
+  }
 }
 
 void PDFiumEngine::DrawText(int page_index,
@@ -5171,6 +5180,8 @@
                             base::span<const InkTextInfo> text_info,
                             double pdf_zoom,
                             const InkTextBoxAttributes& attributes) {
+  std::vector<gfx::Rect> canceled_rects = CancelPaints();
+
   CHECK(PageIndexInBounds(page_index));
   PDFiumPage* pdfium_page = GetPage(page_index);
   CHECK(pdfium_page);
@@ -5258,6 +5269,10 @@
     edited_pages_unload_preventers_.insert(
         {page_index, PDFiumPage::ScopedPageUnloadPreventer(pdfium_page)});
   }
+
+  for (const gfx::Rect& rect : canceled_rects) {
+    client_->Invalidate(rect);
+  }
 }
 
 void PDFiumEngine::UpdateTextActiveAndInvalidate(InkTextId id, bool active) {
@@ -5302,6 +5317,8 @@
 void PDFiumEngine::ApplyStroke(int page_index,
                                InkStrokeId id,
                                const ink::Stroke& stroke) {
+  std::vector<gfx::Rect> canceled_rects = CancelPaints();
+
   // Saving a stroke will have the same page bounds limitations as the original
   // document.
   PDFiumPage* pdfium_page = GetPage(page_index);
@@ -5326,6 +5343,10 @@
     edited_pages_unload_preventers_.insert(
         {page_index, PDFiumPage::ScopedPageUnloadPreventer(pdfium_page)});
   }
+
+  for (const gfx::Rect& rect : canceled_rects) {
+    client_->Invalidate(rect);
+  }
 }
 
 void PDFiumEngine::UpdateStrokeActive(int page_index,
@@ -5342,6 +5363,8 @@
 }
 
 void PDFiumEngine::DiscardStroke(int page_index, InkStrokeId id) {
+  std::vector<gfx::Rect> canceled_rects = CancelPaints();
+
   CHECK(PageIndexInBounds(page_index));
   auto it = ink_stroke_data_.find(id);
   CHECK(it != ink_stroke_data_.end());
@@ -5353,6 +5376,10 @@
   if (!PageStillHasEdits(page_index)) {
     edited_pages_unload_preventers_.erase(page_index);
   }
+
+  for (const gfx::Rect& rect : canceled_rects) {
+    client_->Invalidate(rect);
+  }
 }
 
 PDFLoadedWithV2InkAnnotations PDFiumEngine::ContainsV2InkPath(
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 594dc71..1e9d6cc 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -938,7 +938,8 @@
   void FinishPaint(size_t progressive_index, SkBitmap& image_data);
 
   // Stops any paints that are in progress.
-  void CancelPaints();
+  // Returns the rectangles, in screen coordinates, that had painting canceled.
+  std::vector<gfx::Rect> CancelPaints();
 
   // Invalidates all pages. Use this when some global parameter, such as page
   // orientation, has changed.
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in PDFium via Ink2 Progressive Rendering

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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: CPDF_ProgressiveRenderer persists a deque iterator across asynchronous rendering yields. The Ink2 feature permits mutating the underlying deque during these yields, leading to map reallocation and a Use-After-Free when rendering resumes.

Affected files:

  • third_party/pdfium/core/fpdfapi/render/cpdf_progressiverenderer.cpp
  • pdf/pdfium/pdfium_engine.cc
  • third_party/pdfium/core/fpdfapi/page/cpdf_pageobjectholder.cpp
  • pdf/pdf_ink_module.cc
  • third_party/pdfium/core/fpdfapi/render/cpdf_progressiverenderer.h

Estimated timestamp from git blame: 2025-07-29

Summary

A potential Use-After-Free (UAF) exists in PDFium’s progressive rendering pipeline when combined with the Ink2 annotation feature.

Technical Details

Initial logic and parameters are validated: CPDF_ProgressiveRenderer::Continue() saves a std::deque::const_iterator (last_object_rendered_) across task yields to allow for asynchronous page rendering.

Standard processing applied during the yield window: The Ink2 feature processes user input and appends new strokes to the page’s object list via PDFiumEngine::ApplyStroke. This modifies the std::deque without cancelling ongoing progressive paints.

Pushing to the deque forces an internal map reallocation. When rendering resumes, CPDF_ProgressiveRenderer::Continue() increments the stale iterator. This reads directly from the freed map array, yielding an arbitrary CPDF_PageObject*. The subsequent virtual call to IsActive() on this wild pointer results in immediate control flow hijacking.

Potential Reproduction Steps

  1. Initialize a PDF layout near the deque map boundary.
  2. Trigger a progressive rendering yield.
  3. Apply an Ink2 stroke to force reallocation.
  4. Resume rendering to trigger the virtual function call on the controlled pointer. (Note: These are suggested/potential steps; our tooling agent does not yet have the ability to run code.)

Suggested Fix

Introduce state guards in PDFiumEngine::ApplyStroke (similar to RequestThumbnail) to cancel or properly synchronize active progressive paints before appending to page_object_list_.

Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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