Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker427681143
Fix commit0ccc849d2027 (chromium/src) +154/-46
CISA KEVNot listed
Creditedjakebiles
Disclosed2025-07-15

Changed Functions

FunctionChangeNotes
if
services/network/p2p/socket_tcp.cc
modified
while
services/network/p2p/socket_tcp.cc
modified

Files Changed

  • services/network/p2p/socket_tcp.cc
From 0ccc849d2027de75122e5b187dc99bdf1cc24edf Mon Sep 17 00:00:00 2001
From: Harald Alvestrand <[email protected]>
Date: Wed, 02 Jul 2025 15:15:22 -0700
Subject: [PATCH] Harden P2PSocketTcpBase::SendBatch against errors

This change ensures that even if errors occur that cause the socket
to be deleted during packet processing, SendBatch will terminate
correctly.

Bug: 427681143
Change-Id: I41da6789c8aab44b31c00c6a7844cd86b918561c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6694889
Reviewed-by: Sergey Ulanov <[email protected]>
Commit-Queue: Harald Alvestrand <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1481925}
---

diff --git a/services/network/p2p/socket_tcp.cc b/services/network/p2p/socket_tcp.cc
index ba68bdcf..827ea38 100644
--- a/services/network/p2p/socket_tcp.cc
+++ b/services/network/p2p/socket_tcp.cc
@@ -73,13 +73,13 @@
 
 P2PSocketTcpBase::~P2PSocketTcpBase() = default;
 
