Low chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Chromoting
DescriptionInteger overflow in Chromoting
ComponentChromoting
Bug ClassInteger Overflow
Tracker501900366
Fix commitb8b43e307495 (chromium/src) +79/-24
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
remoting/host/win/event_trace_data.cc
modified
TEST_F
remoting/host/win/event_trace_data_unittest.cc
modified

Files Changed

  • remoting/host/win/event_trace_data.cc
  • remoting/host/win/event_trace_data_unittest.cc
From b8b43e307495d9946f5a6dad02bbcff925d4eeb1 Mon Sep 17 00:00:00 2001
From: Joe Downing <[email protected]>
Date: Thu, 16 Apr 2026 23:12:07 -0700
Subject: [PATCH] CRD: Use safe buffer access in EventTraceData::Create

The Chrome Remote Desktop daemon on Windows parses ETW logs.
An integer truncation bug in EventTraceData::Create allowed a
malicious ETW event to specify an out-of-bounds read offset,
potentially leading to heap memory leakage into log files.

This patch refactors the parsing logic to use base::span and
base::SpanReader. This prevents truncation by using safe arithmetic
(base::CheckedNumeric) and ensures all reads are bounds-checked by
the underlying abstractions.

Key changes:
 - Replaced raw pointer arithmetic with base::SpanReader for
   sequential field extraction.
  - Used base::CheckedNumeric to safely calculate stack data skip
    sizes, preventing truncation and overflow on 32-bit/64-bit
    systems.
  - Replaced strnlen_s and manual char pointer casts with
    base::as_string_view and std::string_view::find for safer string
    parsing.
  - Added an integrity check (DCHECK) to ensure the entire MofData
    buffer is consumed.
  - Added a regression test case to verify safe handling of malformed
    ETW payloads with excessive stack depth.

Bug: 501900366
Change-Id: If10ca8b965be8c648cc2a4acc785a5d231a5e183
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7769113
Reviewed-by: Yuwei Huang <[email protected]>
Commit-Queue: Joe Downing <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1616350}
---

diff --git a/remoting/host/win/event_trace_data.cc b/remoting/host/win/event_trace_data.cc
index 4ae3b2d..26ca871 100644
--- a/remoting/host/win/event_trace_data.cc
+++ b/remoting/host/win/event_trace_data.cc
@@ -4,12 +4,18 @@
 
 #include "remoting/host/win/event_trace_data.h"
 
+#include <string_view>
+
 #include "base/check.h"
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
+#include "base/containers/span_reader.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/logging_win.h"
 #include "base/notreached.h"
+#include "base/numerics/checked_math.h"
+#include "base/strings/string_view_util.h"
 #include "base/strings/utf_string_conversions.h"
 
 namespace remoting {
@@ -70,38 +76,60 @@
   // - For LOG_MESSAGE_FULL events, the MofData buffer is comprised of 5 fields
   //   which must be parsed (or skipped) in sequence.
   if (data.event_type == logging::LOG_MESSAGE) {
-    data.message.assign(reinterpret_cast<const char*>(event->MofData),
-                        event->MofLength);
+    // SAFETY: `event->MofData` and `event->MofLength` are provided by the
+    // Windows ETW subsystem. We trust these values to define the valid memory
+    // range for the event payload.
+    auto message_span = UNSAFE_BUFFERS(base::span(
+        reinterpret_cast<const uint8_t*>(event->MofData), event->MofLength));
+    std::string_view message_view = base::as_string_view(message_span);
+    data.message.assign(message_view.substr(0, message_view.find('\0')));
   } else if (data.event_type == logging::LOG_MESSAGE_FULL) {
-    const uint8_t* mof_data = reinterpret_cast<const uint8_t*>(event->MofData);
-    uint32_t offset = 0;
+    // SAFETY: `event->MofData` and `event->MofLength` are provided by the
+    // Windows ETW subsystem. We trust these values to define the valid memory
+    // range for the event payload.
+    base::SpanReader reader(UNSAFE_BUFFERS(base::span(
+        reinterpret_cast<const uint8_t*>(event->MofData), event->MofLength)));
 
     // Read the size, skip past the stack info, and move the cursor.
-    DWORD stack_depth = *reinterpret_cast<const DWORD*>(mof_data);
-    int bytes_to_skip = sizeof(DWORD) + stack_depth * sizeof(intptr_t);
-    offset += bytes_to_skip;
+    uint32_t stack_depth;
+    if (!reader.ReadU32NativeEndian(stack_depth)) {
+      return data;
+    }
+    base::CheckedNumeric<size_t> bytes_to_skip = stack_depth;
+    bytes_to_skip *= sizeof(intptr_t);
+    if (!bytes_to_skip.IsValid() || !reader.Skip(bytes_to_skip.ValueOrDie())) {
+      return data;
+    }
 
     // Read the line info and move the cursor.
-    data.line =
-        *reinterpret_cast<const int32_t*>(UNSAFE_TODO(mof_data + offset));
-    offset += sizeof(int32_t);
+    if (!reader.ReadI32NativeEndian(data.line)) {
+      return data;
+    }
 
     // Read the file info and move the cursor.
-    const char* file_info =
-        reinterpret_cast<const char*>(UNSAFE_TODO(mof_data + offset));
-    size_t str_len = strnlen_s(file_info, event->MofLength - offset);
-    base::FilePath file_path(base::UTF8ToWide(file_info));
+    std::string_view file_info_view =
+        base::as_string_view(reader.remaining_span());
+    size_t nul_pos = file_info_view.find('\0');
+    if (nul_pos == std::string_view::npos) {
+      return data;
+    }
+    base::FilePath file_path(
+        base::UTF8ToWide(file_info_view.substr(0, nul_pos)));
     data.file_name = base::WideToUTF8(file_path.BaseName().value());
-    offset += (str_len + 1);
+    reader.Skip(nul_pos + 1);
 
     // Read the message and move the cursor.
-    const char* message =
-        reinterpret_cast<const char*>(UNSAFE_TODO(mof_data + offset));
-    str_len = strnlen_s(message, event->MofLength - offset);
-    data.message.assign(message);
-    offset += (str_len + 1);
+    std::string_view message_view =
+        base::as_string_view(reader.remaining_span());
+    nul_pos = message_view.find('\0');
+    if (nul_pos == std::string_view::npos) {
+      return data;
+    }
+    data.message.assign(message_view.substr(0, nul_pos));
+    reader.Skip(nul_pos + 1);
 
-    DCHECK_EQ(event->MofLength, offset);
+    // Ensure that the entire buffer was consumed.
+    DCHECK_EQ(reader.remaining(), 0u);
   } else {
     NOTREACHED() << "Unknown event type: " << data.event_type;
   }
diff --git a/remoting/host/win/event_trace_data_unittest.cc b/remoting/host/win/event_trace_data_unittest.cc
index f47447a..243c0ac6 100644
--- a/remoting/host/win/event_trace_data_unittest.cc
+++ b/remoting/host/win/event_trace_data_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "remoting/host/win/event_trace_data.h"
 
+#include <string>
+#include <vector>
+
 #include "base/check.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
@@ -27,15 +30,13 @@
  protected:
   void InitForLogMessage();
   void InitForLogMessageFull();
+  void InitSharedFields(uint8_t type);
 
   size_t ReserveBufferSpace(size_t space_needed);
 
   FILETIME time_ = {};
   EVENT_TRACE event_trace_ = {};
   std::vector<uint8_t> buffer_;
-
- private:
-  void InitSharedFields(uint8_t type);
 };
 
 void EventTraceDataTest::InitSharedFields(uint8_t type) {
@@ -110,6 +111,7 @@
   EXPECT_EQ(data.process_id, kProcessId);
   EXPECT_EQ(data.thread_id, kThreadId);
   EXPECT_STREQ(data.message.c_str(), kTestLogMessage);
+  EXPECT_EQ(data.message.length(), strlen(kTestLogMessage));
 
   // File and line data should not be filled in for this log message type.
   EXPECT_EQ(data.file_name, std::string());
@@ -131,4 +133,29 @@
   EXPECT_STREQ(data.message.c_str(), kTestLogMessage);
 }
 
