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
Tracker513710926
Fix commit33b843365994 (chromium/src) +128/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
TEST_F
components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
modified
BindLambdaForTesting
components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
modified
ResumableUploadStringRequestTest
components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
modified

Files Changed

  • chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
  • components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
From 33b8433659943f67ac05e4d8e783cb5fc7391198 Mon Sep 17 00:00:00 2001
From: Alex Valadkevich <[email protected]>
Date: Thu, 28 May 2026 06:35:09 -0700
Subject: [PATCH] Defer early-cancel in tabless content analysis dialog

In background or tabless contexts without a page modal dialog manager,
receiving an early fast-track verdict synchronously triggers a
programmatic dialog cancellation. With the newly-implemented
MaybeCancelRequests active, this early cancel synchronously destructs
the uploader object while its own metadata functions are still active on
the call stack, which could lead to a crash.

This CL resolves the lifetime issue by deferring the early-cancel
asynchronously inside ShowDialogNow via a PostTask, allowing the
uploader's execution stack to resolve cleanly before deletion. Unit
tests have also been updated to reflect this deferred uploader
destruction timing.

Bug: 513710926
Change-Id: I0ec6777c6aaae6b297ba2a8faf66413cbf1793f0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876183
Commit-Queue: Fabio Tirelo <[email protected]>
Reviewed-by: Fabio Tirelo <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637656}
---

diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
index 4093e17d..17a0af6 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.cc
@@ -130,7 +130,10 @@
     // accept/cancel the result immediately. See crbug.com/374120523 and
     // crbug.com/388049470 for more context.
     if (!dialog_delegate_->is_pending()) {
-      CancelButtonClicked();
+      content::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(&ContentAnalysisDialogController::CancelButtonClicked,
+                         weak_ptr_factory_.GetWeakPtr()));
     }
     return;
   }
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
index 0e1aeab..bb58bad2 100644
--- a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
+++ b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
@@ -312,6 +312,130 @@
   VerifyMetadataRequestHeaders(std::move(resource_request), "10", "test-token");
 }
 
+TEST_F(ResumableUploadRequestBaseTest, OnMetadataUploadCompleted_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false, base::NullCallback());
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          auto metadata_response_head =
+              network::CreateURLResponseHead(net::HTTP_OK);
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-Status",
+                                                     "active");
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-URL",
+                                                     kUploadUrl);
+          metadata_response_head->headers->AddHeader(
+              "X-Goog-Upload-Header-Cep-Response",
+              GetEncodedContentAnalysisResponse());
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"), std::move(metadata_response_head),
+              "metadata_response", network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
+TEST_F(ResumableUploadRequestBaseTest, Finish_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false, base::NullCallback());
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"),
+              network::CreateURLResponseHead(net::HTTP_UNAUTHORIZED),
+              "unauthorized_response",
+              network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
+TEST_F(ResumableUploadRequestBaseTest, MaybeSendHashAndFinish_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  auto register_on_got_hash_callback = base::BindLambdaForTesting(
+      [&](enterprise_connectors::OnGotHashCallback got_hash_callback) {
+        // Do nothing
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false,
+      std::move(register_on_got_hash_callback));
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          auto metadata_response_head =
+              network::CreateURLResponseHead(net::HTTP_OK);
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-Status",
+                                                     "active");
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-URL",
+                                                     kUploadUrl);
+          metadata_response_head->headers->AddHeader(
+              "X-Goog-Upload-Header-Cep-Response", "");
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"), std::move(metadata_response_head),
+              "metadata_response", network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
 class ResumableUploadStringRequestTest : public ResumableUploadRequestBaseTest {
  private:
   base::test::ScopedFeatureList scoped_feature_list_{{kDlpScanPastedImages}};
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
index 0e1aeab..bb58bad2 100644
--- a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
+++ b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base_unittest.cc
@@ -312,6 +312,130 @@
   VerifyMetadataRequestHeaders(std::move(resource_request), "10", "test-token");
 }
 
+TEST_F(ResumableUploadRequestBaseTest, OnMetadataUploadCompleted_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false, base::NullCallback());
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          auto metadata_response_head =
+              network::CreateURLResponseHead(net::HTTP_OK);
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-Status",
+                                                     "active");
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-URL",
+                                                     kUploadUrl);
+          metadata_response_head->headers->AddHeader(
+              "X-Goog-Upload-Header-Cep-Response",
+              GetEncodedContentAnalysisResponse());
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"), std::move(metadata_response_head),
+              "metadata_response", network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
+TEST_F(ResumableUploadRequestBaseTest, Finish_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false, base::NullCallback());
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"),
+              network::CreateURLResponseHead(net::HTTP_UNAUTHORIZED),
+              "unauthorized_response",
+              network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
+TEST_F(ResumableUploadRequestBaseTest, MaybeSendHashAndFinish_UAF_Test) {
+  base::RunLoop run_loop;
+
+  std::unique_ptr<MockResumableUploadRequestBase> uploader;
+  auto callback = base::BindLambdaForTesting(
+      [&uploader, &run_loop](bool success, int http_status,
+                             const std::string& response_body) {
+        // Asynchronously destroy the uploader to replicate the new service-side
+        // deferred deletion model (to prevent UAF inside active stack).
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&uploader, &run_loop]() {
+              uploader.reset();
+              run_loop.Quit();
+            }));
+      });
+
+  auto register_on_got_hash_callback = base::BindLambdaForTesting(
+      [&](enterprise_connectors::OnGotHashCallback got_hash_callback) {
+        // Do nothing
+      });
+
+  uploader = CreateFileRequest<MockResumableUploadRequestBase>(
+      "file content", ScanRequestUploadResult::kSuccess, std::move(callback),
+      base::DoNothing(), /*force_sync_upload=*/false,
+      std::move(register_on_got_hash_callback));
+
+  test_url_loader_factory_.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        if (request.url == GURL("https://google.com")) {
+          auto metadata_response_head =
+              network::CreateURLResponseHead(net::HTTP_OK);
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-Status",
+                                                     "active");
+          metadata_response_head->headers->AddHeader("X-Goog-Upload-URL",
+                                                     kUploadUrl);
+          metadata_response_head->headers->AddHeader(
+              "X-Goog-Upload-Header-Cep-Response", "");
+          test_url_loader_factory_.AddResponse(
+              GURL("https://google.com"), std::move(metadata_response_head),
+              "metadata_response", network::URLLoaderCompletionStatus(net::OK));
+        }
+      }));
+
+  uploader->Start();
+  run_loop.Run();
+}
+
 class ResumableUploadStringRequestTest : public ResumableUploadRequestBaseTest {
  private:
   base::test::ScopedFeatureList scoped_feature_list_{{kDlpScanPastedImages}};
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.