CVE-2026-17877
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
RunAsUserPreExecDelegateremoting/host/linux/linux_process_launcher_delegate.cc |
modified | |
ifremoting/host/linux/linux_process_launcher_delegate.cc |
modified |
Files Changed
remoting/host/linux/linux_process_launcher_delegate.ccremoting/host/linux/linux_process_launcher_delegate.h
Patch
From 5212a57c9737386a8fc5436b466e34b9643cdca3 Mon Sep 17 00:00:00 2001
From: Yuwei Huang <yuweih@chromium.org>
Date: Wed, 10 Jun 2026 18:04:08 -0700
Subject: [PATCH] Support full range of UIDs/GIDs in Linux worker process launcher.
This change uses std::optional<uid_t>/gid_t instead of signed int
to represent the UID and GID to support the full range of IDs.
BUG=522426086
TAG=agy
CONV=90735113-800d-4888-9044-71076d6fef36
Change-Id: If5db60a26dc9c1186aa1a4a9570485920dc61332
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7923507
Auto-Submit: Yuwei Huang <yuweih@chromium.org>
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1645013}
---
diff --git a/remoting/host/linux/linux_process_launcher_delegate.cc b/remoting/host/linux/linux_process_launcher_delegate.cc
index 2c35bff6f..0133a08 100644
--- a/remoting/host/linux/linux_process_launcher_delegate.cc
+++ b/remoting/host/linux/linux_process_launcher_delegate.cc
@@ -9,6 +9,8 @@
#include <sys/types.h>
#include <unistd.h>
+#include <optional>
+
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
@@ -48,8 +50,12 @@
class RunAsUserPreExecDelegate : public base::LaunchOptions::PreExecDelegate {
public:
- RunAsUserPreExecDelegate(bool new_session, int uid, int gid)
- : new_session_(new_session), uid_(uid), gid_(gid) {}
+ RunAsUserPreExecDelegate(bool new_session,
+ std::optional<uid_t> uid,
+ std::optional<gid_t> gid)
+ : new_session_(new_session), uid_(uid), gid_(gid) {
+ CHECK(uid_.has_value() == gid_.has_value());
+ }
~RunAsUserPreExecDelegate() override = default;
RunAsUserPreExecDelegate(const RunAsUserPreExecDelegate&) = delete;
@@ -62,15 +68,15 @@
RAW_LOG(FATAL, "Failed to create a new session.");
}
}
- if (uid_ >= 0 || gid_ >= 0) {
+ if (uid_.has_value() || gid_.has_value()) {
if (setgroups(0, nullptr) != 0) {
RAW_LOG(FATAL, "Failed to clear supplementary groups");
}
}
- if (gid_ >= 0 && setgid(gid_) != 0) {
+ if (gid_.has_value() && setgid(*gid_) != 0) {
RAW_LOG(FATAL, "Failed to setgid");
}
- if (uid_ >= 0 && setuid(uid_) != 0) {
+ if (uid_.has_value() && setuid(*uid_) != 0) {
RAW_LOG(FATAL, "Failed to setuid");
}
// Kill the child process when the parent is dead.
@@ -83,8 +89,8 @@
private:
bool new_session_;
- int uid_;
- int gid_;
+ std::optional<uid_t> uid_;
+ std::optional<gid_t> gid_;
};
} // namespace
diff --git a/remoting/host/linux/linux_process_launcher_delegate.h b/remoting/host/linux/linux_process_launcher_delegate.h
index 5b81488..13b1c38 100644
--- a/remoting/host/linux/linux_process_launcher_delegate.h
+++ b/remoting/host/linux/linux_process_launcher_delegate.h
@@ -5,7 +5,10 @@
#ifndef REMOTING_HOST_LINUX_LINUX_PROCESS_LAUNCHER_DELEGATE_H_
#define REMOTING_HOST_LINUX_LINUX_PROCESS_LAUNCHER_DELEGATE_H_
+#include <sys/types.h>
+
#include <memory>
+#include <optional>
#include "base/command_line.h"
#include "base/environment.h"
@@ -46,13 +49,13 @@
// to be launched, i.e. calling setsid().
bool new_session = false;
- // The effective user ID of the process to be launched. A negative value
+ // The effective user ID of the process to be launched. `std::nullopt`
// indicates no change of the effective user.
- int uid = -1;
+ std::optional<uid_t> uid;
- // The effective group ID of the process to be launched. A negative value
+ // The effective group ID of the process to be launched. `std::nullopt`
// indicates no change of the effective group ID.
- int gid = -1;
+ std::optional<gid_t> gid;
// The working directory of the process to be launched. An empty value
// indicates no change of the working directory.
Original Bug Report
Potential LPE in CRD Linux host due to signedness error preventing privilege drop for high UIDs
Flapjack, 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: The Chrome Remote Desktop (CRD) Linux daemon uses signed integers to store the target UID and GID when spawning worker processes. If a user has a UID/GID greater than or equal to 2,147,483,648, the IDs are cast to negative values, causing the privilege-dropping checks to fail. This potentially allows the user’s CRD session to run with inherited root privileges, leading to a Local Privilege Escalation (LPE).
Affected files:
remoting/host/linux/linux_process_launcher_delegate.ccremoting/host/linux/linux_process_launcher_delegate.hremoting/host/daemon_process_linux.ccremoting/host/linux/desktop_session_factory_linux.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
There is a potential Local Privilege Escalation (LPE) vulnerability in the Chrome Remote Desktop (CRD) Linux host. The root-privileged CRD daemon spawns worker processes (Network and Desktop) for connecting users and drops privileges to match the user’s identity. However, the variables used to store the user’s UID and GID during the launch process are typed as signed 32-bit integers (int). If an environment uses large UIDs or GIDs (>= 2,147,483,648), which is common in enterprise setups integrating Active Directory via SSSD, these IDs implicitly cast to negative numbers. This negative value bypasses the checks meant to trigger the privilege dropping functions (setuid, setgid, setgroups), causing the worker processes to execute as root.
Technical Details
- The system-wide CRD daemon runs as
root. - When preparing to launch the network or desktop process for a user, the daemon fetches their system information using
GetPasswdUserInfo(username)(remoting/base/passwd_utils.cc). This correctly populates aPasswdUserInfostruct containing unsigneduid_tandgid_tfields. - The daemon copies these values into a
LinuxWorkerProcessLauncherDelegate::LaunchOptionsstruct (remoting/host/linux/linux_process_launcher_delegate.h). However, this struct definesuidandgidas signedint. For UIDs >=2^31, this causes an implicit cast to a negative value. - These negative values are passed to the constructor of
RunAsUserPreExecDelegate(remoting/host/linux/linux_process_launcher_delegate.cc), which also stores them asint uid_andint gid_. - During
base::LaunchProcess, a child process is forked (inheriting root privileges). Beforeexecis called,RunAsUserPreExecDelegate::RunAsyncSafe()executes in the child process to drop privileges. RunAsyncSafe()checksif (uid_ >= 0 || gid_ >= 0)to clear supplementary groups, andif (uid_ >= 0 && setuid(uid_) != 0)to set the user ID. Because the values are negative, these checks evaluate tofalse.- The
setuid,setgid, andsetgroupscalls are completely bypassed. Theexeccall proceeds, and the worker processes run asrootinstead of the connecting user.
(Note: These are potential steps, as our tooling cannot currently run code to provide a functional Proof of Concept.)
Potential Attacker Steps
- The attacker must possess legitimate, but unprivileged, credentials for a Linux system running the system-wide CRD daemon.
- The system’s identity provider (e.g., Active Directory) must assign the attacker a UID or GID >= 2,147,483,648.
- The attacker establishes a Chrome Remote Desktop connection to the target system.
- The resulting Remote Desktop session and associated network processes will automatically run with
rootprivileges, allowing the attacker to completely compromise the system.
Suggested Remediation
- Update
LinuxWorkerProcessLauncherDelegate::LaunchOptions(remoting/host/linux/linux_process_launcher_delegate.h) to useuid_t uid;andgid_t gid;instead ofint. - Update
RunAsUserPreExecDelegate(remoting/host/linux/linux_process_launcher_delegate.cc) to storeuid_t uid_;andgid_t gid_;. - Since
uid_tandgid_tare unsigned and cannot represent a negative “no change” value, usestd::optional<uid_t>andstd::optional<gid_t>, or define a sentinel value (e.g.,(uid_t)-1) to indicate when the IDs should not be modified, and explicitly check against that state rather than using>= 0.
Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff
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.