CVE-2026-84334
Overview
Background
- Chromoting
- Chrome Remote Desktop’s host component, which exposes host services to local clients over a named Mojo IPC pipe.
- Named Mojo Pipe Server
- A Windows named-pipe endpoint (
named_mojo_ipc_server) that brokers Mojo IPC connections between the Chromoting host process and connecting client processes. - `WTSQueryUserToken`
- A Windows Terminal Services API that retrieves the access token of the user logged into a given session, requiring
SE_TCB_NAMEprivilege held byLocalSystem. - User SID
- The Security Identifier uniquely naming a Windows user account, used here to confirm a connecting process belongs to the legitimate session owner.
Root Cause Analysis
On Windows, IsTrustedMojoEndpoint in remoting/host/mojo_caller_security_checker.cc authorized a connecting Mojo client solely by verifying that the caller’s process image was code-signed and trusted via IsBinaryTrusted(caller_process_image_path). This check confirmed what binary connected but never confirmed who owned the connecting process, so any local user running a trusted, code-signed Chromoting binary could connect to a host service running in another user’s session, violating the invariant that a client must belong to the same session user the host is serving. Additionally, the Named Mojo Pipe Server did not mark client invitations as untrusted, leaving the Mojo broker reachable to a connecting process that should not have full channel-broker capabilities.
The fix adds IsWinCallerUserSidValid, which computes the expected session-owner SID (via WTSQueryUserToken when running as LocalSystem, or the current process user as a developer fallback) and rejects any caller whose process-token User SID does not match. It also sets MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS on invitations so the broker restricts what the client can do. Together these restore the requirement that a client be both a trusted binary and owned by the correct session user.
IsWinCallerUserSidValid) in addition to the binary-trust check, and by marking invitations MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS.Attack Path
- Local foothold
An attacker runs an unprivileged process in their own Windows logon session on a machine where the Chromoting host service runs as
LocalSystem. - Locate the pipe
The attacker connects to the host’s named Mojo pipe exposed by the
Named Mojo Pipe Server, which serves another session’s user. - Pass the weak check
Because authorization only required
IsBinaryTrusted, the attacker leverages a trusted, code-signed Chromoting binary so the endpoint accepts the connection despite the caller belonging to a different user. - Cross-session access The host treats the attacker’s process as a legitimate client and grants access to host-services IPC that should have been scoped to the active session user.
- Privilege escalation
By interacting with services brokered by the
LocalSystem-owned host, the attacker gains capabilities beyond their own account, escalating local privilege.
Impact Assessment
LocalSystem host process, enabling local privilege escalation. This occurs on Windows in the host-services IPC channel exposed by the Named Mojo Pipe Server. Preconditions are local code execution on the machine, a running Chromoting host, and possession of (or ability to run) a code-signed trusted Chromoting binary to satisfy the pre-fix binary check.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifremoting/host/mojo_caller_security_checker.cc |
modified | |
ifremoting/host/BUILD.gn |
modified |
Files Changed
remoting/host/BUILD.gnremoting/host/chromoting_host_services_server.ccremoting/host/mojo_caller_security_checker.cc
Audit Directions
- Binary trust without identityFlag any IPC authorization that trusts a caller based on process image signing (
IsBinaryTrusted) without also verifying the caller’s owning user or session SID. - Cross-session Windows IPCReview named-pipe and Mojo endpoints reachable across Windows sessions to ensure they validate the connecting process token against the intended session owner, e.g. via
WTSQueryUserTokenandbase::win::AccessToken::FromProcess. - Unrestricted Mojo invitationsCheck that Mojo invitations sent to less-privileged clients set
MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESSto limit broker capabilities granted to the peer.
Patch
From 8941f7c9070f1e9e6c7cd465f1072e1700630e36 Mon Sep 17 00:00:00 2001 From: Yuwei Huang <[email protected]> Date: Mon, 01 Jun 2026 13:25:44 -0700 Subject: [PATCH] Harden Windows Named Mojo Pipe Server for Chromoting Host Services Harden the Named Mojo Pipe Server on Windows against potential local privilege escalation. Mitigations: * Set MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS on client invitations to restrict Mojo channel broker access. * Enforce session-aware User SID verification of the client process. * Add a safe fallback for local user-mode developer environments. * Use move semantics for token extraction and explicitly link wtsapi32.lib. Bug: 517798926 Change-Id: I406538b61990fd35084904f433cfc7f37cbbb171 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7886655 Auto-Submit: Yuwei Huang <[email protected]> Reviewed-by: Joe Downing <[email protected]> Commit-Queue: Yuwei Huang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1639627} --- diff --git a/remoting/host/chromoting_host_services_server.cc b/remoting/host/chromoting_host_services_server.cc index 77c6ce0..8cc4dce 100644 --- a/remoting/host/chromoting_host_services_server.cc +++ b/remoting/host/chromoting_host_services_server.cc @@ -23,6 +23,7 @@ #if BUILDFLAG(IS_WIN) #include "base/strings/strcat_win.h" #include "base/win/win_util.h" +#include "mojo/public/c/system/invitation.h" #endif namespace remoting { @@ -44,6 +45,8 @@ options.security_descriptor = base::StrCat({L"O:", user_sid, L"G:", user_sid, L"D:(A;;GA;;;AU)"}); options.include_peer_process_info = true; + options.extra_send_invitation_flags = + MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS; #elif BUILDFLAG(IS_LINUX) // Allow the endpoint to be connected by any users iff the server is run as // root. diff --git a/remoting/host/mojo_caller_security_checker.cc b/remoting/host/mojo_caller_security_checker.cc index b0080bb..20bddec 100644 --- a/remoting/host/mojo_caller_security_checker.cc +++ b/remoting/host/mojo_caller_security_checker.cc @@ -26,6 +26,14 @@ #endif #if BUILDFLAG(IS_WIN) +#include <windows.h> + +#include <wtsapi32.h> + +#include "base/strings/utf_string_conversions.h" +#include "base/win/access_token.h" +#include "base/win/scoped_handle.h" +#include "base/win/sid.h" #include "remoting/host/win/trust_util.h" #endif @@ -56,6 +64,72 @@ std::to_array<const std::string_view>({kBundleId, "remote_webauthn"}); #endif +#if BUILDFLAG(IS_WIN) +bool IsWinCallerUserSidValid( + const named_mojo_ipc_server::ConnectionInfo& caller) { + std::optional<base::win::AccessToken> current_token = + base::win::AccessToken::FromCurrentProcess(); + if (!current_token.has_value()) { + PLOG(ERROR) << "Failed to open current process token."; + return false; + } + + std::optional<base::win::Sid> expected_sid; + + // Verify the client's identity depending on the host service context: + // - If running as `LocalSystem` (standard service/production mode), the + // host has the necessary TCB privileges (`SE_TCB_NAME`) to query the + // legitimate active session user's token via `WTSQueryUserToken`. + // - If running as a standard user process (developer diagnostics/testing + // environments or developer unit tests), the host lacks TCB privileges + // to query other session tokens. In this case, the active remote session's + // owner is simply the current process owner itself, so we securely fall + // back to validating the client against the current host process token. + if (current_token->User() == + base::win::Sid::FromKnownSid(base::win::WellKnownSid::kLocalSystem)) { + // SYSTEM Mode: Retrieve the target Windows session owner's User SID. + HANDLE session_token_raw = nullptr; + if (!::WTSQueryUserToken(caller.session_id, &session_token_raw)) { + PLOG(ERROR) << "WTSQueryUserToken failed for session ID " + << caller.session_id; + return false; + } + base::win::ScopedHandle session_token_handle(session_token_raw); + + std::optional<base::win::AccessToken> session_token = + base::win::AccessToken::FromToken(std::move(session_token_handle)); + if (!session_token.has_value()) { + PLOG(ERROR) << "Failed to get access token from session token handle."; + return false; + } + expected_sid = session_token->User(); + } else { + // User/Developer Mode Fallback: Use current process User SID as the + // expected value. + expected_sid = current_token->User(); + } + + std::optional<base::win::AccessToken> client_token = + base::win::AccessToken::FromProcess(caller.process.Handle()); + if (!client_token.has_value()) { + PLOG(ERROR) << "Failed to open client process token for PID " << caller.pid; + return false; + } + base::win::Sid client_sid = client_token->User(); + + if (client_sid != *expected_sid) { + LOG(ERROR) << "Client user SID (" + << base::WideToUTF8(client_sid.ToSddlString().value_or(L"")) + << ") does not match expected user SID (" + << base::WideToUTF8(expected_sid->ToSddlString().value_or(L"")) + << ")"; + return false; + } + + return true; +} +#endif // BUILDFLAG(IS_WIN) + } // namespace bool IsTrustedMojoEndpoint( @@ -114,7 +188,10 @@ return false; } #if BUILDFLAG(IS_WIN) - return IsBinaryTrusted(caller_process_image_path); + if (!IsBinaryTrusted(caller_process_image_path)) { + return false; + } + return IsWinCallerUserSidValid(caller); #else // Linux binaries are not code-signed, so we just return true. return true; diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn index 302f780a..5a78f0fb 100644 --- a/remoting/host/BUILD.gn +++ b/remoting/host/BUILD.gn @@ -356,6 +356,7 @@ } if (is_win) { deps += [ "//remoting/host/win:trust_util" ] + libs = [ "wtsapi32.lib" ] } }
Original Bug Report
Local Privilege Escalation in Chrome Remote Desktop via Process Hollowing
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: Chrome Remote Desktop on Windows contains a potential security bypass where Mojo endpoint validation verifies the signature of a caller’s on-disk binary rather than its executing memory. A local unprivileged attacker could spawn an allowed binary in a suspended state, inject code, and connect to the LocalSystem named pipe. The service accepts the hollowed process as a trusted Mojo peer, enabling potential local privilege escalation.
Affected files:
remoting/host/mojo_caller_security_checker.ccremoting/host/win/trust_util.ccremoting/host/base/process_util.cccomponents/named_mojo_ipc_server/named_mojo_message_pipe_server.ccremoting/host/chromoting_host_services_server.cc
Estimated timestamp from git blame: 2022-02-25
Summary
A potential security bypass and local privilege escalation vulnerability exists in Chrome Remote Desktop for Windows. The validation mechanism in IsTrustedMojoEndpoint verifies the Authenticode signature of the caller’s on-disk binary rather than the running code executing inside the process memory space. This allows a local, non-privileged attacker to spawn an allowed binary in a suspended state, inject arbitrary code, and connect to the LocalSystem daemon’s named pipe. Consequently, the injected process is accepted as a fully-trusted Mojo peer of the LocalSystem broker.
Affected Code Paths
- Named Pipe Setup:
ChromotingHostServicesServer[remoting/host/chromoting_host_services_server.cc] exposes the named pipe\\.\pipe\chromoting.host_services_mojo_ipcwith a DACL ofD:(A;;GA;;;AU)(all Authenticated Users can connect). - Caller Identity Query: When a client connects, the service queries the client’s PID and opens a process handle with
PROCESS_QUERY_LIMITED_INFORMATIONaccess. - Path and Signature Verification:
IsTrustedMojoEndpoint[remoting/host/mojo_caller_security_checker.cc] queries the process image path viaQueryFullProcessImageNameand verifies the cryptographic signature withIsBinaryTrusted[remoting/host/win/trust_util.cc].
The Security Flaw
The verification mechanism relies on:
QueryFullProcessImageNameto resolve the client’s path. However, the Windows kernel resolves this to the original executable image that was loaded into the section object during process creation. If the process is subsequently hollowed or injected with code, the returned path remains unchanged.WinVerifyTrustcalled withdwUnionChoice = WTD_CHOICE_FILE. This validates the signature of the file resting on disk rather than the actual code executing in memory.
Potential Attack Steps
An attacker could theoretically follow these steps to trigger the bypass:
- Launch one of the allowed binaries (e.g.,
remote_open_url.exe) in a suspended state (CREATE_SUSPENDED). - Write a malicious payload into the process memory using standard injection APIs (e.g.,
VirtualAllocEx/WriteProcessMemory). - Resume the thread, allowing the payload to execute and connect to
\\.\pipe\chromoting.host_services_mojo_ipc. - The service resolves the client’s process path to the legitimate executable and confirms its signature on disk. The connection is accepted as a trusted, non-isolated Mojo peer.
Note: Since our testing tools do not have execution capability, these steps represent a potential attack path based on static code analysis and Windows API behaviors.
Suggested Remediation
To fix this issue, Chrome Remote Desktop should avoid relying on on-disk signature validation of client processes. Potential remedies include:
- Mojo Isolation: Restrict the Mojo connection by setting the client invitation as isolated or untrusted (using
MOJO_SEND_INVITATION_FLAG_UNTRUSTED_PROCESS). This prevents a compromised client process from gaining control over the Mojo broker channel and performing privilege escalation. - Access Control Tokens: Rather than relying solely on signature verification of the executable, utilize operating-system-level sandbox, AppContainer, or explicit user SID checks on the client process token to restrict access to the host services pipe.
Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379
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.