Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Chromoting
DescriptionInsufficient validation of untrusted input in Chromoting
ComponentChromoting
Bug ClassLogic Error
Tracker517351411
Fix commitf6839e97cb00 (chromium/src) +68/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
for
remoting/host/chromoting_host_services_client.cc
modified
if
remoting/host/win/acl_util.cc
modified

Files Changed

  • remoting/host/chromoting_host_services_client.cc
  • remoting/host/win/BUILD.gn
  • remoting/host/win/acl_util.cc
  • remoting/host/win/acl_util.h
  • remoting/host/win/host_service.cc
From f6839e97cb00fa1d7b71ff4576733a88ac6148f0 Mon Sep 17 00:00:00 2001
From: Yuwei Huang <yuweih@chromium.org>
Date: Thu, 28 May 2026 20:04:06 -0700
Subject: [PATCH] remoting: Verify CRD host server privilege on Windows named pipe client

This change configures NamedPlatformChannel::Options to enable
verify_server_privilege on Windows for ChromotingHostServicesClient.
This ensures the client process verifies that the target named pipe
server has an equal or greater process integrity level than itself
during connection handshake, preventing potential sandbox escape / named
pipe squatting attacks from lower-integrity session 0 processes.

Also, the named pipe is opened by the privileged daemon process, and
less privileged processes won't be able to query process and token info
about it, so additional changes are made to allow authenticated users to
query limited process info and token info about the daemon process.

Bug: 517351411
Change-Id: Ia80c76f844b5ed0a3fac5c5884b76509dad519a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7883474
Auto-Submit: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: Joe Downing <joedow@chromium.org>
Commit-Queue: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1638202}
---

diff --git a/remoting/host/chromoting_host_services_client.cc b/remoting/host/chromoting_host_services_client.cc
index b34167e..f2b8aa1 100644
--- a/remoting/host/chromoting_host_services_client.cc
+++ b/remoting/host/chromoting_host_services_client.cc
@@ -15,6 +15,7 @@
 #include "components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
 #include "mojo/public/cpp/system/invitation.h"
 #include "remoting/base/constants.h"
 #include "remoting/host/ipc_constants.h"
@@ -31,7 +32,15 @@
 mojo::PendingRemote<mojom::ChromotingHostServices> ConnectToServer(
     const std::vector<mojo::NamedPlatformChannel::ServerName>& server_names) {
   for (const auto& server_name : server_names) {
-    auto endpoint = named_mojo_ipc_server::ConnectToServer(server_name);
+    mojo::PlatformChannelEndpoint endpoint;
+#if BUILDFLAG(IS_WIN)
+    mojo::NamedPlatformChannel::Options options;
+    options.server_name = server_name;
+    options.verify_server_privilege = true;
+    endpoint = named_mojo_ipc_server::ConnectToServer(options);
+#else
+    endpoint = named_mojo_ipc_server::ConnectToServer(server_name);
+#endif
     if (!endpoint.is_valid()) {
       VLOG(1) << "Cannot connect to IPC through server name " << server_name
               << ". Endpoint is invalid.";
diff --git a/remoting/host/win/BUILD.gn b/remoting/host/win/BUILD.gn
index bd69ae5..9e3eae03 100644
--- a/remoting/host/win/BUILD.gn
+++ b/remoting/host/win/BUILD.gn
@@ -426,6 +426,7 @@
     "wts_session_process_delegate.cc",
   ]
   deps = [
+    ":acl_util",
     ":messages",
     ":remoting_lib_idl",
     ":remoting_lib_ps",
diff --git a/remoting/host/win/acl_util.cc b/remoting/host/win/acl_util.cc
index 0707531e..4cddd99 100644
--- a/remoting/host/win/acl_util.cc
+++ b/remoting/host/win/acl_util.cc
@@ -7,6 +7,7 @@
 #include <windows.h>
 
 #include "base/logging.h"
+#include "base/win/scoped_handle.h"
 #include "base/win/security_descriptor.h"
 
 namespace remoting {
@@ -36,4 +37,38 @@
   return true;
 }
 
+bool AddTokenAccessRightForWellKnownSid(base::win::WellKnownSid well_known_sid,
+                                        DWORD new_right) {
+  HANDLE token_handle = nullptr;
+  if (!::OpenProcessToken(::GetCurrentProcess(),
+                          TOKEN_QUERY | WRITE_DAC | READ_CONTROL,
+                          &token_handle)) {
+    PLOG(ERROR) << "Failed to open current process token";
+    return false;
+  }
+  base::win::ScopedHandle token(token_handle);
+
+  auto sd = base::win::SecurityDescriptor::FromHandle(
+      token.get(), base::win::SecurityObjectType::kKernel,
+      DACL_SECURITY_INFORMATION);
+  if (!sd) {
+    PLOG(ERROR)
+        << "Failed to read security descriptor of current process token";
+    return false;
+  }
+  if (!sd->SetDaclEntry(well_known_sid, base::win::SecurityAccessMode::kGrant,
+                        new_right,
+                        /* inheritance= */ 0)) {
+    PLOG(ERROR) << "Failed to set DACL entry on token security descriptor";
+    return false;
+  }
+  if (!sd->WriteToHandle(token.get(), base::win::SecurityObjectType::kKernel,
+                         DACL_SECURITY_INFORMATION)) {
+    PLOG(ERROR)
+        << "Failed to write security descriptor to current process token";
+    return false;
+  }
+  return true;
+}
+
 }  // namespace remoting
diff --git a/remoting/host/win/acl_util.h b/remoting/host/win/acl_util.h
index 8180d0b..9074655c 100644
--- a/remoting/host/win/acl_util.h
+++ b/remoting/host/win/acl_util.h
@@ -17,6 +17,11 @@
     base::win::WellKnownSid well_known_sid,
     DWORD new_right);
 
+// Adds new access right to the current process token for |well_known_sid|.
+// Returns a boolean indicating whether the operation was successful.
+bool AddTokenAccessRightForWellKnownSid(base::win::WellKnownSid well_known_sid,
+                                        DWORD new_right);
+
 }  // namespace remoting
 
 #endif  // REMOTING_HOST_WIN_ACL_UTIL_H_
diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc
index 8d382da..2ebc17ba 100644
--- a/remoting/host/win/host_service.cc
+++ b/remoting/host/win/host_service.cc
@@ -33,6 +33,7 @@
 #include "remoting/base/scoped_sc_handle_win.h"
 #include "remoting/host/base/host_exit_codes.h"
 #include "remoting/host/daemon_process.h"
+#include "remoting/host/win/acl_util.h"
 #include "remoting/host/win/com_security.h"
 #include "remoting/host/win/core_resource.h"
 #include "remoting/host/win/wts_terminal_observer.h"
@@ -93,6 +94,20 @@
   }
 }
 
+// Grants PROCESS_QUERY_LIMITED_INFORMATION on the current process and
+// TOKEN_QUERY on its process token to standard users. This allows standard
+// user client processes to verify the session ID and integrity level of the
+// SYSTEM daemon process during named pipe connections.
+void GrantQueryAccessToAuthenticatedUsers() {
+  bool success = AddProcessAccessRightForWellKnownSid(
+                     base::win::WellKnownSid::kAuthenticatedUser,
+                     PROCESS_QUERY_LIMITED_INFORMATION) &&
+                 AddTokenAccessRightForWellKnownSid(
+                     base::win::WellKnownSid::kAuthenticatedUser, TOKEN_QUERY);
+  CHECK(success)
+      << "Failed to grant process/token query access to Authenticated Users.";
+}
+
 }  // namespace
 
 HostService* HostService::GetInstance() {
@@ -325,6 +340,7 @@
     return;
   }
 
+  GrantQueryAccessToAuthenticatedUsers();
   CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
       new AutoThreadTaskRunner(main_task_runner_, run_loop.QuitClosure())));
 
@@ -379,6 +395,7 @@
   // Subscribe to session change notifications.
   if (WTSRegisterSessionNotification(window.hwnd(), NOTIFY_FOR_ALL_SESSIONS) !=
       FALSE) {
+    GrantQueryAccessToAuthenticatedUsers();
     CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
         new AutoThreadTaskRunner(main_task_runner_, run_loop.QuitClosure())));
 
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential sandbox escape in ChromotingHostServicesClient on Windows

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

