CVE-2026-4456
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/digital_credentials/cross_device_transaction_impl.cc |
modified | |
TEST_Pcontent/browser/digital_credentials/cross_device_transaction_impl_unittest.cc |
modified |
Files Changed
content/browser/digital_credentials/cross_device_transaction_impl.cccontent/browser/digital_credentials/cross_device_transaction_impl_unittest.cc
Patch
From 952d06969915c8e44f9ab6008be6f14f6338901e Mon Sep 17 00:00:00 2001 From: Mohamed Amir Yosef <[email protected]> Date: Thu, 12 Mar 2026 02:51:15 -0700 Subject: [PATCH] [DC] Fix crash when Bluetooth is disabled during a DC flow When performing a Digital Credential Request, Chrome crashes if the Bluetooth adapter is powered off while a transaction is in progress. This happens because TransactionImpl (a BluetoothAdapter::Observer) synchronously executes its completion callback upon receiving the AdapterPoweredChanged(false) notification. The completion callback synchronously destroys the DigitalIdentityProvider and the TransactionImpl itself. Destroying an observer while the BluetoothAdapter is still iterating over its ObserverList causes a use-after-free or re-entrancy crash. This CL fixes the issue by posting a task to execute the completion callback asynchronously, which is the standard pattern for preventing synchronous destruction in observer notifications. This is a follow-up to https://crrev.com/c/7132078 which fixed multiple error cases when the bluetooth permission is denied ...etc but overlooked one scenario when the Bluetooth adapter gets turned off during a transaction. Fixed: 488617440 Change-Id: If78d9d3b5cb8d27b4bcaad48ba8850da2ebae0b5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7656824 Commit-Queue: Mohamed Amir Yosef <[email protected]> Reviewed-by: Martin Kreichgauer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1598301} --- diff --git a/content/browser/digital_credentials/cross_device_transaction_impl.cc b/content/browser/digital_credentials/cross_device_transaction_impl.cc index 7d9e81aa..83faad0 100644 --- a/content/browser/digital_credentials/cross_device_transaction_impl.cc +++ b/content/browser/digital_credentials/cross_device_transaction_impl.cc @@ -127,7 +127,9 @@ } if (!powered && !waiting_for_power_) { FIDO_LOG(EVENT) << "Lost BLE power during digital identity transaction."; - std::move(callback_).Run(base::unexpected(SystemError::kLostPower)); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback_), + base::unexpected(SystemError::kLostPower))); return; } if (powered && waiting_for_power_) { diff --git a/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc b/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc index d09bb92..016e425 100644 --- a/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc +++ b/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc @@ -73,10 +73,24 @@ static base::Value request() { base::DictValue request_value; request_value.Set("foo", "bar"); - return base::Value(std::move(request)); + return base::Value(std::move(request_value)); } RequestInfo::RequestType request_type() { return GetParam(); } + void CreateTransaction() { + transaction_ = Transaction::New( + RequestInfo(request_type(), origin(), request()), qr_generator_key(), + network_context_factory(), event_callback_.GetRepeatingCallback(), + base::BindOnce( + &DigitalIdentityCrossDeviceTransactionTest::OnTransactionDone, + base::Unretained(this))); + } + + void OnTransactionDone(base::expected<Response, Error> result) { + transaction_.reset(); + callback_.SetValue(std::move(result)); + } + static std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key() { std::array<uint8_t, device::cablev2::kQRKeySize> key = {0}; return key; @@ -94,23 +108,20 @@ base::test::TestFuture<base::expected<Response, Error>> callback_; base::test::TestFuture<Event> event_callback_; base::test::TaskEnvironment task_environment_; + std::unique_ptr<Transaction> transaction_; }; TEST_P(DigitalIdentityCrossDeviceTransactionTest, NoBle) { bluetooth_values_for_testing_->SetLESupported(false); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), base::DoNothing(), callback_.GetCallback()); + CreateTransaction(); EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kNoBleSupport)); } TEST_P(DigitalIdentityCrossDeviceTransactionTest, NoAdapter) { EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(false)); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), base::DoNothing(), callback_.GetCallback()); + CreateTransaction(); // Callback should not have been called synchronously. EXPECT_FALSE(callback_.IsReady()); EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kNoBleSupport)); @@ -122,9 +133,7 @@ .WillRepeatedly( Return(device::BluetoothAdapter::PermissionStatus::kDenied)); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), base::DoNothing(), callback_.GetCallback()); + CreateTransaction(); EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kPermissionDenied)); } @@ -135,14 +144,11 @@ Return(device::BluetoothAdapter::PermissionStatus::kAllowed)); EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(false)); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), event_callback_.GetRepeatingCallback(), - callback_.GetCallback()); + CreateTransaction(); EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kBluetoothNotPowered)); - reinterpret_cast<TransactionImpl*>(transaction.get()) + reinterpret_cast<TransactionImpl*>(transaction_.get()) ->AdapterPoweredChanged(nullptr, /*powered=*/true); EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kReady)); @@ -165,10 +171,7 @@ device::BluetoothAdapter::RequestSystemPermissionCallback callback) { permission_callback = std::move(callback); }); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), event_callback_.GetRepeatingCallback(), - callback_.GetCallback()); + CreateTransaction(); EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kNeedPermission)); @@ -181,7 +184,7 @@ TEST_P(DigitalIdentityCrossDeviceTransactionTest, NeedPermissionThenGranted) { EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_adapter_, GetOsPermissionStatus) - .WillOnce( + .WillRepeatedly( Return(device::BluetoothAdapter::PermissionStatus::kUndetermined)); EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(true)); @@ -192,10 +195,7 @@ device::BluetoothAdapter::RequestSystemPermissionCallback callback) { permission_callback = std::move(callback); }); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), event_callback_.GetRepeatingCallback(), - callback_.GetCallback()); + CreateTransaction(); EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kNeedPermission)); @@ -209,20 +209,23 @@ BleTurnedOffDuringTransaction) { EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_adapter_, GetOsPermissionStatus) - .WillOnce(Return(device::BluetoothAdapter::PermissionStatus::kAllowed)); + .WillRepeatedly( + Return(device::BluetoothAdapter::PermissionStatus::kAllowed)); EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(true)); - std::unique_ptr<Transaction> transaction = Transaction::New( - RequestInfo(request_type(), origin(), request()), qr_generator_key(), - network_context_factory(), event_callback_.GetRepeatingCallback(), - callback_.GetCallback()); + CreateTransaction(); EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kReady)); - reinterpret_cast<TransactionImpl*>(transaction.get()) - ->AdapterPoweredChanged(nullptr, /*powered=*/false); + reinterpret_cast<TransactionImpl*>(transaction_.get()) + ->AdapterPoweredChanged(mock_adapter_.get(), /*powered=*/false); + + // The callback should not have been run yet because it was posted to the task + // runner. This verifies the fix for the synchronous destruction crash. + EXPECT_FALSE(callback_.IsReady()); EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kLostPower)); + EXPECT_EQ(transaction_, nullptr); } INSTANTIATE_TEST_SUITE_P(,
Regression Test / PoC
diff --git a/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc b/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc
index d09bb92..016e425 100644
--- a/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc
+++ b/content/browser/digital_credentials/cross_device_transaction_impl_unittest.cc
@@ -73,10 +73,24 @@
static base::Value request() {
base::DictValue request_value;
request_value.Set("foo", "bar");
- return base::Value(std::move(request));
+ return base::Value(std::move(request_value));
}
RequestInfo::RequestType request_type() { return GetParam(); }
+ void CreateTransaction() {
+ transaction_ = Transaction::New(
+ RequestInfo(request_type(), origin(), request()), qr_generator_key(),
+ network_context_factory(), event_callback_.GetRepeatingCallback(),
+ base::BindOnce(
+ &DigitalIdentityCrossDeviceTransactionTest::OnTransactionDone,
+ base::Unretained(this)));
+ }
+
+ void OnTransactionDone(base::expected<Response, Error> result) {
+ transaction_.reset();
+ callback_.SetValue(std::move(result));
+ }
+
static std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key() {
std::array<uint8_t, device::cablev2::kQRKeySize> key = {0};
return key;
@@ -94,23 +108,20 @@
base::test::TestFuture<base::expected<Response, Error>> callback_;
base::test::TestFuture<Event> event_callback_;
base::test::TaskEnvironment task_environment_;
+ std::unique_ptr<Transaction> transaction_;
};
TEST_P(DigitalIdentityCrossDeviceTransactionTest, NoBle) {
bluetooth_values_for_testing_->SetLESupported(false);
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), base::DoNothing(), callback_.GetCallback());
+ CreateTransaction();
EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kNoBleSupport));
}
TEST_P(DigitalIdentityCrossDeviceTransactionTest, NoAdapter) {
EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(false));
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), base::DoNothing(), callback_.GetCallback());
+ CreateTransaction();
// Callback should not have been called synchronously.
EXPECT_FALSE(callback_.IsReady());
EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kNoBleSupport));
@@ -122,9 +133,7 @@
.WillRepeatedly(
Return(device::BluetoothAdapter::PermissionStatus::kDenied));
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), base::DoNothing(), callback_.GetCallback());
+ CreateTransaction();
EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kPermissionDenied));
}
@@ -135,14 +144,11 @@
Return(device::BluetoothAdapter::PermissionStatus::kAllowed));
EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(false));
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), event_callback_.GetRepeatingCallback(),
- callback_.GetCallback());
+ CreateTransaction();
EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kBluetoothNotPowered));
- reinterpret_cast<TransactionImpl*>(transaction.get())
+ reinterpret_cast<TransactionImpl*>(transaction_.get())
->AdapterPoweredChanged(nullptr, /*powered=*/true);
EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kReady));
@@ -165,10 +171,7 @@
device::BluetoothAdapter::RequestSystemPermissionCallback
callback) { permission_callback = std::move(callback); });
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), event_callback_.GetRepeatingCallback(),
- callback_.GetCallback());
+ CreateTransaction();
EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kNeedPermission));
@@ -181,7 +184,7 @@
TEST_P(DigitalIdentityCrossDeviceTransactionTest, NeedPermissionThenGranted) {
EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(true));
EXPECT_CALL(*mock_adapter_, GetOsPermissionStatus)
- .WillOnce(
+ .WillRepeatedly(
Return(device::BluetoothAdapter::PermissionStatus::kUndetermined));
EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(true));
@@ -192,10 +195,7 @@
device::BluetoothAdapter::RequestSystemPermissionCallback
callback) { permission_callback = std::move(callback); });
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), event_callback_.GetRepeatingCallback(),
- callback_.GetCallback());
+ CreateTransaction();
EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kNeedPermission));
@@ -209,20 +209,23 @@
BleTurnedOffDuringTransaction) {
EXPECT_CALL(*mock_adapter_, IsPresent).WillRepeatedly(Return(true));
EXPECT_CALL(*mock_adapter_, GetOsPermissionStatus)
- .WillOnce(Return(device::BluetoothAdapter::PermissionStatus::kAllowed));
+ .WillRepeatedly(
+ Return(device::BluetoothAdapter::PermissionStatus::kAllowed));
EXPECT_CALL(*mock_adapter_, IsPowered).WillRepeatedly(Return(true));
- std::unique_ptr<Transaction> transaction = Transaction::New(
- RequestInfo(request_type(), origin(), request()), qr_generator_key(),
- network_context_factory(), event_callback_.GetRepeatingCallback(),
- callback_.GetCallback());
+ CreateTransaction();
EXPECT_EQ(event_callback_.Take(), Event(SystemEvent::kReady));
- reinterpret_cast<TransactionImpl*>(transaction.get())
- ->AdapterPoweredChanged(nullptr, /*powered=*/false);
+ reinterpret_cast<TransactionImpl*>(transaction_.get())
+ ->AdapterPoweredChanged(mock_adapter_.get(), /*powered=*/false);
+
+ // The callback should not have been run yet because it was posted to the task
+ // runner. This verifies the fix for the synchronous destruction crash.
+ EXPECT_FALSE(callback_.IsReady());
EXPECT_THAT(callback_.Take(), ContainsError(SystemError::kLostPower));
+ EXPECT_EQ(transaction_, nullptr);
}
INSTANTIATE_TEST_SUITE_P(,
Original Bug Report
Browser Process Heap-Use-After-Free in Digital Credentials API (Renderer → Browser Memory Corruption)
Report description
Browser Process Heap-Use-After-Free in Digital Credentials API (Renderer → Browser Memory Corruption)
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
content/browser/webid/digital_credentials/cross_device_transaction_impl.cc
The problem
Please describe the technical details of the vulnerability
Summary
A Heap Use-After-Free vulnerability exists in the Browser Process within the Digital Credentials implementation.
A regression changed error callback execution from asynchronous to synchronous. This allows a TransactionImpl object to be destroyed while a background worker thread is still executing inside its context.
This results in:
- Cross-thread lifetime violation
- Use-after-free write in Browser Process memory
- Deterministic heap corruption
- Renderer → Browser security boundary violation
The crash is reproducible and confirmed in ASAN-instrumented Chromium builds.
Affected Component
content/browser/webid/digital_credentials/
Specifically:
cross_device_transaction_impl.cc
Tested Version:
141.0.7369.0 (Windows x64)
Vulnerability Type
- Heap Use-After-Free
- Cross-thread object lifetime violation
- Browser Process memory corruption
Root Cause Analysis
In cross_device_transaction_impl.cc, error handling previously dispatched callbacks asynchronously via PostTask.
The regression changed this to synchronous execution:
// Vulnerable pattern
std::move(callback_).Run(base::unexpected(error));
Lifetime Sequence
callback_is owned byDigitalIdentityRequestImpl- Synchronous
.Run()immediately triggers parent cleanup - Cleanup calls
provider_.reset() provider_ownsTransactionImplTransactionImplis destroyed- Background worker thread is still executing inside
TransactionImpl - Worker resumes execution on freed memory
This creates a classic cross-thread Use-After-Free condition in the Browser Process.
Technical Evidence
ASAN Crash
this is the current crash report "
KEY_VALUES_STRING: 1
Key : Analysis.CPU.mSec
Value: 1484
Key : Analysis.Elapsed.mSec
Value: 7383
Key : Analysis.IO.Other.Mb
Value: 0
Key : Analysis.IO.Read.Mb
Value: 1
Key : Analysis.IO.Write.Mb
Value: 0
Key : Analysis.Init.CPU.mSec
Value: 1218
Key : Analysis.Init.Elapsed.mSec
Value: 6072
Key : Analysis.Memory.CommitPeak.Mb
Value: 2001
Key : Analysis.Version.DbgEng
Value: 10.0.29507.1001
Key : Analysis.Version.Description
Value: 10.2511.5.1 amd64fre
Key : Analysis.Version.Ext
Value: 1.2511.5.1
Key : Failure.Bucket
Value: APPLICATION_FAULT_AVRF_517a7ed_chrome_elf.dll!Unknown
Key : Failure.Exception.Code
Value: 0x517a7ed
Key : Failure.Exception.IP.Address
Value: 0x7ffdd1f5de62
Key : Failure.Exception.IP.Module
Value: chrome_elf
Key : Failure.Exception.IP.Offset
Value: 0x1ade62
Key : Failure.Hash
Value: {83d3484b-61a5-7688-5a33-ec9bb1799d38}
Key : Failure.ProblemClass.Primary
Value: APPLICATION_FAULT
Key : Faulting.IP.Type
Value: Paged
Key : Timeline.Process.Start.DeltaSec
Value: 250
Key : WER.Process.Version
Value: 141.0.7369.0
FILE_IN_CAB: 3479e8e4-5242-4370-96e4-baa39fd3a88f.dmp
NTGLOBALFLAG: 2000000
APPLICATION_VERIFIER_LOADED: 1
CONTEXT: (.ecxr)
rax=000002193723fdbc rbx=00000095be5ff340 rcx=00000095be5fede0
rdx=0000000000000004 rsi=0000000000000000 rdi=00000012b7cbfdb8
rip=00007ffdd1f5de62 rsp=00000095be5feda0 rbp=00000095be5ff370
r8=0000000000000096 r9=00000000000000a0 r10=00007ffe7a400000
r11=00007ffe7a4ed775 r12=000002067f580000 r13=00000095be5fedc0
r14=00000095be5fede0 r15=000002193723fdbc
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=0000 ds=0000 es=0000 fs=0053 gs=002b efl=00000202
chrome_elf!GetHandleVerifier+0xdd672:
00007ffd`d1f5de62 4c89f1 mov rcx,r14
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ffdd1f5de62 (chrome_elf!GetHandleVerifier+0x00000000000dd672)
ExceptionCode: 0517a7ed
ExceptionFlags: 00000000
NumberParameters: 0
PROCESS_NAME: chrome.exe
ERROR_CODE: (NTSTATUS) 0x517a7ed - <Unable to get error code text>
EXCEPTION_CODE_STR: 517a7ed
STACK_TEXT:
00000095`be5feda0 00007ffd`57be5cbc : 00000000`45e0360e 00000095`be5ff560 00000095`be5ff5b0 00000000`00000001 : chrome_elf!GetHandleVerifier+0xdd672
00000095`be5ff3c0 00007ffd`5797a7e0 : 00000095`be5ff640 00001214`7f954c80 00000000`00000000 00007ffd`57924382 : chrome!GetHandleVerifier+0x3655dc
00000095`be5ff600 00007ffd`5797d5c4 : 00000000`00000000 00000095`be5ff980 00000095`be5ffa10 00000000`00000000 : chrome!GetHandleVerifier+0xfa100
00000095`be5ff720 00007ffd`5797a5c4 : 00000095`be5ff900 00007ffd`446b34b2 00000095`be5ffb60 00000000`0000000b : chrome!GetHandleVerifier+0xfcee4
00000095`be5ffa70 00007ffd`5797a219 : 00000000`00000000 00000095`be5ffb60 00000095`be5ffbe0 00000206`7f580000 : chrome!GetHandleVerifier+0xf9ee4
00000095`be5ffae0 00007ffd`5796c160 : 00000000`00000000 00000000`00000000 00000000`00000246 00007ffd`57964e8f : chrome!GetHandleVerifier+0xf9b39
00000095`be5ffc40 00007ffd`578a55b4 : 00000000`00000000 00000000`00000000 00000000`00000002 00000000`00000000 : chrome!GetHandleVerifier+0xeba80
00000095`be5ffd20 00007ffd`ccdbb18d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : chrome!GetHandleVerifier+0x24ed4
00000095`be5ffe30 00007ffe`7b53e8d7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : clang_rt_asan_dynamic_x86_64!_asan_wrap_CreateThread+0x14d
00000095`be5ffe70 00007ffe`7d2ac40c : 00000000`00000000 00000000`00000000 000004f0`fffffb30 000004d0`fffffb30 : KERNEL32!BaseThreadInitThunk+0x17
00000095`be5ffea0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x2c
STACK_COMMAND: ~3s; .ecxr ; kb
IP_IN_PAGED_CODE:
chrome_elf!GetHandleVerifier+dd672
00007ffd`d1f5de62 4c89f1 mov rcx,r14
SYMBOL_NAME: chrome_elf+1ade62
MODULE_NAME: chrome_elf
IMAGE_NAME: chrome_elf.dll
FAILURE_BUCKET_ID: APPLICATION_FAULT_AVRF_517a7ed_chrome_elf.dll!Unknown
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
IMAGE_VERSION: 141.0.7369.0
FAILURE_ID_HASH: {83d3484b-61a5-7688-5a33-ec9bb1799d38}
Followup: MachineOwner
---------"
Exception:
ExceptionCode: 0x517a7ed (ASAN security trap)
Process: chrome.exe (Browser Process)
Thread: Background worker thread
Register state at crash:
rax = 000002193723fdbc
r15 = 000002193723fdbc
Previously observed faulting instruction:
mov qword ptr [r13+r15], rax
This indicates:
- Attacker-influenced value written
- Write target derived from freed object memory
- ASAN confirms the region was already freed and poisoned
Subsequent crash observed in:
chrome_elf!GetHandleVerifier
clang_rt_asan_dynamic_x86_64
This is consistent with allocator metadata corruption caused by a UAF write.
Security Boundary Impact
- Triggered from renderer context
- Corrupts memory in privileged Browser Process
- Violates renderer sandbox boundary
This qualifies as:
Renderer → Browser Process memory corruption.
Browser-process UAF vulnerabilities are considered high severity because they affect privileged code execution context.
Reproduction Steps
- I used win32-release_x64-media_asan-win32-release_x64-1504065.
- Launch chromium from chromium folder with this flag
chrome.exe --enable-features=FedCmDigitalIdentity - Load the attached
poc.html. - Ensure Bluetooth is enabled.
- Trigger the Digital Credentials request.
- Immediately disable Bluetooth.
- Observe crash in Browser Process (not renderer).
The crash is reproducible and deterministic under ASAN.
Exploitability Discussion
The vulnerability provides:
- Use-after-free write primitive
- Occurs in privileged Browser Process
- Reachable from web content
Suggested Fix
Restore asynchronous dispatch of the error callback:
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), base::unexpected(error))
);
This ensures:
TransactionImpldestructor runs after stack unwinds- Worker thread cannot resume inside freed object
- Object lifetime semantics are preserved
Impact analysis
Impact
- Memory Corruption: Yes
- Use-After-Free: Yes
- Browser Process: Yes
- Renderer → Browser Boundary: Yes
- Remote Triggerable: Yes
This represents high-severity memory corruption in a privileged process.
The cause
What version of Chrome have you found the security issue in?
chromium 141.0.7369.0, chrome Version 145.0.7632.117 (Official Build) (64-bit)
Is the security issue related to a crash?
Yes, it is related to a crash.
Choose the type of vulnerability
Memory Corruption
How would you like to be publicly acknowledged for your report?
sean wong