Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chromoting
DescriptionUse after free in Chromoting
ComponentChromoting
Bug ClassUAF
Tracker513231432
Fix commit7ac4947ceecc (chromium/src) +71/-14
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TakeLogData
remoting/protocol/webrtc_event_log_data.cc
modified
TEST
remoting/protocol/webrtc_event_log_data_unittest.cc
modified
for
remoting/protocol/webrtc_event_log_data_unittest.cc
modified

Files Changed

  • remoting/protocol/webrtc_event_log_data.cc
  • remoting/protocol/webrtc_event_log_data.h
  • remoting/protocol/webrtc_event_log_data_unittest.cc
From 7ac4947ceeccf92492fe37fb8feba9ae7ce7ae70 Mon Sep 17 00:00:00 2001
From: Lambros Lambrou <[email protected]>
Date: Fri, 15 May 2026 09:17:24 -0700
Subject: [PATCH] Synchronize WebrtcEventLogData with mutex locking

Resolve potential threading race conditions and data corruption in
WebrtcEventLogData by adding base::Lock synchronization.

Changes include:
- Added base::Lock to protect internal circular_deque sections_.
- Implemented MAGI-reviewed zero-allocation buffer recycling in CreateNewSection().
- Added CHECK_GT validation to SetMaxSectionSizeForTest and SetMaxSectionsForTest.
- Eliminated base::checked_cast<int> to prevent DoS integer overflow crashes.
- Added multithreaded concurrency stress test.

TAG=agy
CONV=057084ee-cea7-42d5-a8ca-4575cf9a75e3

Bug: 513231432
Change-Id: I64bab172d521e704f75289cd3af0eb30ddc60123
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7848427
Commit-Queue: Joe Downing <[email protected]>
Reviewed-by: Joe Downing <[email protected]>
Auto-Submit: Lambros Lambrou <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1631341}
---

diff --git a/remoting/protocol/webrtc_event_log_data.cc b/remoting/protocol/webrtc_event_log_data.cc
index 322e277..e1b3c943 100644
--- a/remoting/protocol/webrtc_event_log_data.cc
+++ b/remoting/protocol/webrtc_event_log_data.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/check_op.h"
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 
@@ -21,26 +22,33 @@
 WebrtcEventLogData::~WebrtcEventLogData() = default;
 
 void WebrtcEventLogData::SetMaxSectionSizeForTest(int max_section_size) {
+  base::AutoLock lock(lock_);
+  CHECK_GT(max_section_size, 0);
   max_section_size_ = max_section_size;
 }
 
 void WebrtcEventLogData::SetMaxSectionsForTest(int max_sections) {
+  base::AutoLock lock(lock_);
+  CHECK_GT(max_sections, 0);
   max_sections_ = max_sections;
   sections_.reserve(max_sections_);
 }
 
 base::circular_deque<WebrtcEventLogData::LogSection>
 WebrtcEventLogData::TakeLogData() {
+  base::AutoLock lock(lock_);
   auto result = std::move(sections_);
 
-  // The |sections_| container is still valid but unspecified. Call Clear() to
-  // be certain it is empty (and the correct capacity is reserved).
-  Clear();
+  // The |sections_| container is still valid but unspecified. Call
+  // ClearLocked() to be certain it is empty (and the correct capacity is
+  // reserved).
+  ClearLocked();
   return result;
 }
 
 void WebrtcEventLogData::Write(std::string_view log_event) {
-  if (base::checked_cast<int>(log_event.size()) > max_section_size_) {
+  base::AutoLock lock(lock_);
+  if (log_event.size() > static_cast<size_t>(max_section_size_)) {
     LOG(WARNING) << "Oversized RTC log event: size = " << log_event.size();
   }
 
@@ -54,6 +62,11 @@
 }
 
 void WebrtcEventLogData::Clear() {
+  base::AutoLock lock(lock_);
+  ClearLocked();
+}
+
+void WebrtcEventLogData::ClearLocked() {
   sections_.clear();
   sections_.reserve(max_sections_);
 }
@@ -65,17 +78,21 @@
 
   // The event log entries are packet headers generated by WebRTC, and it is
   // assumed that the sizes are small enough to prevent integer overflow.
-  return base::checked_cast<int>(sections_.back().size() + log_event_size) >
-         max_section_size_;
+  return sections_.back().size() + log_event_size >
+         static_cast<size_t>(max_section_size_);
 }
 
 void WebrtcEventLogData::CreateNewSection() {
   if (static_cast<int>(sections_.size()) >= max_sections_) {
-    // Discard oldest section to make room.
+    // Recycle oldest section buffer to avoid heap allocation under lock.
+    auto section = std::move(sections_.front());
     sections_.pop_front();
+    section.clear();
+    sections_.push_back(std::move(section));
+  } else {
+    sections_.emplace_back();
+    sections_.back().reserve(max_section_size_);
   }
-  sections_.emplace_back();
-  sections_.back().reserve(max_section_size_);
 }
 
 }  // namespace remoting::protocol