Overview: ChromotingHostServicesClient on Windows connects to its IPC server without enabling verify_server_privilege. This potentially allows a lower-integrity process running in session 0 to act as a trusted broker and send arbitrary handles to the client. An attacker could exploit this to perform arbitrary CloseHandle operations on the medium-integrity client process.

Affected files:

  • remoting/host/chromoting_host_services_client.cc

Estimated timestamp from git blame: 2021-11-02

Potential Issue Description

On Windows, ChromotingHostServicesClient connects to the Chrome Remote Desktop (CRD) host services named pipe (\\.\pipe\mojo.chromoting.host_services_mojo_ipc). However, it connects using default NamedPlatformChannel::Options where verify_server_privilege is set to false.

Because of this omission, the client does not verify that the server’s process integrity level is equal to or greater than its own during the named pipe connection handshake. An attacker with a compromised low-privilege or sandboxed process running in session 0 could squat or serve this named pipe. When a medium-integrity helper process (e.g., remote_webauthn.exe, remote_security_key.exe, or remote_open_url.exe) starts and connects to the pipe, the client will accept the connection.

Since Mojo invitations accepted via IncomingInvitation::Accept unconditionally treat the incoming transport peer as a trusted broker, the client process will trust handles sent from the squatter. The attacker can then send handles with HandleOwner::kRecipient to the client, forcing the medium-integrity client to wrap and close arbitrary handle values in its own process. This arbitrary CloseHandle primitive can lead to handle-reuse type confusion, potential sandbox escape, and local privilege escalation.

Root Cause Analysis

In remoting/host/chromoting_host_services_client.cc (line 39), ConnectToServer is called:

auto endpoint = named_mojo_ipc_server::ConnectToServer(server_name);

Here, named_mojo_ipc_server::ConnectToServer(server_name) constructs a default NamedPlatformChannel::Options object where verify_server_privilege defaults to false.

In mojo/public/cpp/platform/named_platform_channel_win.cc, verify_server_privilege determines whether to verify the integrity level of the pipe server:

if (handle.is_valid() && options.verify_server_privilege) {
  if (!VerifyServerPrivilege(handle.GetHandle().Get())) {
    DLOG(ERROR) << "Server privilege check failed.";
    return PlatformChannelEndpoint();
  }
}

Because the client does not enable this option, it connects to any server offering the pipe, bypassing integrity verification. The client only checks that the server’s session ID is 0, which is easily satisfied by any session-0 process.

Once the connection is accepted via mojo::IncomingInvitation::Accept in chromoting_host_services_client.cc (line 60), the transport is configured with is_peer_trusted = true (mojo/core/ipcz_driver/invitation.cc, line 417).

In mojo/core/ipcz_driver/transport.cc (lines 203-210), when a message is received with HandleOwner::kRecipient, the check for transport trustworthiness passes because is_peer_trusted is true. The client process wraps the received handle value in a ScopedHandle (line 217), which calls CloseHandle on destruction.

Potential Attack Steps

An attacker might perform the following steps to potentially exploit the issue (note that these are suggested/potential steps, and our tooling does not yet have the ability to run code or verify live execution):

  1. A compromised low-privilege process running in session 0 creates the named pipe \\.\pipe\mojo.chromoting.host_services_mojo_ipc before the legitimate service starts (pre-squatting) or registers a pipe instance of the same name.
  2. The user initiates an action triggering a medium-integrity helper process (e.g., remote_webauthn.exe).
  3. The helper process connects to the attacker-controlled named pipe and passes the peer_session_id == 0 check.
  4. The helper process treats the attacker’s pipe as a trusted broker.
  5. The attacker transmits a driver-object frame with handle_owner = HandleOwner::kRecipient containing target handle values of active resources (e.g., process or thread handles).
  6. The helper process decodes the frame and closes its own active handles, leading to handle recycling and potentially arbitrary code execution at medium integrity.

Suggested Fix

To fix this issue, update remoting/host/chromoting_host_services_client.cc to explicitly configure verify_server_privilege = true in the connection options before calling ConnectToServer. For example:

mojo::NamedPlatformChannel::Options options;
options.server_name = server_name;
options.verify_server_privilege = true;
auto endpoint = named_mojo_ipc_server::ConnectToServer(options);

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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