-void P2PSocketTcpBase::InitAccepted(const net::IPEndPoint& remote_address,
+bool P2PSocketTcpBase::InitAccepted(const net::IPEndPoint& remote_address,
                                     std::unique_ptr<net::StreamSocket> socket) {
   DCHECK(socket);
   remote_address_.ip_address = remote_address;
   // TODO(ronghuawu): Add FakeSSLServerSocket.
   socket_ = std::move(socket);
-  DoRead();
+  return DoRead();
 }
 
 void P2PSocketTcpBase::Init(
@@ -128,15 +128,18 @@
   DCHECK_NE(result, net::ERR_IO_PENDING);
 
   if (result != net::OK) {
-    LOG(WARNING) << "Error from connecting socket, result=" << result;
+    LOG(WARNING) << "Error from connecting socket, result=" << result
+                 << ", destroying socket";
     OnError();
     return;
   }
 
-  OnOpen();
+  if (!OnOpen()) {
+    LOG(ERROR) << "Socket destroyed in OnConnected/OnOpen";
+  }
 }
 
-void P2PSocketTcpBase::OnOpen() {
+bool P2PSocketTcpBase::OnOpen() {
   // Setting socket send and receive buffer size.
   if (net::OK != socket_->SetReceiveBufferSize(kTcpRecvSocketBufferSize)) {
     LOG(WARNING) << "Failed to set socket receive buffer size to "
@@ -149,9 +152,9 @@
   }
 
   if (!DoSendSocketCreateMsg())
-    return;
+    return false;
 
-  DoRead();
+  return DoRead();
 }
 
 bool P2PSocketTcpBase::DoSendSocketCreateMsg() {
@@ -198,7 +201,7 @@
   return true;
 }
 
-void P2PSocketTcpBase::DoRead() {
+bool P2PSocketTcpBase::DoRead() {
   while (true) {
     if (!read_buffer_.get()) {
       read_buffer_ = base::MakeRefCounted<net::GrowableIOBuffer>();
@@ -214,14 +217,23 @@
     const int result = socket_->Read(
         read_buffer_.get(), read_buffer_->RemainingCapacity(),
         base::BindOnce(&P2PSocketTcp::OnRead, base::Unretained(this)));
-    if (result == net::ERR_IO_PENDING || !HandleReadResult(result))
-      return;
+    if (result == net::ERR_IO_PENDING) {
+      return true;  // not finished, but blocked
+    }
+    if (!HandleReadResult(result)) {
+      return false;  // error, socket deleted
+    }
   }
 }
 
 void P2PSocketTcpBase::OnRead(int result) {
-  if (HandleReadResult(result))
-    DoRead();
+  if (!HandleReadResult(result)) {
+    LOG(ERROR) << "OnRead/HandleReadResult reports socket destroyed";
+    return;
+  }
+  if (!DoRead()) {
+    LOG(ERROR) << "OnRead/DoRead reports socket destroyed";
+  }
 }
 
 bool P2PSocketTcpBase::OnPacket(base::span<const uint8_t> data) {
@@ -262,17 +274,17 @@
   return true;
 }
 
-void P2PSocketTcpBase::WriteOrQueue(SendBuffer& send_buffer) {
+bool P2PSocketTcpBase::WriteOrQueue(SendBuffer& send_buffer) {
   if (write_buffer_.buffer.get()) {
     write_queue_.push(send_buffer);
-    return;
+    return true;
   }
 
   write_buffer_ = send_buffer;
-  DoWrite();
+  return DoWrite();
 }
 
-void P2PSocketTcpBase::DoWrite() {
+bool P2PSocketTcpBase::DoWrite() {
   while (!write_pending_ && write_buffer_.buffer.get()) {
     int result = socket_->Write(
         write_buffer_.buffer.get(), write_buffer_.buffer->BytesRemaining(),
@@ -282,9 +294,10 @@
     if (result == net::ERR_IO_PENDING) {
       write_pending_ = true;
     } else if (!HandleWriteResult(result)) {
-      break;
+      return false;  // Error, socket is destroyed.
     }
   }
+  return true;
 }
 
 void P2PSocketTcpBase::OnWritten(int result) {
@@ -293,8 +306,13 @@
 
   write_pending_ = false;
 
-  if (HandleWriteResult(result))
-    DoWrite();
+  if (!HandleWriteResult(result)) {
+    LOG(ERROR) << "Socket destroyed in OnWritten/HandleWriteResult";
+    return;
+  }
+  if (!DoWrite()) {
+    LOG(ERROR) << "Socket destroyed in OnWritten/DoWrite";
+  }
 }
 
 bool P2PSocketTcpBase::HandleWriteResult(int result) {
@@ -373,13 +391,14 @@
     }
   }
 
-  DoSend(packet_info.destination, data, packet_info.packet_options);
-  return true;
+  return DoSend(packet_info.destination, data, packet_info.packet_options);
 }
 
 void P2PSocketTcpBase::Send(base::span<const uint8_t> data,
                             const P2PPacketInfo& packet_info) {
-  SendPacket(data, packet_info);
+  if (!SendPacket(data, packet_info)) {
+    LOG(ERROR) << "Socket destroyed while sending";
+  }
 }
 
 void P2PSocketTcpBase::SendBatch(
@@ -444,7 +463,7 @@
   return OnPacket(input.subspan(kPacketHeaderSize, packet_size));
 }
 
-void P2PSocketTcp::DoSend(const net::IPEndPoint& to,
+bool P2PSocketTcp::DoSend(const net::IPEndPoint& to,
                           base::span<const uint8_t> data,
                           const webrtc::AsyncSocketPacketOptions& options) {
   const size_t buffer_size = kPacketHeaderSize + data.size();
@@ -468,7 +487,7 @@
                              send_buffer_without_header.size(),
                              options.packet_time_params, webrtc::TimeMicros());
 
-  WriteOrQueue(send_buffer);
+  return WriteOrQueue(send_buffer);
 }
 
 // P2PSocketStunTcp
@@ -509,7 +528,7 @@
   return OnPacket(input.first(packet_size));
 }
 
-void P2PSocketStunTcp::DoSend(const net::IPEndPoint& to,
+bool P2PSocketStunTcp::DoSend(const net::IPEndPoint& to,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/services/network/p2p/socket_tcp_unittest.cc b/services/network/p2p/socket_tcp_unittest.cc
index fcdd217..45761c14 100644
--- a/services/network/p2p/socket_tcp_unittest.cc
+++ b/services/network/p2p/socket_tcp_unittest.cc
@@ -685,4 +685,81 @@
   EXPECT_TRUE(ssl_socket_provider.ConnectDataConsumed());
 }
 
+// Verify that we can send packets using SendBatch
+TEST_F(P2PSocketTcpTest, SendAfterStunRequestWithSendBatch) {
+  // Receive packet from |dest_|.
+  std::vector<uint8_t> request_packet;
+  CreateStunRequest(&request_packet);
+
+  std::string received_data;
+  received_data.append(IntToSize(request_packet.size()));
+  received_data.append(request_packet.begin(), request_packet.end());
+
+  EXPECT_CALL(*fake_client_.get(), SendComplete(_)).Times(2);
+
+  EXPECT_CALL(*fake_client_.get(), DataReceived(_)).Times(1);
+  EXPECT_CALL(*this, SinglePacketReceptionHelper(_, SpanEq(request_packet), _));
+  socket_->AppendInputData(received_data);
+
+  webrtc::AsyncSocketPacketOptions options;
+  // Now we should be able to send any data to |dest_|.
+  std::vector<mojom::P2PSendPacketPtr> packet_batch;
+  std::vector<uint8_t> packet;
+  // CreateRandomPacket(&packet);
+  packet = {0x01, 0x02, 0x03};
+  std::string expected_data;
+  expected_data.append(IntToSize(packet.size()));
+  expected_data.append(packet.begin(), packet.end());
+  packet_batch.emplace_back(mojom::P2PSendPacket::New(
+      packet, P2PPacketInfo(dest_.ip_address, options, 0)));
+  std::vector<uint8_t> packet2;
+  // CreateRandomPacket(&packet2);
+  packet2 = {0x04, 0x05, 0x06};
+  expected_data.append(IntToSize(packet2.size()));
+  expected_data.append(packet2.begin(), packet2.end());
+  packet_batch.emplace_back(mojom::P2PSendPacket::New(
+      packet2, P2PPacketInfo(dest_.ip_address, options, 0)));
+  socket_impl_->SendBatch(std::move(packet_batch));
+
+  EXPECT_EQ(expected_data, sent_data_);
+
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(P2PSocketTcpTest, SendBatchWithBrokenFirstPacket) {
+  // Receive packet from |dest_|.
+  std::vector<uint8_t> request_packet;
+  CreateStunRequest(&request_packet);
+
+  std::string received_data;
+  received_data.append(IntToSize(request_packet.size()));
+  received_data.append(request_packet.begin(), request_packet.end());
+  EXPECT_CALL(*fake_client_.get(), DataReceived(_)).Times(1);
+  EXPECT_CALL(*this, SinglePacketReceptionHelper(_, SpanEq(request_packet), _));
+
+  socket_->AppendInputData(received_data);
+
+  webrtc::AsyncSocketPacketOptions options;
+  std::vector<mojom::P2PSendPacketPtr> packet_batch;
+  std::vector<uint8_t> packet;
+  CreateRandomPacket(&packet);
+  packet_batch.emplace_back(mojom::P2PSendPacket::New(
+      packet, P2PPacketInfo(dest_.ip_address, options, 0)));
+  std::vector<uint8_t> packet2;
+  CreateRandomPacket(&packet2);
+  packet_batch.emplace_back(mojom::P2PSendPacket::New(
+      packet2, P2PPacketInfo(dest_.ip_address, options, 0)));
+  socket_->set_error_on_next_write(net::ERR_FAILED);
+  // We now expect the first packet to cause the connection to be destroyed,
+  // and the second packet to never be processed.
+  auto socket_impl_ptr = socket_impl_.get();
+  socket_delegate_.ExpectDestruction(std::move(socket_impl_));
+  // The socket will be destroyed, so clear our raw_ptr to it to avoid
+  // a dangling pointer warning.
+  socket_ = nullptr;
+  socket_impl_ptr->SendBatch(std::move(packet_batch));
+  // process any queued callbacks.
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace network
Loading diff…

Original Bug Report

reported by [email protected]

P2PSocket(this) object is freed, causing Use-After-Free vulnerability

Steps to reproduce the problem

Access poc.html after applying diff

Problem Description

Access to the freed object starts at: https://source.chromium.org/chromium/chromium/src/+/main:services/network/p2p/socket_tcp.cc;l=391 return true: https://source.chromium.org/chromium/chromium/src/+/main:services/network/p2p/socket_tcp.cc;l=377 The object is freed at: https://source.chromium.org/chromium/chromium/src/+/main:services/network/p2p/socket_tcp.cc;l=305

In the SendPacket function called by SendBatch, as in the first link, the iterator is stopped via return false when this object is freed.

In the third link, this object is freed, but if you look at the second link, return true is returned, so SendBatch does not detect that this object is freed, and Use-After-Free occurs.

Summary

P2PSocket(this) object is freed, causing Use-After-Free vulnerability

Custom Questions

Type of crash:

Network Service

Crash state:

Access to this object that has been freed

Reporter credit:

jakebiles

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker