Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Chromoting
DescriptionInappropriate implementation in Chromoting
ComponentChromoting
Bug ClassLogic Error
Tracker522426086
Fix commit5212a57c9737 (chromium/src) +20/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
RunAsUserPreExecDelegate
remoting/host/linux/linux_process_launcher_delegate.cc
modified
if
remoting/host/linux/linux_process_launcher_delegate.cc
modified

Files Changed

  • remoting/host/linux/linux_process_launcher_delegate.cc
  • remoting/host/linux/linux_process_launcher_delegate.h
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.
Loading diff…

Original Bug Report

reported by rj...@google.com

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.cc
  • remoting/host/linux/linux_process_launcher_delegate.h
  • remoting/host/daemon_process_linux.cc
  • remoting/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

  1. The system-wide CRD daemon runs as root.
  2. 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 a PasswdUserInfo struct containing unsigned uid_t and gid_t fields.
  3. The daemon copies these values into a LinuxWorkerProcessLauncherDelegate::LaunchOptions struct (remoting/host/linux/linux_process_launcher_delegate.h). However, this struct defines uid and gid as signed int. For UIDs >= 2^31, this causes an implicit cast to a negative value.
  4. These negative values are passed to the constructor of RunAsUserPreExecDelegate (remoting/host/linux/linux_process_launcher_delegate.cc), which also stores them as int uid_ and int gid_.
  5. During base::LaunchProcess, a child process is forked (inheriting root privileges). Before exec is called, RunAsUserPreExecDelegate::RunAsyncSafe() executes in the child process to drop privileges.
  6. RunAsyncSafe() checks if (uid_ >= 0 || gid_ >= 0) to clear supplementary groups, and if (uid_ >= 0 && setuid(uid_) != 0) to set the user ID. Because the values are negative, these checks evaluate to false.
  7. The setuid, setgid, and setgroups calls are completely bypassed. The exec call proceeds, and the worker processes run as root instead 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

  1. The attacker must possess legitimate, but unprivileged, credentials for a Linux system running the system-wide CRD daemon.
  2. The system’s identity provider (e.g., Active Directory) must assign the attacker a UID or GID >= 2,147,483,648.
  3. The attacker establishes a Chrome Remote Desktop connection to the target system.
  4. The resulting Remote Desktop session and associated network processes will automatically run with root privileges, allowing the attacker to completely compromise the system.

Suggested Remediation

  1. Update LinuxWorkerProcessLauncherDelegate::LaunchOptions (remoting/host/linux/linux_process_launcher_delegate.h) to use uid_t uid; and gid_t gid; instead of int.
  2. Update RunAsUserPreExecDelegate (remoting/host/linux/linux_process_launcher_delegate.cc) to store uid_t uid_; and gid_t gid_;.
  3. Since uid_t and gid_t are unsigned and cannot represent a negative “no change” value, use std::optional<uid_t> and std::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.

View on issue tracker