Overview

Critical
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free DigitalCredentials
DescriptionUse after free DigitalCredentials
ComponentChromium
Sub-componentDC
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker516942828
Fix commit57b6bdd81cd7 (chromium/src) +123/-6
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-27 (per vendor advisory)
Fix landed2026-06-08 (public git)
Disclosed2026-06-11
Patch gap3 days — fix public in source before the advisory

Background

`DigitalIdentityProviderDesktop`
the desktop browser-process implementation that drives the Digital Credentials UI and holds the request callback_ used to report a result back to the renderer-facing request layer.
`DigitalIdentityRequestImpl` / `DocumentService`
the frame-bound Mojo interface implementation that owns the provider; as a DocumentService it is tied to a content::WebContents/frame and is synchronously destroyed when that host goes away, taking the provider with it (per the advisory).
`DigitalIdentityMultiStepDialog` / `dialog_`
the multi-step Views dialog held via dialog_; resetting it synchronously closes the associated views::Widget.
Reentrant destruction
closing a views::Widget can, through activation/WidgetObserver events, synchronously tear down the hosting WebContents, so a call that resets the dialog can delete this before the call returns.

Root Cause Analysis

On the request-failure path, EndRequestWithError() in digital_identity_provider_desktop.cc first called bluetooth_manual_dialog_controller_.reset() and dialog_.reset(), then executed std::move(callback_).Run(base::unexpected(status)). The dialog_.reset() call synchronously destroys the dialog’s views::Widget; when that dialog is hosted in a transient container (such as an extension popup that closes on deactivation), the widget teardown can synchronously destroy the hosting WebContents, which destroys the frame-bound DigitalIdentityRequestImpl DocumentService and therefore the DigitalIdentityProviderDesktop (this). The violated invariant is that this (and its member callback_) must remain alive for the duration of EndRequestWithError(); once the provider is deleted mid-method, the subsequent std::move(callback_).Run(...) reads and moves from a member of freed memory — a browser-process heap use-after-free.

The fix moves the callback into a stack local, auto local_callback = std::move(callback_), before the two reset() calls, and then runs std::move(local_callback).Run(...), so if the provider is synchronously destroyed the callback lives safely on the stack and no freed member is touched.

Key insight
The single mistake is dereferencing an owned member (callback_) after an intervening reset() that can synchronously delete the enclosing object; the fix hoists that member into a stack-local local_callback before the destructive reset() calls so the post-teardown Run() no longer touches freed memory.

Attack Path

  1. Initiate a Digital Credentials request from a transient host A page/frame whose UI is hosted in a container that closes on deactivation (the advisory cites an extension popup) starts a Digital Credentials flow, causing DigitalIdentityProviderDesktop to show the multi-step dialog (e.g. via ShowQrCodeDialog, as the regression test drives it) with callback_ set.
  2. Force the error/cancel path The request is cancelled or otherwise fails so that control reaches EndRequestWithError(); the regression test reproduces this by cancelling the dialog, which posts OnCanceled() -> EndRequestWithError().
  3. Trigger synchronous owner destruction during dialog close Inside EndRequestWithError(), dialog_.reset() closes the views::Widget, whose activation/WidgetObserver events synchronously destroy the hosting WebContents, deleting the DocumentService DigitalIdentityRequestImpl and thus this (the test simulates this precisely with ProviderDestroyerOnWidgetClosingObserver, which resets the provider on OnWidgetClosing).
  4. Use-after-free on the freed provider When control returns, the pre-patch code runs std::move(callback_).Run(...), reading/moving from callback_ inside the freed DigitalIdentityProviderDesktop, which is the browser-process heap UAF.
  5. Escalation (inferred) Turning this UAF read/move into a controlled read/write primitive or code execution in the browser process is not demonstrated by the diff; any such escalation is (inferred).

Impact Assessment

An attacker who can drive a Digital Credentials request from a transient, deactivation-closing host triggers a use-after-free of the DigitalIdentityProviderDesktop object in the browser process, the highest-privilege context in Chrome’s model. The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The advisory classifies this as a UAF in DigitalCredentials but does not report exploitation in the wild or a severity rating in the provided metadata. Escalation from this UAF to a read/write primitive, sandbox-level compromise, or remote code execution is not shown by the patch and is (inferred).

Changed Functions

FunctionChangeNotes
TestDigitalIdentityProviderDesktop
chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
modified
ProviderDestroyerOnWidgetClosingObserver
chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
modified
if
chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
modified

Files Changed

  • chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
  • chrome/browser/digital_credentials/digital_identity_provider_desktop.h
  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc

