Low chrome UAF 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebProtect
DescriptionUse after free in WebProtect
ComponentWebProtect
Bug ClassUAF
Tracker500474551
Fix commit74389539507a (chromium/src) +230/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
TestPrintJobForContentAnalysis
chrome/browser/printing/print_view_manager_unittest.cc
modified
FakePrintAnalysisDelegate
chrome/browser/printing/print_view_manager_unittest.cc
modified
TestPrintViewManagerForContentAnalysis
chrome/browser/printing/print_view_manager_unittest.cc
modified

Files Changed

  • chrome/browser/printing/print_view_manager_unittest.cc
  • chrome/browser/printing/print_view_manager_base.cc
From 74389539507aaa0c89b28d183874acd1259149e9 Mon Sep 17 00:00:00 2001
From: Ali Kadum <akadum@google.com>
Date: Wed, 23 Sep 2026 13:04:50 -0700
Subject: [PATCH] Make a safe copy of print data before enterprise content analysis

Creates an immutable browser-owned copy of the print data via
base::RefCountedBytes before running enterprise content analysis. This
prevents a compromised renderer from mutating the underlying shared
memory while analysis is running, ensuring that the printed document
matches the data that was analyzed.

Fixed: 500474551
Change-Id: Ifdff2322a0298ac8ed3588f4ecade223d78d3de0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8445537
Reviewed-by: Dominique Fauteux-Chapleau <domfc@chromium.org>
Commit-Queue: Ali Kadum <akadum@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Auto-Submit: Ali Kadum <akadum@google.com>
Cr-Commit-Position: refs/heads/main@{#1703699}
---
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 4856857..921dad13 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
@@ -1181,13 +1182,17 @@
   scanning_data.printer_name =
       base::UTF16ToUTF8(print_job_->document()->settings().device_name());
 
+  // Create an immutable, browser-owned copy of the print data so a compromised
+  // renderer cannot mutate the backing shared memory while analysis is running.
+  auto safe_print_data =
+      base::MakeRefCounted<base::RefCountedBytes>(*print_data);
   auto on_verdict = base::BindOnce(
       &PrintViewManagerBase::CompletePrintDocumentAfterContentAnalysis,
-      weak_ptr_factory_.GetWeakPtr(), print_data, page_size, content_area,
+      weak_ptr_factory_.GetWeakPtr(), safe_print_data, page_size, content_area,
       offsets);
 
   enterprise_data_protection::PrintIfAllowedByPolicy(
-      print_data, web_contents()->GetOutermostWebContents(),
+      std::move(safe_print_data), web_contents()->GetOutermostWebContents(),
       std::move(scanning_data), std::move(on_verdict));
 }
 
diff --git a/chrome/browser/printing/print_view_manager_unittest.cc b/chrome/browser/printing/print_view_manager_unittest.cc
index 5d32795..a5a3c16 100644
--- a/chrome/browser/printing/print_view_manager_unittest.cc
+++ b/chrome/browser/printing/print_view_manager_unittest.cc
@@ -10,35 +10,42 @@
 #include "base/containers/span.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "build/build_config.h"
+#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_preview_test.h"
+#include "chrome/browser/printing/print_view_manager_base.h"
+#include "components/enterprise/buildflags/buildflags.h"
 #include "content/public/test/browser_test_utils.h"
+#include "printing/print_settings.h"
+#include "printing/printed_document.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
+#endif
+
 #if BUILDFLAG(IS_WIN)
 #include "base/auto_reset.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/notreached.h"
 #include "base/values.h"
-#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_job_manager.h"
 #include "chrome/browser/printing/print_job_worker.h"
 #include "chrome/browser/printing/print_test_utils.h"
-#include "chrome/browser/printing/print_view_manager_base.h"
 #include "chrome/browser/printing/printer_query.h"
 #include "components/printing/common/print.mojom.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/test/test_renderer_host.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "printing/mojom/print.mojom.h"
