Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in WebView
DescriptionInappropriate implementation in WebView
ComponentWebView
Bug ClassLogic Error
Tracker502228856
Fix commitcfb95bd258f8 (chromium/src) +21/-16
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
android_webview/browser/aw_print_manager.cc
modified

Files Changed

  • android_webview/browser/aw_print_manager.cc
  • android_webview/browser/aw_print_manager.h
From cfb95bd258f8904760555616e8503a82d9e4792f Mon Sep 17 00:00:00 2001
From: Peter Pakkenberg <pbirk@chromium.org>
Date: Wed, 15 Apr 2026 08:43:39 -0700
Subject: [PATCH] Reset webview print fd when printing is done

Fixed: 502228856
Change-Id: I704585cb57dabbd8607b62c08c84da13e9b32c2a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7761982
Commit-Queue: Richard Coles <torne@chromium.org>
Auto-Submit: Peter Birk Pakkenberg <pbirk@chromium.org>
Reviewed-by: Richard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1615204}
---

diff --git a/android_webview/browser/aw_print_manager.cc b/android_webview/browser/aw_print_manager.cc
index ea72f7a3..2abbd97 100644
--- a/android_webview/browser/aw_print_manager.cc
+++ b/android_webview/browser/aw_print_manager.cc
@@ -62,9 +62,14 @@
 }
 
 void AwPrintManager::PdfWritingDone(int page_count) {
-  pdf_writing_done_callback().Run(page_count);
   // Invalidate the file descriptor so it doesn't get reused.
-  fd_ = -1;
+  fd_ = base::kInvalidFd;
+  // Trigger the callback to notify the embedding application that printing is
+  // done. A non-positive `page_count` value (<=0) will be presented as an error
+  // callback to the application.
+  if (pdf_writing_done_callback()) {
+    pdf_writing_done_callback().Run(page_count);
+  }
 }
 
 bool AwPrintManager::PrintNow() {
@@ -134,12 +139,14 @@
     printing::mojom::DidPrintDocumentParamsPtr params,
     DidPrintDocumentCallback callback) {
   if (params->document_cookie != cookie()) {
+    PdfWritingDone(0);
     std::move(callback).Run(false);
     return;
   }
 
   const printing::mojom::DidPrintContentParams& content = *params->content;
   if (!content.metafile_data_region.IsValid()) {
+    PdfWritingDone(0);
     std::move(callback).Run(false);
     return;
   }
@@ -147,35 +154,31 @@
   auto data = base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(
       content.metafile_data_region);
   if (!data) {
-    std::move(callback).Run(false);
-    return;
-  }
-
-  if (number_pages() > printing::kMaxPageCount) {
-    web_contents()->Stop();
     PdfWritingDone(0);
     std::move(callback).Run(false);
     return;
   }
 
-  DCHECK(pdf_writing_done_callback());
+  if (number_pages() > printing::kMaxPageCount) {
+    PdfWritingDone(0);
+    std::move(callback).Run(false);
+    return;
+  }
+
   base::ThreadPool::CreateTaskRunner(
       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
       ->PostTaskAndReplyWithResult(
           FROM_HERE, base::BindOnce(&SaveDataToFd, fd_, number_pages(), data),
           base::BindOnce(&AwPrintManager::OnDidPrintDocumentWritingDone,
-                         pdf_writing_done_callback(), std::move(callback)));
+                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-// static
 void AwPrintManager::OnDidPrintDocumentWritingDone(
-    const PdfWritingDoneCallback& callback,
     DidPrintDocumentCallback did_print_document_cb,
     uint32_t page_count) {
   DCHECK_LE(page_count, printing::kMaxPageCount);
-  if (callback)
-    callback.Run(base::checked_cast<int>(page_count));
+  PdfWritingDone(base::checked_cast<int>(page_count));
   std::move(did_print_document_cb).Run(true);
 }
 
diff --git a/android_webview/browser/aw_print_manager.h b/android_webview/browser/aw_print_manager.h
index 8cfc47e..bdcd853 100644
--- a/android_webview/browser/aw_print_manager.h
+++ b/android_webview/browser/aw_print_manager.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "components/printing/browser/print_manager.h"
 #include "components/printing/common/print.mojom-forward.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -50,8 +51,7 @@
   void ScriptedPrint(printing::mojom::ScriptedPrintParamsPtr params,
                      ScriptedPrintCallback callback) override;
 
-  static void OnDidPrintDocumentWritingDone(
-      const PdfWritingDoneCallback& callback,
+  void OnDidPrintDocumentWritingDone(
       DidPrintDocumentCallback did_print_document_cb,
       uint32_t page_count);
 
@@ -61,6 +61,8 @@
   int fd_ = -1;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  base::WeakPtrFactory<AwPrintManager> weak_ptr_factory_{this};
 };
 
 }  // namespace android_webview
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential File Descriptor reuse in AwPrintManager allows unsandboxed write

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.

Overview: AwPrintManager in Android WebView fails to reset its internal file descriptor and document cookie after a successful print job. If the operating system recycles the closed file descriptor for a sensitive resource, a compromised renderer can reuse the stale cookie to trigger an arbitrary write to it. This provides a potential sandbox escape primitive by writing to an active file or socket within the browser/app process.

Affected files:

  • android_webview/browser/aw_print_manager.cc
  • android_webview/browser/aw_print_manager.h

Estimated timestamp from git blame: 2023-06-21

Summary

A potential file descriptor (FD) reuse vulnerability exists in AwPrintManager within Android WebView. When a print operation completes successfully, the manager fails to invalidate its document cookie and the associated raw file descriptor. This allows a compromised renderer to trigger subsequent writes to that FD integer. If the FD is recycled by the browser process for other purposes (e.g., Mojo channels, network sockets, or sensitive files), the renderer can perform an unsandboxed write to those resources.

Root Cause

In android_webview/browser/aw_print_manager.cc, the UpdateParam method initializes fd_ (a raw int) and generates a new document cookie via set_cookie(printing::PrintSettings::NewCookie()).

When the renderer completes the document printing, it calls the DidPrintDocument Mojo IPC. AwPrintManager::DidPrintDocument verifies that the provided document_cookie matches the stored cookie. If it matches, it posts a task (SaveDataToFd) to a background thread pool to write the data, and schedules the reply callback OnDidPrintDocumentWritingDone on the UI thread.

Because OnDidPrintDocumentWritingDone is static, it does not clear the fd_ or cookie_ members of the AwPrintManager instance upon completion. While AwPrintManager::PdfWritingDone correctly resets fd_ = -1, it is only called on failure paths (e.g., exceeding page limits) and not during a standard successful print flow. Additionally, cookie_ is never reset to an invalid state.

When the print operation finishes, the Android framework takes ownership of the ParcelFileDescriptor and closes it. This frees the FD number at the OS level, while the AwPrintManager instance still considers the integer valid.

Potential Exploitation Steps

Note: These are suggested steps based on static analysis; our tooling agent cannot execute code to provide a working proof-of-concept.

  1. A compromised renderer waits for the user or app to perform a legitimate print or ‘Save-as-PDF’ operation in a WebView.
  2. The renderer receives the legitimate print request and records the valid document_cookie.
  3. The renderer fulfills the legitimate print request via the DidPrintDocument IPC. The browser writes the data and the Android OS closes the file descriptor.
  4. The attacker waits for the browser (app) process to recycle the freed FD number for a sensitive resource, such as a Mojo channel socket, a network socket, or an app-private database file.
  5. The compromised renderer sends a second DidPrintDocument IPC with the recorded cookie and a malicious payload in the metafile_data_region.
  6. AwPrintManager accepts the request because the cookie_ was never reset and still matches. It posts the SaveDataToFd task to the background thread pool.
  7. The background thread executes base::WriteFileDescriptor, writing the attacker’s payload into the recycled FD.
  8. Note on App Crash: After the background write succeeds, the reply callback on the UI thread triggers AwPdfExporter.didExportPdf in Java. Because the Java result callback was nulled out after the first print, this throws a NullPointerException and crashes the app. However, because the arbitrary write occurs on a background thread before the UI thread executes the reply, the sandbox escape payload is successfully delivered prior to the crash.

Suggested Fix

The state of AwPrintManager must be properly cleaned up after a successful print job.

Modify the success path so that the cookie_ is invalidated (e.g., reset to 0 or an invalid cookie state) and fd_ is explicitly reset to -1 once the file descriptor is no longer needed. This could be achieved by making OnDidPrintDocumentWritingDone a non-static member function (bound with a WeakPtr), or by ensuring that a cleanup routine similar to PdfWritingDone is invoked unconditionally upon task completion.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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