Audit Directions

  • Member access after reentrant `reset()`/teardown
    Audit any method that calls .reset(), Close(), or destroys a Widget/dialog and then touches this-members afterward; hoist needed state (especially callbacks) onto the stack before the destructive call, since UI teardown can synchronously delete the owning object.
  • `DocumentService`-owned objects running callbacks
    Review other frame-bound DocumentService implementations and the objects they own for spots where a synchronous UI or WebContents teardown can delete the service mid-method before a callback_.Run(...) or similar member dereference.
  • Transient/deactivation-closing UI hosts
    Look at flows whose dialogs may be hosted in extension popups or other containers that close on deactivation, where widget close events reenter and destroy the hosting WebContents during an in-progress browser-process call.
From 57b6bdd81cd758d9a11914b9c28ae51d8fbee529 Mon Sep 17 00:00:00 2001
From: Mohamed Amir Yosef <[email protected]>
Date: Mon, 08 Jun 2026 03:11:49 -0700
Subject: [PATCH] [DC] Fix UAF on dialog close

DigitalIdentityProviderDesktop::EndRequestWithError() resets the multi-
step dialog, which synchronously destroys the associated Views widget.
If the dialog is hosted in a transient container (like an extension
popup) that closes on deactivation, closing the widget can synchronously
destroy the hosting WebContents.

Since the Mojo interface implementation DigitalIdentityRequestImpl is a
frame-bound DocumentService, the WebContents destruction synchronously
deletes it, which in turn deletes the DigitalIdentityProviderDesktop
instance on the stack.

When control returns to EndRequestWithError(), the method attempts to
access and run callback_, which is a member of the now-deleted provider
instance, resulting in a browser-process heap use-after-free (UAF).

This CL fixes the UAF by moving the callback to a local stack variable
before resetting the dialog. If the provider is synchronously destroyed,
the callback resides safely on the stack.

This CL also adds a regression browser test that reproduces the UAF
under a simulated WebContents teardown.

Fixed: 516942828
Test: DigitalIdentityMultiStepDialogBrowserTest.EndRequestWithErrorOwnerDestroyedDuringDialogClose
Change-Id: Ia916e6551a7cda6a0a9e1bfb3cfdbd93482da039
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904663
Reviewed-by: Andrii Natiahlyi <[email protected]>
Commit-Queue: Mohamed Amir Yosef <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1643069}
---

diff --git a/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc b/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
index 6332881..867cd5e60 100644
--- a/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
+++ b/chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
@@ -399,8 +399,19 @@
     return;
   }
 
+  // `dialog_.reset()` can synchronously close the UI which (via activation
+  // observers) may destroy the hosting WebContents, resulting in the
+  // synchronous destruction of the frame-bound Mojo DocumentService
+  // `DigitalIdentityRequestImpl` and therefore `this`.
+  //
+  // To avoid a Use-After-Free, move the callback to a local variable on the
+  // stack before resetting the dialog or the manual bluetooth controller (both
+  // of which could trigger synchronous teardown via UI events).
+  auto local_callback = std::move(callback_);
+
   bluetooth_manual_dialog_controller_.reset();
   dialog_.reset();
+  // `this` may be deleted at this point.
 
-  std::move(callback_).Run(base::unexpected(status));
+  std::move(local_callback).Run(base::unexpected(status));
 }
diff --git a/chrome/browser/digital_credentials/digital_identity_provider_desktop.h b/chrome/browser/digital_credentials/digital_identity_provider_desktop.h
index 1387e29bd..2be6166 100644
--- a/chrome/browser/digital_credentials/digital_identity_provider_desktop.h
+++ b/chrome/browser/digital_credentials/digital_identity_provider_desktop.h
@@ -74,6 +74,13 @@
     callback_ = std::move(callback);
   }
 
+  // Ensures `dialog_` is initialized and returns it.
+  DigitalIdentityMultiStepDialog* EnsureDialogCreated();
+
+  // Called to end the request with an error.
+  void EndRequestWithError(
+      content::DigitalIdentityProvider::RequestStatusForMetrics);
+
  private:
   // Called whenever some significant event occurs during the transaction.
   void OnEvent(const std::string& qr_url,
@@ -90,8 +97,6 @@
       base::expected<content::digital_credentials::cross_device::Response,
                      content::digital_credentials::cross_device::Error>);
 
-  // Ensures `dialog_` is initialized and returns it.
-  DigitalIdentityMultiStepDialog* EnsureDialogCreated();
 
   // Shows dialog which prompts user to manually turn on bluetooth.
   void ShowBluetoothManualTurnOnDialog();
@@ -115,9 +120,6 @@
   // canceling the dialog.
   void OnCanceled();
 
-  // Called to end the request with an error.
-  void EndRequestWithError(
-      content::DigitalIdentityProvider::RequestStatusForMetrics);
 
   // The web contents to which the dialog is modal to.
   base::WeakPtr<content::WebContents> web_contents_;
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
index 30059c9..40ad72c 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/scoped_observation.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/digital_credentials/digital_identity_provider_desktop.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -233,3 +234,106 @@
         ui::mojom::DialogButton::kOk));
   }
 }
+
+namespace {
+
+// Subclass to expose protected methods for testing.
+class TestDigitalIdentityProviderDesktop
+    : public DigitalIdentityProviderDesktop {
+ public:
+  using DigitalIdentityProviderDesktop::EndRequestWithError;
+  using DigitalIdentityProviderDesktop::EnsureDialogCreated;
+  using DigitalIdentityProviderDesktop::set_callback_for_testing;
+  using DigitalIdentityProviderDesktop::set_rp_origin_for_testing;
+  using DigitalIdentityProviderDesktop::set_web_contents_for_testing;
+
+  // Calls the protected ShowQrCodeDialog.
+  void SetUpAndShowQrDialog(content::WebContents* web_contents,
+                            base::OnceClosure callback) {
+    set_web_contents_for_testing(web_contents->GetWeakPtr());
+    set_rp_origin_for_testing(url::Origin::Create(GURL("https://rp.example")));
+    set_callback_for_testing(base::BindOnce(
+        [](base::OnceClosure callback,
+           base::expected<
+               TestDigitalIdentityProviderDesktop::DigitalCredential,
+               content::DigitalIdentityProvider::RequestStatusForMetrics>
+               result) { std::move(callback).Run(); },
+        std::move(callback)));
+    ShowQrCodeDialog("FIDO:/0123456789", RequestInfo::RequestType::kGet);
+  }
+
+  DigitalIdentityMultiStepDialog* GetDialog() { return EnsureDialogCreated(); }
+};
+
+class ProviderDestroyerOnWidgetClosingObserver : public views::WidgetObserver {
+ public:
+  explicit ProviderDestroyerOnWidgetClosingObserver(
+      base::OnceClosure destruction_callback)
+      : destruction_callback_(std::move(destruction_callback)) {}
+
+  void OnWidgetClosing(views::Widget* widget) override {
+    widget->RemoveObserver(this);
+    if (destruction_callback_) {
+      std::move(destruction_callback_).Run();
+    }
+  }
+
+ private:
+  base::OnceClosure destruction_callback_;
+};
+
+}  // namespace
+
+// Regression test for UAF in
+// DigitalIdentityProviderDesktop::EndRequestWithError when the owner is
+// synchronously destroyed during dialog close.
+IN_PROC_BROWSER_TEST_F(DigitalIdentityMultiStepDialogBrowserTest,
+                       EndRequestWithErrorOwnerDestroyedDuringDialogClose) {
+  auto provider = std::make_unique<TestDigitalIdentityProviderDesktop>();
+
+  base::RunLoop run_loop;
+  // Show the dialog via the real ShowQrCodeDialog flow.
+  provider->SetUpAndShowQrDialog(GetActiveWebContents(),
+                                 run_loop.QuitClosure());
+
+  // Retrieve the widget robustly using TestApi and EnsureDialogCreated.
+  // Wrap `TestApi` in a nested scope so it is destroyed before the message
+  // loop runs. Otherwise, when the loop runs and triggers the UAF teardown,
+  // the dialog is deleted, leaving `TestApi` holding a dangling raw_ptr.
+  views::Widget* widget = nullptr;
+  {
+    DigitalIdentityMultiStepDialog* dialog = provider->GetDialog();
+    DigitalIdentityMultiStepDialog::TestApi dialog_test_api(dialog);
+    widget = dialog_test_api.GetWidget();
+  }
+  ASSERT_TRUE(widget);
+  base::WeakPtr<views::Widget> weak_widget = widget->GetWeakPtr();
+
+  // Set up the observer to synchronously destroy the provider when the widget
+  // closes.
+  ProviderDestroyerOnWidgetClosingObserver observer(base::BindOnce(
+      [](std::unique_ptr<TestDigitalIdentityProviderDesktop>* provider) {
+        provider->reset();
+      },
+      base::Unretained(&provider)));
+  widget->AddObserver(&observer);
+
+  // Trigger cancellation. OnDialogCanceled() will PostTask a call to
+  // OnCanceled() -> EndRequestWithError().
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
index 30059c9..40ad72c 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include "base/scoped_observation.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/digital_credentials/digital_identity_provider_desktop.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -233,3 +234,106 @@
         ui::mojom::DialogButton::kOk));
   }
 }
