CVE-2026-13879
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fdevice/bluetooth/socket_unittest.cc |
modified |
Files Changed
device/bluetooth/socket.ccdevice/bluetooth/socket_unittest.cc
Patch
From e940e425d4fadb301240e0c6aa144b15cf96a735 Mon Sep 17 00:00:00 2001 From: Hongchan Choi <[email protected]> Date: Fri, 29 May 2026 17:12:12 -0700 Subject: [PATCH] [Bluetooth] Fix Use-After-Unmap vulnerability in bluetooth::Socket Resolve a Use-After-Unmap vulnerability in Socket::SendMore() by replacing the non-owning net::WrappedIOBuffer with an owning net::IOBufferWithSize for asynchronous socket writes. Previously, Socket::SendMore() wrapped a base::span pointing directly into the Mojo Core shared memory ring buffer with a net::WrappedIOBuffer. Because WrappedIOBuffer is non-owning and the socket write occurs asynchronously on a background socket thread, the buffer's lifetime could exceed the mapping of the Mojo shared memory ring buffer. If the Mojo data pipe was closed or reset before the background socket write completed, Mojo Core synchronously unmapped the shared memory, leaving the background thread with a dangling pointer, resulting in a Use-After-Unmap crash. To resolve this, we replace WrappedIOBuffer with an owning IOBufferWithSize. Before posting the async write operation, the data is copied from the Mojo ring buffer into the newly allocated IOBufferWithSize. Since the IOBufferWithSize owns its heap storage and is ref-counted by the background socket task, the memory remains valid throughout the asynchronous write execution regardless of the Mojo stream or Socket lifecycle. Bug: 499022239 Test: autoninja -C out/Default device_unittests && ./out/Default/device_unittests --gtest_filter='SocketTest.*' --single-process-tests Change-Id: I4c0f2d86c76f31031c2df80343405eb9f42bf916 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7886436 Reviewed-by: Matt Reynolds <[email protected]> Commit-Queue: Hongchan Choi <[email protected]> Cr-Commit-Position: refs/heads/main@{#1638838} --- diff --git a/device/bluetooth/socket.cc b/device/bluetooth/socket.cc index c15adbd..05ba38f 100644 --- a/device/bluetooth/socket.cc +++ b/device/bluetooth/socket.cc @@ -6,15 +6,12 @@ #include <string> #include <utility> -#include <vector> #include "base/containers/span.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/logging.h" -#include "base/memory/ptr_util.h" #include "base/numerics/safe_conversions.h" -#include "base/strings/string_view_util.h" #include "device/bluetooth/bluetooth_socket.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" @@ -169,9 +166,11 @@ return; } - std::string_view chars = base::as_string_view(pending_read_buffer); - bluetooth_socket_->Send(base::MakeRefCounted<net::WrappedIOBuffer>(chars), - chars.size(), + auto io_buffer = + base::MakeRefCounted<net::IOBufferWithSize>(pending_read_buffer.size()); + io_buffer->span().copy_from(pending_read_buffer); + const int buffer_size = io_buffer->size(); + bluetooth_socket_->Send(std::move(io_buffer), buffer_size, base::BindOnce(&Socket::OnBluetoothSocketSend, weak_ptr_factory_.GetWeakPtr()), base::BindOnce(&Socket::OnBluetoothSocketSendError, diff --git a/device/bluetooth/socket_unittest.cc b/device/bluetooth/socket_unittest.cc index 5a0e4aac..08a15f9 100644 --- a/device/bluetooth/socket_unittest.cc +++ b/device/bluetooth/socket_unittest.cc @@ -202,4 +202,29 @@ WriteAndVerifySend("message_6", /*success=*/true); } +TEST_F(SocketTest, TestSend_ClosedDuringSend) { + EXPECT_FALSE(send_stream_->QuerySignalsState().never_writable()); + EXPECT_FALSE(fake_bluetooth_socket_->HasSendArgs()); + + std::string message = "test_message"; + size_t actually_written_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, + send_stream_->WriteData(base::as_byte_span(message), + MOJO_WRITE_DATA_FLAG_NONE, + actually_written_bytes)); + EXPECT_EQ(message.size(), actually_written_bytes); + + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(fake_bluetooth_socket_->HasSendArgs()); + auto send_args = fake_bluetooth_socket_->TakeSendArgs(); + + send_stream_.reset(); + + base::RunLoop().RunUntilIdle(); + + auto success_callback = std::move(std::get<2>(*send_args)); + std::move(success_callback).Run(/*num_bytes_sent=*/message.size()); +} + } // namespace bluetooth
Regression Test / PoC
diff --git a/device/bluetooth/socket_unittest.cc b/device/bluetooth/socket_unittest.cc
index 5a0e4aac..08a15f9 100644
--- a/device/bluetooth/socket_unittest.cc
+++ b/device/bluetooth/socket_unittest.cc
@@ -202,4 +202,29 @@
WriteAndVerifySend("message_6", /*success=*/true);
}
+TEST_F(SocketTest, TestSend_ClosedDuringSend) {
+ EXPECT_FALSE(send_stream_->QuerySignalsState().never_writable());
+ EXPECT_FALSE(fake_bluetooth_socket_->HasSendArgs());
+
+ std::string message = "test_message";
+ size_t actually_written_bytes = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ send_stream_->WriteData(base::as_byte_span(message),
+ MOJO_WRITE_DATA_FLAG_NONE,
+ actually_written_bytes));
+ EXPECT_EQ(message.size(), actually_written_bytes);
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(fake_bluetooth_socket_->HasSendArgs());
+ auto send_args = fake_bluetooth_socket_->TakeSendArgs();
+
+ send_stream_.reset();
+
+ base::RunLoop().RunUntilIdle();
+
+ auto success_callback = std::move(std::get<2>(*send_args));
+ std::move(success_callback).Run(/*num_bytes_sent=*/message.size());
+}
+
} // namespace bluetooth
Original Bug Report
Potential Use-After-Unmap Information Leak via Bluetooth Socket Send
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 security team.
Overview: A Use-After-Unmap vulnerability exists in device/bluetooth/Socket when sending data over a Bluetooth socket. The browser wraps a Mojo shared memory pointer in a non-owning net::WrappedIOBuffer and passes it to an asynchronous socket thread, leading to a potential information leak if the Mojo data pipe is closed while the write is pending.
Affected files:
device/bluetooth/socket.ccdevice/bluetooth/bluetooth_socket_net.cc
Estimated timestamp from git blame: 2024-06-13
Mechanism
In device/bluetooth/socket.cc, Socket::SendMore() reads outgoing data from a Mojo data pipe. It calls send_stream_->BeginReadData(), which returns a base::span pointing directly into Mojo Core’s underlying shared memory ring buffer.
This span is then wrapped in a net::WrappedIOBuffer and passed to bluetooth_socket_->Send() for asynchronous transmission:
void Socket::SendMore() {
// ...
MojoResult result = send_stream_->BeginReadData(MOJO_WRITE_DATA_FLAG_NONE, pending_read_buffer);
// ...
std::string_view chars = base::as_string_view(pending_read_buffer);
bluetooth_socket_->Send(base::MakeRefCounted<net::WrappedIOBuffer>(chars),
chars.size(), ...);
}
net::WrappedIOBuffer is explicitly designed as a non-owning wrapper and is documented as “ONLY safe for synchronous use.” However, on platforms using BluetoothSocketNet (such as Linux and ChromeOS), the Send() method queues the request and posts a task to a background socket_thread_ to perform the blocking I/O.
The Vulnerability
Because the WrappedIOBuffer is passed across threads for an asynchronous operation, its lifetime outlives the synchronous scope of SendMore(). If the Mojo data pipe consumer handle (send_stream_) is reset before the background thread completes the socket write, Mojo Core will immediately and synchronously unmap (munmap) the shared memory ring buffer from the browser’s address space.
Because this memory is allocated via mmap rather than PartitionAlloc, it is not protected by MiraclePtr (BackupRefPtr). The background socket_thread_ retains a dangling pointer inside the WrappedIOBuffer parked in net::SocketPosix::write_buf_.
Potential Attack Scenario
- An attacker compromises a process with access to
device::mojom::Adapter(e.g., the utility process handling Nearby Share on ChromeOS). - The attacker uses
ConnectToServiceInsecurelyto establish a Bluetooth connection to an attacker-controlled remote device, yielding amojom::Socketand its associated data pipes. - The attacker writes up to 64KB (the default pipe capacity) into the send stream.
- The attacker manipulates the remote Bluetooth device to stall L2CAP/RFCOMM network acknowledgments. This fills the kernel’s send buffer, causing the browser’s background
socket_thread_to hitEWOULDBLOCKduring thesend()call.net::SocketPosixstores theWrappedIOBufferinwrite_buf_and waits for the socket to become writable. - The compromised utility process closes its end of the Mojo send stream.
- The browser’s UI thread processes the closure, calls
ShutdownSend(), and resetssend_stream_. This causes Mojo Core to synchronouslymunmapthe 64KB shared memory ring buffer. - The attacker sprays the browser’s virtual address space (e.g., via other IPC channels) to reallocate the newly unmapped 64KB block with sensitive browser memory.
- The attacker’s remote device resumes sending acknowledgments, unblocking the socket.
- The
socket_thread_wakes up and re-attempts thesend()call using the danglingWrappedIOBuffer, reading up to 64KB of the reallocated, sensitive memory and transmitting it to the attacker’s Bluetooth device.
Suggested Fix
To fix this issue, Socket::SendMore() should not use net::WrappedIOBuffer for asynchronous operations. The data obtained from BeginReadData() should be copied into an owning buffer type before being passed to bluetooth_socket_->Send().
Alternatively, if avoiding a copy is strictly necessary, Socket::SendMore() should use a specialized adapter (similar to network::MojoToNetIOBuffer) that takes ownership of a MojoHandle to ensure the Mojo memory remains mapped until the IO buffer is destroyed.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
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.