+TEST_F(EventTraceDataTest, LogFullMessage_LargeStackDepth) {
+  InitSharedFields(static_cast<uint8_t>(logging::LOG_MESSAGE_FULL));
+
+  // A large stack depth that would require more memory than is available in
+  // the buffer should be handled safely.
+  DWORD large_stack_depth = 0x3FFFFFFE;
+  size_t data_size = sizeof(DWORD);
+  size_t offset = ReserveBufferSpace(data_size);
+  UNSAFE_TODO(memcpy(buffer_.data() + offset, &large_stack_depth, data_size));
+
+  // Set the MofLength to the current buffer size, which only contains the
+  // large stack depth value.
+  event_trace_.MofData = buffer_.data();
+  event_trace_.MofLength = buffer_.size();
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/remoting/host/win/event_trace_data_unittest.cc b/remoting/host/win/event_trace_data_unittest.cc
index f47447a..243c0ac6 100644
--- a/remoting/host/win/event_trace_data_unittest.cc
+++ b/remoting/host/win/event_trace_data_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "remoting/host/win/event_trace_data.h"
 
+#include <string>
+#include <vector>
+
 #include "base/check.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
@@ -27,15 +30,13 @@
  protected:
   void InitForLogMessage();
   void InitForLogMessageFull();
+  void InitSharedFields(uint8_t type);
 
   size_t ReserveBufferSpace(size_t space_needed);
 
   FILETIME time_ = {};
   EVENT_TRACE event_trace_ = {};
   std::vector<uint8_t> buffer_;
-
- private:
-  void InitSharedFields(uint8_t type);
 };
 
 void EventTraceDataTest::InitSharedFields(uint8_t type) {
@@ -110,6 +111,7 @@
   EXPECT_EQ(data.process_id, kProcessId);
   EXPECT_EQ(data.thread_id, kThreadId);
   EXPECT_STREQ(data.message.c_str(), kTestLogMessage);
+  EXPECT_EQ(data.message.length(), strlen(kTestLogMessage));
 
   // File and line data should not be filled in for this log message type.
   EXPECT_EQ(data.file_name, std::string());
@@ -131,4 +133,29 @@
   EXPECT_STREQ(data.message.c_str(), kTestLogMessage);
 }
 
+TEST_F(EventTraceDataTest, LogFullMessage_LargeStackDepth) {
+  InitSharedFields(static_cast<uint8_t>(logging::LOG_MESSAGE_FULL));
+
+  // A large stack depth that would require more memory than is available in
+  // the buffer should be handled safely.
+  DWORD large_stack_depth = 0x3FFFFFFE;
+  size_t data_size = sizeof(DWORD);
+  size_t offset = ReserveBufferSpace(data_size);
+  UNSAFE_TODO(memcpy(buffer_.data() + offset, &large_stack_depth, data_size));
+
+  // Set the MofLength to the current buffer size, which only contains the
+  // large stack depth value.
+  event_trace_.MofData = buffer_.data();
+  event_trace_.MofLength = buffer_.size();
+
+  // The malformed data should be detected and handled without crashing or
+  // reading out of bounds.
+  EventTraceData data = EventTraceData::Create(&event_trace_);
+
+  // Since the payload is malformed, the line number and message should not
+  // be populated.
+  EXPECT_EQ(data.line, 0);
+  EXPECT_TRUE(data.message.empty());
+}
+
 }  // namespace remoting
Loading diff…

Original Bug Report

reported by [email protected]

Potential OOB Heap Read in CRD SYSTEM Daemon via Malicious ETW Events

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.

Overview: The Chrome Remote Desktop (CRD) daemon on Windows contains a potential out-of-bounds heap read vulnerability when parsing Event Tracing for Windows (ETW) logs. An integer truncation bug in EventTraceData::Create allows a malicious ETW event to specify an out-of-bounds read offset, leading to heap memory leakage into CRD log files. A compromised low-privilege CRD process could exploit this to bypass ASLR by reading the generated logs.

Affected files:

  • remoting/host/win/event_trace_data.cc
  • remoting/host/win/etw_trace_consumer.cc
  • remoting/host/win/etw_trace_controller.cc
  • remoting/host/daemon_process_win.cc

Estimated timestamp from git blame: 2025-11-16

Vulnerability Overview