+
+namespace {
+
+// Subclass to expose protected methods for testing.
+class TestDigitalIdentityProviderDesktop
+    : public DigitalIdentityProviderDesktop {
+ public:
+  using DigitalIdentityProviderDesktop::EndRequestWithError;
+  using DigitalIdentityProviderDesktop::EnsureDialogCreated;
+  using DigitalIdentityProviderDesktop::set_callback_for_testing;
+  using DigitalIdentityProviderDesktop::set_rp_origin_for_testing;
+  using DigitalIdentityProviderDesktop::set_web_contents_for_testing;
+
+  // Calls the protected ShowQrCodeDialog.
+  void SetUpAndShowQrDialog(content::WebContents* web_contents,
+                            base::OnceClosure callback) {
+    set_web_contents_for_testing(web_contents->GetWeakPtr());
+    set_rp_origin_for_testing(url::Origin::Create(GURL("https://rp.example")));
+    set_callback_for_testing(base::BindOnce(
+        [](base::OnceClosure callback,
+           base::expected<
+               TestDigitalIdentityProviderDesktop::DigitalCredential,
+               content::DigitalIdentityProvider::RequestStatusForMetrics>
+               result) { std::move(callback).Run(); },
+        std::move(callback)));
+    ShowQrCodeDialog("FIDO:/0123456789", RequestInfo::RequestType::kGet);
+  }
+
+  DigitalIdentityMultiStepDialog* GetDialog() { return EnsureDialogCreated(); }
+};
+
+class ProviderDestroyerOnWidgetClosingObserver : public views::WidgetObserver {
+ public:
+  explicit ProviderDestroyerOnWidgetClosingObserver(
+      base::OnceClosure destruction_callback)
+      : destruction_callback_(std::move(destruction_callback)) {}
+
+  void OnWidgetClosing(views::Widget* widget) override {
+    widget->RemoveObserver(this);
+    if (destruction_callback_) {
+      std::move(destruction_callback_).Run();
+    }
+  }
+
+ private:
+  base::OnceClosure destruction_callback_;
+};
+
+}  // namespace
+
+// Regression test for UAF in
+// DigitalIdentityProviderDesktop::EndRequestWithError when the owner is
+// synchronously destroyed during dialog close.
+IN_PROC_BROWSER_TEST_F(DigitalIdentityMultiStepDialogBrowserTest,
+                       EndRequestWithErrorOwnerDestroyedDuringDialogClose) {
+  auto provider = std::make_unique<TestDigitalIdentityProviderDesktop>();
+
+  base::RunLoop run_loop;
+  // Show the dialog via the real ShowQrCodeDialog flow.
+  provider->SetUpAndShowQrDialog(GetActiveWebContents(),
+                                 run_loop.QuitClosure());
+
+  // Retrieve the widget robustly using TestApi and EnsureDialogCreated.
+  // Wrap `TestApi` in a nested scope so it is destroyed before the message
+  // loop runs. Otherwise, when the loop runs and triggers the UAF teardown,
+  // the dialog is deleted, leaving `TestApi` holding a dangling raw_ptr.
+  views::Widget* widget = nullptr;
+  {
+    DigitalIdentityMultiStepDialog* dialog = provider->GetDialog();
+    DigitalIdentityMultiStepDialog::TestApi dialog_test_api(dialog);
+    widget = dialog_test_api.GetWidget();
+  }
+  ASSERT_TRUE(widget);
+  base::WeakPtr<views::Widget> weak_widget = widget->GetWeakPtr();
+
+  // Set up the observer to synchronously destroy the provider when the widget
+  // closes.
+  ProviderDestroyerOnWidgetClosingObserver observer(base::BindOnce(
+      [](std::unique_ptr<TestDigitalIdentityProviderDesktop>* provider) {
+        provider->reset();
+      },
+      base::Unretained(&provider)));
+  widget->AddObserver(&observer);
+
+  // Trigger cancellation. OnDialogCanceled() will PostTask a call to
+  // OnCanceled() -> EndRequestWithError().
+  static_cast<views::DialogDelegate*>(widget->widget_delegate())
+      ->CancelDialog();
+  ASSERT_FALSE(widget->IsClosed());
+
+  // Run the message loop to execute the posted tasks.
+  // This will run EndRequestWithError(), triggering the UAF if the bug exists.
+  run_loop.Run();
+
+  // Verify that the provider was safely destroyed (pointer is null).
+  EXPECT_FALSE(provider);
+
+  // Clean up if the widget is still alive.
+  if (weak_widget) {
+    weak_widget->RemoveObserver(&observer);
+    views::test::WidgetDestroyedWaiter(weak_widget.get()).Wait();
+  }
+}
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in DigitalIdentityProviderDesktop::EndRequestWithError

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential heap use-after-free (UAF) vulnerability exists in the browser process due to DigitalIdentityProviderDesktop synchronously destroying its multi-step dialog. Under specific conditions—such as when the dialog is modal to a WebContents hosted in a transient container (like an extension popup or bubble) that closes on deactivation—closing the dialog can trigger the synchronous destruction of the hosting WebContents. This results in the synchronous deletion of the DigitalIdentityProviderDesktop instance, leaving a dangling pointer when its member callback is subsequently invoked.