diff --git a/remoting/protocol/webrtc_event_log_data.h b/remoting/protocol/webrtc_event_log_data.h
index 53e2b9d..3d98da1 100644
--- a/remoting/protocol/webrtc_event_log_data.h
+++ b/remoting/protocol/webrtc_event_log_data.h
@@ -10,6 +10,8 @@
 #include <vector>
 
 #include "base/containers/circular_deque.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
 
 namespace remoting::protocol {
 
@@ -50,24 +52,29 @@
 
  private:
   // Returns true if a new section must be created to store the event.
-  bool NeedNewSection(size_t log_event_size) const;
+  bool NeedNewSection(size_t log_event_size) const
+      EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
   // Appends a new section of zero size to the end of the list, removing the
   // oldest one if necessary. On return, the section at the end (the list's
   // "back") will be empty, ready to accept the new data.
-  void CreateNewSection();
+  void CreateNewSection() EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
-  base::circular_deque<LogSection> sections_;
+  // Removes all event data without acquiring lock_.
+  void ClearLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
+  mutable base::Lock lock_;
+  base::circular_deque<LogSection> sections_ GUARDED_BY(lock_);
 
   // Value chosen to keep the memory-usage within reasonable limits, but also
   // allow for recording "most" sessions entirely.
   const int kMaxSections = 1000;
-  int max_sections_ = kMaxSections;
+  int max_sections_ GUARDED_BY(lock_) = kMaxSections;
 
   // A larger value will reduce memory-allocations at the cost of discarding a
   // larger chunk of the event log.
   const int kMaxSectionSize = 102400;  // 100K
-  int max_section_size_ = kMaxSectionSize;
+  int max_section_size_ GUARDED_BY(lock_) = kMaxSectionSize;
 };
 
 }  // namespace remoting::protocol
diff --git a/remoting/protocol/webrtc_event_log_data_unittest.cc b/remoting/protocol/webrtc_event_log_data_unittest.cc
index be43155..7c71854 100644
--- a/remoting/protocol/webrtc_event_log_data_unittest.cc
+++ b/remoting/protocol/webrtc_event_log_data_unittest.cc
@@ -4,6 +4,11 @@
 
 #include "remoting/protocol/webrtc_event_log_data.h"
 
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
+#include "base/run_loop.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace remoting::protocol {
@@ -67,4 +72,32 @@
   EXPECT_TRUE(data.empty());
 }
 
