CVE-2026-10945
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forpdf/pdfium/pdfium_engine.cc |
modified |
Files Changed
pdf/pdfium/pdfium_engine.ccpdf/pdfium/pdfium_engine.h
Patch
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.
Original Bug Report
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.cpppdf/pdfium/pdfium_engine.ccthird_party/pdfium/core/fpdfapi/page/cpdf_pageobjectholder.cpppdf/pdf_ink_module.ccthird_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
- Initialize a PDF layout near the deque map boundary.
- Trigger a progressive rendering yield.
- Apply an Ink2 stroke to force reallocation.
- 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.