The Chrome Remote Desktop (CRD) SYSTEM daemon (remoting_host.exe) on Windows is potentially vulnerable to an out-of-bounds (OOB) heap read. The issue resides in EventTraceData::Create when parsing LOG_MESSAGE_FULL ETW events. Missing bounds checks and an integer truncation vulnerability allow an attacker-controlled stack_depth value to generate a massive, out-of-bounds offset. Subsequent string operations then read arbitrary heap memory until a null byte is encountered.

If host logging is enabled (e.g., via the LogToFile registry key), this leaked memory is written to log files located in %ProgramFiles%. Because a compromised CRD network process running under LocalService has both the ability to inject ETW events and read files in %ProgramFiles%, this creates a SYSTEM-to-LocalService information leak, which can be used to defeat Address Space Layout Randomization (ASLR).

Technical Details

When the CRD daemon receives an ETW event, EtwTraceConsumerImpl::Core::DispatchEvent calls EventTraceData::Create(event) (remoting/host/win/event_trace_data.cc:54). If the event type is logging::LOG_MESSAGE_FULL, the MofData buffer is parsed:

  1. Missing Length Check: At line 80, a DWORD (stack_depth) is read directly from mof_data without verifying that event->MofLength >= sizeof(DWORD).
  2. Integer Truncation: At line 81, int bytes_to_skip = sizeof(DWORD) + stack_depth * sizeof(intptr_t); is calculated. On a 64-bit system, sizeof(intptr_t) is 8. The multiplication evaluates as a 64-bit size_t. If an attacker sets stack_depth to a crafted value like 0x3FFFFFFE, the result 0x1FFFFFFF0 + 4 (0x1FFFFFFF4) is truncated when assigned to the signed 32-bit int bytes_to_skip, becoming -12 (or 0xFFFFFFF4).
  3. Out-of-Bounds Offset: At line 82, offset += bytes_to_skip;. Since offset is a uint32_t initialized to 0, adding -12 results in offset = 0xFFFFFFF4 due to unsigned wrap-around. There is no bounds check to verify offset <= event->MofLength.
  4. OOB Memory Reads: The code then attempts to read the line number, file name, and message using this massive offset.
    • At line 85, data.line reads 4 bytes from mof_data + 0xFFFFFFF4.
    • At line 92, strnlen_s(file_info, event->MofLength - offset) is called. Since offset is huge, the subtraction underflows to a massive length, neutering the strnlen_s limit.
    • At line 93, base::UTF8ToWide(file_info) is called with a const char*. This implicitly constructs a std::string_view, which calls strlen(file_info), causing a continuous read from the out-of-bounds pointer until a null byte is found.
    • This same unbounded read via strlen occurs at line 101 for data.message.assign(message).

Potential Attack Scenario

These steps outline how an attacker might theoretically trigger the vulnerability. Note that these steps have not been verified with a functional proof-of-concept.

  1. Prerequisite: The target system has CRD installed and host logging enabled via HKLM\SOFTWARE\Google\Chrome Remote Desktop\logging\LogToFile.
  2. An attacker compromises the CRD network process, which runs with a restricted LocalService token.
  3. The attacker uses the network process to generate and send a crafted EVENT_TRACE to the ETW session chrome_remote_desktop_host_logger, specifying the logging::kLogEventId GUID and logging::LOG_MESSAGE_FULL type.
  4. The crafted MofData payload contains a stack_depth chosen to induce truncation and force offset to point to a valid, predictable heap memory region relative to the MofData allocation.
  5. The SYSTEM daemon parses the event, reads out-of-bounds heap memory, and logs it to chrome_remote_desktop_*.log in %ProgramFiles%\Google\Chrome Remote Desktop\<version>\.
  6. The LocalService account has default read permissions for %ProgramFiles%. The attacker reads the log file to extract the leaked SYSTEM heap data.

Suggested Fix

  1. Bounds Checking: Ensure event->MofLength is checked before reading stack_depth.
  2. Safe Arithmetic: Use base::CheckedNumeric or size_t for bytes_to_skip and offset calculations to prevent truncation and wrap-around. Explicitly check that offset <= event->MofLength at each step.
  3. Safe String Handling: Replace the raw pointer manipulation and strnlen_s logic with base::span<const uint8_t>. Use span slicing and bounded string constructors (e.g., passing explicit lengths to std::string and base::UTF8ToWide) instead of relying on null-termination.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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.

View on issue tracker