-#include "printing/print_settings.h"
 #include "printing/print_settings_conversion.h"
-#include "printing/printed_document.h"
 #include "printing/units.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/gfx/geometry/point.h"
@@ -460,4 +467,213 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+namespace {
+
+class TestPrintJobForContentAnalysis : public PrintJob {
+ public:
+  TestPrintJobForContentAnalysis() = default;
+  TestPrintJobForContentAnalysis(const TestPrintJobForContentAnalysis&) =
+      delete;
+  TestPrintJobForContentAnalysis& operator=(
+      const TestPrintJobForContentAnalysis&) = delete;
+
+  void SetDocument(scoped_refptr<PrintedDocument> doc) {
+    UpdatePrintedDocument(std::move(doc));
+  }
+
+  void StartPrinting() override { set_job_pending_for_testing(true); }
+  void Stop() override { set_job_pending_for_testing(false); }
+  void Cancel() override { set_job_pending_for_testing(false); }
+
+#if BUILDFLAG(IS_WIN)
+  void StartPdfToEmfConversion(scoped_refptr<base::RefCountedMemory> bytes,
+                               const gfx::Size& page_size,
+                               const gfx::Rect& content_area,
+                               const GURL& url) override {
+    printed_data_ = bytes;
+  }
+  scoped_refptr<base::RefCountedMemory> printed_data() const {
+    return printed_data_;
+  }
+#endif
+
+ protected:
+  ~TestPrintJobForContentAnalysis() override {
+    set_job_pending_for_testing(false);
+  }
+
+#if BUILDFLAG(IS_WIN)
+ private:
+  scoped_refptr<base::RefCountedMemory> printed_data_;
+#endif
+};
+
+class FakePrintAnalysisDelegate
+    : public enterprise_connectors::ContentAnalysisDelegate {
+ public:
+  FakePrintAnalysisDelegate(
+      content::WebContents* contents,
+      Data data,
+      CompletionCallback callback,
+      enterprise_connectors::DeepScanAccessPoint access_point,
+      base::OnceCallback<void(CompletionCallback)> save_callback)
+      : ContentAnalysisDelegate(contents,
+                                ClearPageRegion(std::move(data)),
+                                base::DoNothing(),
+                                access_point) {
+    std::move(save_callback).Run(std::move(callback));
+  }
+  FakePrintAnalysisDelegate(const FakePrintAnalysisDelegate&) = delete;
+  FakePrintAnalysisDelegate& operator=(const FakePrintAnalysisDelegate&) =
+      delete;
+  ~FakePrintAnalysisDelegate() override = default;
+
+ private:
+  static Data ClearPageRegion(Data data) {
+    data.page = base::ReadOnlySharedMemoryRegion();
+    return data;
+  }
+};
+
+class TestPrintViewManagerForContentAnalysis : public PrintViewManagerBase {
+ public:
+  explicit TestPrintViewManagerForContentAnalysis(
+      content::WebContents* web_contents)
+      : PrintViewManagerBase(web_contents) {}
+  TestPrintViewManagerForContentAnalysis(
+      const TestPrintViewManagerForContentAnalysis&) = delete;
+  TestPrintViewManagerForContentAnalysis& operator=(
+      const TestPrintViewManagerForContentAnalysis&) = delete;
+  ~TestPrintViewManagerForContentAnalysis() override { print_job_ = nullptr; }
+
+  void SetPrintJobForTesting(scoped_refptr<PrintJob> print_job) {
+    print_job_ = print_job;
+  }
+
+  using PrintViewManagerBase::ContentAnalysisBeforePrintingDocument;
+
+ protected:
+  void GetPrintPreviewParams(GetPrintPreviewParamsCallback callback) override {
+    NOTREACHED();
+  }
+  void SetupScriptedPrintPreview(
+      SetupScriptedPrintPreviewCallback callback) override {
+    NOTREACHED();
+  }
+  void ShowScriptedPrintPreview() override { NOTREACHED(); }
+  void RequestPrintPreview(
+      mojom::RequestPrintPreviewParamsPtr params) override {
+    NOTREACHED();
+  }
+  void CheckForCancel(const base::UnguessableToken& preview_ui_id,
+                      int32_t request_id,
+                      CheckForCancelCallback callback) override {
+    NOTREACHED();
+  }
+  void SetAccessibilityTree(
+      int32_t cookie,
+      const ui::AXTreeUpdate& accessibility_tree) override {
+    NOTREACHED();
+  }
+};
+
+}  // namespace
+
+using PrintViewManagerBaseTest = PrintPreviewTest;
+
+TEST_F(PrintViewManagerBaseTest,
+       ContentAnalysisBeforePrintingDocumentSnapshotProtectsAgainstMutation) {
+  base::ScopedClosureRunner reset_factory(base::BindOnce(
+      &enterprise_connectors::ContentAnalysisDelegate::ResetFactoryForTesting));
+  enterprise_connectors::ContentAnalysisDelegate::DisableUIForTesting();
+
+  auto print_view_manager =
+      std::make_unique<TestPrintViewManagerForContentAnalysis>(web_contents());
+
+  auto test_job = base::MakeRefCounted<TestPrintJobForContentAnalysis>();
+  auto settings = std::make_unique<PrintSettings>();
+  settings->set_device_name(u"TestPrinter");
+  auto document =
+      base::MakeRefCounted<PrintedDocument>(std::move(settings), u"Doc", 123);
+  test_job->SetDocument(document);
+  print_view_manager->SetPrintJobForTesting(test_job);
+
+  // Set up shared memory with initial benign data.
+  constexpr std::string_view kBenignData = "BENIGN_DATA_FOR_TESTING";
+  constexpr std::string_view kMutatedData = "MUTATED_SENSITIVE_DATA!";
+  static_assert(kBenignData.size() == kMutatedData.size());
+
+  base::MappedReadOnlyRegion region =
+      base::ReadOnlySharedMemoryRegion::Create(kBenignData.size());
+  ASSERT_TRUE(region.IsValid());
+  region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
+      base::as_byte_span(kBenignData));
+
+  auto shared_data =
+      base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region.region);
+  ASSERT_TRUE(shared_data);
+
+  // Intercept the content analysis completion callback using the testing
+  // factory.
+  enterprise_connectors::ContentAnalysisDelegate::CompletionCallback
+      saved_callback;
+  enterprise_connectors::ContentAnalysisDelegate::SetFactoryForTesting(
+      base::BindLambdaForTesting(
+          [&](content::WebContents* contents,
+              enterprise_connectors::ContentAnalysisDelegate::Data data,
+              enterprise_connectors::ContentAnalysisDelegate::CompletionCallback
+                  callback,
+              enterprise_connectors::DeepScanAccessPoint access_point)
+              -> std::unique_ptr<
+                  enterprise_connectors::ContentAnalysisDelegate> {
+            return std::make_unique<FakePrintAnalysisDelegate>(
+                contents, std::move(data), std::move(callback), access_point,
+                base::BindLambdaForTesting(
+                    [&](enterprise_connectors::ContentAnalysisDelegate::
+                            CompletionCallback cb) {
+                      saved_callback = std::move(cb);
+                    }));
+          }));
+
+  enterprise_connectors::ContentAnalysisDelegate::Data scanning_data;
+  print_view_manager->ContentAnalysisBeforePrintingDocument(
+      std::move(scanning_data), shared_data, gfx::Size(100, 100),
+      gfx::Rect(0, 0, 100, 100), gfx::Point(0, 0));
+
+  // The analysis scan has been initiated. In production, DidPrintDocument would
+  // now return and unblock the renderer.
+  // Now simulate a compromised renderer modifying the shared memory before the
+  // scan verdict arrives.
+  region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
+      base::as_byte_span(kMutatedData));
+
+  // The scan completes with an ALLOW verdict.
+  ASSERT_TRUE(saved_callback);
+  enterprise_connectors::ContentAnalysisDelegate::Data result_data;
+  enterprise_connectors::ContentAnalysisDelegate::Result result;
+  result.page_result = true;
+  std::move(saved_callback).Run(result_data, result);
+
+  // Verify that the data sent to the printer matches the benign snapshot taken
+  // before the mutation, NOT the mutated data.
+  std::string_view printed_data;
+#if BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(test_job->printed_data());
+  printed_data = std::string_view(
+      reinterpret_cast<const char*>(test_job->printed_data()->data()),
+      kBenignData.size());
+#else
+  auto doc_data = document->GetDocumentData();
+  ASSERT_TRUE(doc_data.data.IsValid());
+  base::ReadOnlySharedMemoryMapping mapping = doc_data.data.Map();
+  ASSERT_TRUE(mapping.IsValid());
+  printed_data = std::string_view(mapping.GetMemoryAsSpan<const char>().data(),
+                                  kBenignData.size());
+#endif
+  EXPECT_EQ(kBenignData, printed_data);
+  EXPECT_NE(kMutatedData, printed_data);
+}
+#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+
 }  // namespace printing
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/printing/print_view_manager_unittest.cc b/chrome/browser/printing/print_view_manager_unittest.cc
index 5d32795..a5a3c16 100644
--- a/chrome/browser/printing/print_view_manager_unittest.cc
+++ b/chrome/browser/printing/print_view_manager_unittest.cc
@@ -10,35 +10,42 @@
 #include "base/containers/span.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "build/build_config.h"