Affected files:

  • chrome/browser/digital_credentials/digital_identity_provider_desktop.cc
  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
  • content/browser/digital_credentials/digital_identity_request_impl.cc

Estimated timestamp from git blame: 2024-08-28

Root Cause Analysis

In DigitalIdentityProviderDesktop::EndRequestWithError, located in chrome/browser/digital_credentials/digital_identity_provider_desktop.cc, the code resets its multi-step dialog and then immediately attempts to move and invoke callback_:

void DigitalIdentityProviderDesktop::EndRequestWithError(
    RequestStatusForMetrics status) {
  if (callback_.is_null()) {
    return;
  }

  bluetooth_manual_dialog_controller_.reset();
  dialog_.reset();                                     // [1]

  std::move(callback_).Run(base::unexpected(status));  // [2] UAF occurs here
}

At [1], dialog_.reset() triggers the destructor of DigitalIdentityMultiStepDialog (chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc), which executes:

DigitalIdentityMultiStepDialog::~DigitalIdentityMultiStepDialog() {
  if (dialog_ && !dialog_->IsClosed()) {
    dialog_->CloseWithReason(GetWidgetDelegate()->get_closed_reason());
  }
}

When the digital identity request is initiated from a transient container (such as an extension popup or bubble that is configured to close upon an activation or focus change), closing the widget via CloseWithReason() fires window deactivation or visibility observers synchronously. This can lead to the synchronous destruction of the hosting WebContents.

Because the Mojo interface implementation DigitalIdentityRequestImpl is a frame-bound DocumentService, the destruction of WebContents synchronously destroys the RenderFrameHostImpl, which in turn deletes the DigitalIdentityRequestImpl instance. Since DigitalIdentityRequestImpl owns DigitalIdentityProviderDesktop, the provider instance this is synchronously deleted on the stack.

When the destructor of DigitalIdentityProviderDesktop finishes, control returns to EndRequestWithError. At [2], the method attempts to access and move callback_, which now resides inside the deallocated memory space of the deleted DigitalIdentityProviderDesktop instance, resulting in a browser-process heap use-after-free (UAF).

Potential Attack Scenario

Since our security evaluation is based on static analysis and we do not have the ability to run functional code, the following sequence represents a potential attack flow:

  1. Renderer Compromise: An attacker compromises a renderer process that has access to a transient/bubble WebContents (such as an extension popup or bubble).
  2. Mojo Call: The compromised renderer calls blink::mojom::DigitalIdentityRequest::Get over the bound Mojo interface, initiating the digital identity credential flow.
  3. Dialog Display: The browser process instantiates DigitalIdentityProviderDesktop and shows the multi-step dialog to the user.
  4. Cancellation Trigger: The attacker triggers a cancellation or abort path (e.g., via Mojo or by driving deactivation events that dismiss the dialog).
  5. UAF Execution: EndRequestWithError is executed. The synchronous closure of the dialog triggers the destruction of the transient container and the DigitalIdentityProviderDesktop instance. The browser process then dereferences callback_ on the deleted instance, causing a UAF crash or potentially leading to remote code execution (RCE) in the privileged browser process.

Suggested Remediation

To prevent the potential use-after-free, move the callback to a local stack variable before resetting the dialog. This ensures that even if this is synchronously destroyed during dialog_.reset(), the callback resides safely on the execution stack and is not accessed via a dangling this pointer:

void DigitalIdentityProviderDesktop::EndRequestWithError(
    RequestStatusForMetrics status) {
  if (callback_.is_null()) {
    return;
  }

  bluetooth_manual_dialog_controller_.reset();
  auto local_callback = std::move(callback_);
  dialog_.reset();

  std::move(local_callback).Run(base::unexpected(status));
}

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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