+TEST(WebrtcEventLogDataTest, MultiThreadedAccess) {
+  base::test::TaskEnvironment task_environment;
+  WebrtcEventLogData event_log;
+  event_log.SetMaxSectionSizeForTest(100);
+
+  constexpr int kNumTasks = 10;
+  base::RunLoop run_loop;
+  auto barrier_closure =
+      base::BarrierClosure(kNumTasks, run_loop.QuitClosure());
+
+  for (int i = 0; i < kNumTasks; ++i) {
+    base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(
+                                              [](WebrtcEventLogData* log,
+                                                 base::RepeatingClosure done) {
+                                                for (int j = 0; j < 100; ++j) {
+                                                  log->Write("test");
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/remoting/protocol/webrtc_event_log_data_unittest.cc b/remoting/protocol/webrtc_event_log_data_unittest.cc
index be43155..7c71854 100644
--- a/remoting/protocol/webrtc_event_log_data_unittest.cc
+++ b/remoting/protocol/webrtc_event_log_data_unittest.cc
@@ -4,6 +4,11 @@
 
 #include "remoting/protocol/webrtc_event_log_data.h"
 
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
+#include "base/run_loop.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace remoting::protocol {
@@ -67,4 +72,32 @@
   EXPECT_TRUE(data.empty());
 }
 
+TEST(WebrtcEventLogDataTest, MultiThreadedAccess) {
+  base::test::TaskEnvironment task_environment;
+  WebrtcEventLogData event_log;
+  event_log.SetMaxSectionSizeForTest(100);
+
+  constexpr int kNumTasks = 10;
+  base::RunLoop run_loop;
+  auto barrier_closure =
+      base::BarrierClosure(kNumTasks, run_loop.QuitClosure());
+
+  for (int i = 0; i < kNumTasks; ++i) {
+    base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(
+                                              [](WebrtcEventLogData* log,
+                                                 base::RepeatingClosure done) {
+                                                for (int j = 0; j < 100; ++j) {
+                                                  log->Write("test");
+                                                  if (j % 10 == 0) {
+                                                    log->TakeLogData();
+                                                  }
+                                                }
+                                                done.Run();
+                                              },
+                                              &event_log, barrier_closure));
+  }
+
+  run_loop.Run();
+}
+
 }  // namespace remoting::protocol
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in Chrome Remote Desktop host via data race in WebrtcEventLogData

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

Overview: A data race in WebrtcEventLogData allows concurrent unsynchronized access to a base::circular_deque from a ThreadPool worker and the CRD network thread. This can lead to a Use-After-Free (UAF) or heap corruption in the unsandboxed, high-privilege CRD host process. An authenticated attacker could potentially trigger this by requesting an RTC log transfer while generating significant logging activity.

Affected files:

  • remoting/protocol/webrtc_event_log_data.cc
  • remoting/protocol/webrtc_event_log_data.h
  • remoting/protocol/webrtc_transport.cc
  • remoting/host/file_transfer/rtc_log_file_operations.cc
  • remoting/host/client_session.cc

Estimated timestamp from git blame: 2020-10-07

Summary

A potential memory safety vulnerability exists in the Chrome Remote Desktop (CRD) host due to a data race in the remoting::protocol::WebrtcEventLogData class. The class manages WebRTC event logs using a base::circular_deque<std::vector<uint8_t>> that is accessed from multiple threads without synchronization. This can result in a Use-After-Free (UAF) or heap corruption in the high-privilege host process (typically running as SYSTEM on Windows or root on Linux).

Root Cause Analysis

The WebrtcEventLogData class (defined in remoting/protocol/webrtc_event_log_data.h) stores log events in a sections_ deque. It provides two primary methods that are invoked from different threads:

  1. Write(std::string_view log_event): Invoked by the WebRTC internal logging task queue. In Chromium, this queue is backed by a base::ThreadPool sequenced task runner. This method accesses sections_.back() to append data to the current log section.
  2. TakeLogData(): Invoked on the CRD network thread (an AutoThread) when a client requests an RTC log transfer (via a data channel with the rtc-log-transfer- prefix). This method moves the sections_ deque into a local variable and clears the original member (remoting/protocol/webrtc_event_log_data.cc:34).

There is no synchronization (e.g., base::Lock) protecting the sections_ member. If TakeLogData() is called while a WebRTC logging task is executing Write(), a data race occurs on the deque.

Specifically, if Write() obtains a reference to the last section (auto& section = sections_.back()) and TakeLogData() simultaneously moves the deque, the reference held by Write() now points to a std::vector owned by the reader on the network thread. If the reader then destroys its copy of the data (for example, if the attacker closes the data channel immediately after requesting the transfer), the reference in Write() becomes dangling, leading to a Use-After-Free write when section.insert() is called.

Security Impact

The CRD host process is unsandboxed and operates with high system privileges. Successful exploitation of this race could allow an authenticated attacker to achieve arbitrary code execution in a high-privilege context. Furthermore, the rtcLogTransfer capability is enabled unconditionally in ClientSession, meaning this attack vector may be available even if enterprise policies restrict standard file transfers or put the session in ‘view-only’ mode.

Potential Reproduction Steps

Note: These steps are suggested based on code analysis; our current environment does not support functional execution of a PoC.

  1. Establish an authenticated CRD session (e.g., an It2Me support session).
  2. Generate frequent WebRTC event log entries by sending significant network traffic (RTP/RTCP) or frequently creating/closing data channels.
  3. In a loop, initiate multiple RTC log transfers by opening data channels with the prefix rtc-log-transfer- and sending a RequestTransfer message.
  4. Immediately close each data channel after the request is sent to trigger the destruction of the moved log data.
  5. Observe for memory corruption or crashes in the host process. Under ThreadSanitizer (TSan), the data race between Write and TakeLogData should be readily identifiable.

Suggested Fix

Protect access to the sections_ member in WebrtcEventLogData using a base::Lock. Ensure that Write(), TakeLogData(), and Clear() all acquire the lock before accessing the deque. Alternatively, ensure all operations on WebrtcEventLogData are dispatched to the same sequenced task runner, although this may require refactoring how WebRTC’s RtcEventLogOutput sink interacts with the data store.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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