+#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_preview_test.h"
+#include "chrome/browser/printing/print_view_manager_base.h"
+#include "components/enterprise/buildflags/buildflags.h"
 #include "content/public/test/browser_test_utils.h"
+#include "printing/print_settings.h"
+#include "printing/printed_document.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
+#endif
+
 #if BUILDFLAG(IS_WIN)
 #include "base/auto_reset.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/notreached.h"
 #include "base/values.h"
-#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_job_manager.h"
 #include "chrome/browser/printing/print_job_worker.h"
 #include "chrome/browser/printing/print_test_utils.h"
-#include "chrome/browser/printing/print_view_manager_base.h"
 #include "chrome/browser/printing/printer_query.h"
 #include "components/printing/common/print.mojom.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/test/test_renderer_host.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "printing/mojom/print.mojom.h"
-#include "printing/print_settings.h"
 #include "printing/print_settings_conversion.h"
-#include "printing/printed_document.h"
 #include "printing/units.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/gfx/geometry/point.h"
@@ -460,4 +467,213 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+namespace {
+
+class TestPrintJobForContentAnalysis : public PrintJob {
+ public:
+  TestPrintJobForContentAnalysis() = default;
+  TestPrintJobForContentAnalysis(const TestPrintJobForContentAnalysis&) =
+      delete;
+  TestPrintJobForContentAnalysis& operator=(
+      const TestPrintJobForContentAnalysis&) = delete;
+
+  void SetDocument(scoped_refptr<PrintedDocument> doc) {
+    UpdatePrintedDocument(std::move(doc));
+  }
+
+  void StartPrinting() override { set_job_pending_for_testing(true); }
+  void Stop() override { set_job_pending_for_testing(false); }
+  void Cancel() override { set_job_pending_for_testing(false); }
+
+#if BUILDFLAG(IS_WIN)
+  void StartPdfToEmfConversion(scoped_refptr<base::RefCountedMemory> bytes,
+                               const gfx::Size& page_size,
+                               const gfx::Rect& content_area,
+                               const GURL& url) override {
+    printed_data_ = bytes;
+  }
+  scoped_refptr<base::RefCountedMemory> printed_data() const {
+    return printed_data_;
+  }
+#endif
+
+ protected:
+  ~TestPrintJobForContentAnalysis() override {
+    set_job_pending_for_testing(false);
+  }
+
+#if BUILDFLAG(IS_WIN)
+ private:
+  scoped_refptr<base::RefCountedMemory> printed_data_;
+#endif
+};
+
+class FakePrintAnalysisDelegate
+    : public enterprise_connectors::ContentAnalysisDelegate {
+ public:
+  FakePrintAnalysisDelegate(
+      content::WebContents* contents,
+      Data data,
+      CompletionCallback callback,
+      enterprise_connectors::DeepScanAccessPoint access_point,
+      base::OnceCallback<void(CompletionCallback)> save_callback)
+      : ContentAnalysisDelegate(contents,
+                                ClearPageRegion(std::move(data)),
+                                base::DoNothing(),
+                                access_point) {
+    std::move(save_callback).Run(std::move(callback));
+  }
+  FakePrintAnalysisDelegate(const FakePrintAnalysisDelegate&) = delete;
+  FakePrintAnalysisDelegate& operator=(const FakePrintAnalysisDelegate&) =
+      delete;
+  ~FakePrintAnalysisDelegate() override = default;
+
+ private:
+  static Data ClearPageRegion(Data data) {
+    data.page = base::ReadOnlySharedMemoryRegion();
+    return data;
+  }
+};
+
+class TestPrintViewManagerForContentAnalysis : public PrintViewManagerBase {
+ public:
+  explicit TestPrintViewManagerForContentAnalysis(
+      content::WebContents* web_contents)
+      : PrintViewManagerBase(web_contents) {}
+  TestPrintViewManagerForContentAnalysis(
+      const TestPrintViewManagerForContentAnalysis&) = delete;
+  TestPrintViewManagerForContentAnalysis& operator=(
+      const TestPrintViewManagerForContentAnalysis&) = delete;
+  ~TestPrintViewManagerForContentAnalysis() override { print_job_ = nullptr; }
+
+  void SetPrintJobForTesting(scoped_refptr<PrintJob> print_job) {
+    print_job_ = print_job;
+  }
+
+  using PrintViewManagerBase::ContentAnalysisBeforePrintingDocument;
+
+ protected:
+  void GetPrintPreviewParams(GetPrintPreviewParamsCallback callback) override {
+    NOTREACHED();
+  }
+  void SetupScriptedPrintPreview(
+      SetupScriptedPrintPreviewCallback callback) override {
+    NOTREACHED();
+  }
+  void ShowScriptedPrintPreview() override { NOTREACHED(); }
+  void RequestPrintPreview(
+      mojom::RequestPrintPreviewParamsPtr params) override {
+    NOTREACHED();
+  }
+  void CheckForCancel(const base::UnguessableToken& preview_ui_id,
+                      int32_t request_id,
+                      CheckForCancelCallback callback) override {
+    NOTREACHED();
+  }
+  void SetAccessibilityTree(
+      int32_t cookie,
+      const ui::AXTreeUpdate& accessibility_tree) override {
+    NOTREACHED();
+  }
+};
+
+}  // namespace
+
+using PrintViewManagerBaseTest = PrintPreviewTest;
+
+TEST_F(PrintViewManagerBaseTest,
+       ContentAnalysisBeforePrintingDocumentSnapshotProtectsAgainstMutation) {
+  base::ScopedClosureRunner reset_factory(base::BindOnce(
+      &enterprise_connectors::ContentAnalysisDelegate::ResetFactoryForTesting));
+  enterprise_connectors::ContentAnalysisDelegate::DisableUIForTesting();
+
+  auto print_view_manager =
+      std::make_unique<TestPrintViewManagerForContentAnalysis>(web_contents());
+
+  auto test_job = base::MakeRefCounted<TestPrintJobForContentAnalysis>();
+  auto settings = std::make_unique<PrintSettings>();
+  settings->set_device_name(u"TestPrinter");
+  auto document =
+      base::MakeRefCounted<PrintedDocument>(std::move(settings), u"Doc", 123);
+  test_job->SetDocument(document);
+  print_view_manager->SetPrintJobForTesting(test_job);
+
+  // Set up shared memory with initial benign data.
+  constexpr std::string_view kBenignData = "BENIGN_DATA_FOR_TESTING";
+  constexpr std::string_view kMutatedData = "MUTATED_SENSITIVE_DATA!";
+  static_assert(kBenignData.size() == kMutatedData.size());
+
+  base::MappedReadOnlyRegion region =
+      base::ReadOnlySharedMemoryRegion::Create(kBenignData.size());
+  ASSERT_TRUE(region.IsValid());
+  region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
+      base::as_byte_span(kBenignData));
+
+  auto shared_data =
+      base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region.region);
+  ASSERT_TRUE(shared_data);
+
+  // Intercept the content analysis completion callback using the testing
+  // factory.
+  enterprise_connectors::ContentAnalysisDelegate::CompletionCallback
+      saved_callback;
+  enterprise_connectors::ContentAnalysisDelegate::SetFactoryForTesting(
+      base::BindLambdaForTesting(
+          [&](content::WebContents* contents,
+              enterprise_connectors::ContentAnalysisDelegate::Data data,
+              enterprise_connectors::ContentAnalysisDelegate::CompletionCallback
+                  callback,
+              enterprise_connectors::DeepScanAccessPoint access_point)
+              -> std::unique_ptr<
+                  enterprise_connectors::ContentAnalysisDelegate> {
+            return std::make_unique<FakePrintAnalysisDelegate>(
+                contents, std::move(data), std::move(callback), access_point,
+                base::BindLambdaForTesting(
+                    [&](enterprise_connectors::ContentAnalysisDelegate::
+                            CompletionCallback cb) {
+                      saved_callback = std::move(cb);
+                    }));
+          }));
+
+  enterprise_connectors::ContentAnalysisDelegate::Data scanning_data;
+  print_view_manager->ContentAnalysisBeforePrintingDocument(
+      std::move(scanning_data), shared_data, gfx::Size(100, 100),
+      gfx::Rect(0, 0, 100, 100), gfx::Point(0, 0));
+
+  // The analysis scan has been initiated. In production, DidPrintDocument would
+  // now return and unblock the renderer.
+  // Now simulate a compromised renderer modifying the shared memory before the
+  // scan verdict arrives.
+  region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
+      base::as_byte_span(kMutatedData));
+
+  // The scan completes with an ALLOW verdict.
+  ASSERT_TRUE(saved_callback);
+  enterprise_connectors::ContentAnalysisDelegate::Data result_data;
+  enterprise_connectors::ContentAnalysisDelegate::Result result;
+  result.page_result = true;
+  std::move(saved_callback).Run(result_data, result);
+
+  // Verify that the data sent to the printer matches the benign snapshot taken
+  // before the mutation, NOT the mutated data.
+  std::string_view printed_data;
+#if BUILDFLAG(IS_WIN)
+  ASSERT_TRUE(test_job->printed_data());
+  printed_data = std::string_view(
+      reinterpret_cast<const char*>(test_job->printed_data()->data()),
+      kBenignData.size());
+#else
+  auto doc_data = document->GetDocumentData();
+  ASSERT_TRUE(doc_data.data.IsValid());
+  base::ReadOnlySharedMemoryMapping mapping = doc_data.data.Map();
+  ASSERT_TRUE(mapping.IsValid());
+  printed_data = std::string_view(mapping.GetMemoryAsSpan<const char>().data(),
+                                  kBenignData.size());
+#endif
+  EXPECT_EQ(kBenignData, printed_data);
+  EXPECT_NE(kMutatedData, printed_data);
+}
+#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
+
 }  // namespace printing
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.