Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Updater
DescriptionInappropriate implementation in Updater
ComponentUpdater
Bug ClassLogic Error
Tracker520494861
Fix commitfd27327ef76a (chromium/src) +30/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • chrome/updater/app/app_net_worker.cc
  • chrome/updater/constants.h
From fd27327ef76a70083c37a4bb4c92fcb2774a171f Mon Sep 17 00:00:00 2001
From: Noah Rose Ledesma <[email protected]>
Date: Sun, 07 Jun 2026 22:50:08 -0700
Subject: [PATCH] Become "nobody" in updater macOS network fetcher

When launching the out of process network fetcher on macOS, drop down to
to the "nobody" uid & gid. This strategy is used by CECA.

Fixed: 520494861
Change-Id: Ie750b2507b3a16e9fe9ecb4caffd5cd26a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7902379
Commit-Queue: Noah Rose Ledesma <[email protected]>
Reviewed-by: Adam Norberg <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1642996}
---

diff --git a/chrome/updater/app/app_net_worker.cc b/chrome/updater/app/app_net_worker.cc
index 89b3497..1786606c 100644
--- a/chrome/updater/app/app_net_worker.cc
+++ b/chrome/updater/app/app_net_worker.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/updater/app/app_net_worker.h"
 
+#include <grp.h>
+#include <unistd.h>
+
 #include <cstdint>
 #include <memory>
 #include <optional>
@@ -15,6 +18,7 @@
 #include "base/containers/flat_map.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
+#include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
@@ -233,6 +237,29 @@
   ~AppNetWorker() override = default;
 
   void FirstTaskRun() override {
+    static constexpr uid_t kNobodyUid = -2;
+    static constexpr gid_t kNobodyGid = -2;
+
+    // If running as root, drop down to "nobody".
+    if (getuid() == 0) {
+      // Clear supplementary groups inherited from root.
+      if (initgroups("nobody", kNobodyGid) != 0) {
+        VPLOG(1) << "Failed to initgroups";
+        Shutdown(kErrorFailedToDropPrivileges);
+        return;
+      }
+      if (setgid(kNobodyGid) != 0) {
+        VPLOG(1) << "Failed to set gid " << kNobodyGid;
+        Shutdown(kErrorFailedToDropPrivileges);
+        return;
+      }
+      if (setuid(kNobodyUid) != 0) {
+        VPLOG(1) << "Failed to set uid " << kNobodyUid;
+        Shutdown(kErrorFailedToDropPrivileges);
+        return;
+      }
+    }
+
     // This process must be started with the command line switch
     /// `--mojo-platform-channel-handle=N`. In other words, the command line
     // must be prepared by
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h
index edf150ed..dc95cf1 100644
--- a/chrome/updater/constants.h
+++ b/chrome/updater/constants.h
@@ -569,6 +569,9 @@
 // A path references the parent directory.
 inline constexpr int kErrorPathReferencesParent = kUpdaterErrorBase + 84;
 
+// The net-worker subprocess failed to drop root privileges.
+inline constexpr int kErrorFailedToDropPrivileges = kUpdaterErrorBase + 85;
+
 // Policy Management constants.
 // The maximum value allowed for policy AutoUpdateCheckPeriodMinutes.
 inline constexpr int kMaxAutoUpdateCheckPeriodMinutes = 43200;
Loading diff…

Original Bug Report

reported by [email protected]

Potential local privilege escalation via root-privileged updater net-worker in user namespace

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: The macOS system-scope Chrome updater falls back to spawning an out-of-process net-worker via launchctl asuser under the console user’s UID when in-process fetches fail. While this switches the Mach bootstrap namespace, the helper process continues to run with root privileges (EUID 0) without dropping privileges. This potentially allows an unprivileged local user to shadow system-level Mach services in their bootstrap namespace and intercept requests from the root-privileged process.

Affected files:

  • chrome/updater/app/app_net_worker.cc
  • chrome/updater/net/network_fetcher_mac.mm

Estimated timestamp from git blame: 2024-08-05

Detailed Description

On macOS, the system-scope (root) Chrome updater contains a fallback mechanism for network fetching. If the primary in-process network fetch fails, OutOfProcessNetworkFetcher::DialFetchService() (located in chrome/updater/net/network_fetcher_mac.mm) launches a helper process using the following command format:

/bin/launchctl asuser <console_uid> <updater_path> --net-worker

Per Apple’s documentation, launchctl asuser configures the spawned process to run within the target console user’s Mach bootstrap namespace and audit session, but it does not change the user ID (UID/EUID). As a result, the spawned --net-worker subprocess continues to run with root privileges (euid=0) but is situated inside the unprivileged console user’s bootstrap namespace.

Upon startup, the subprocess entry point AppNetWorker::FirstTaskRun() (in chrome/updater/app/app_net_worker.cc) recovers the Mojo endpoint and initiates networking operations via NSURLSession/CFNetwork. No privilege dropping (setuid/setgid) is performed.

Because the root-privileged net-worker is running within the unprivileged user’s bootstrap namespace, any Mach service lookups performed by its network stacks (such as proxy configuration lookups or helper XPC services) are resolved within the user’s namespace first. An unprivileged local user who has control over their own namespace could theoretically register shadow or proxy endpoints for these service names. If the root-privileged process then attempts to resolve and communicate with these services, the communication could be intercepted or manipulated.

This behavior contrasts with similar components in the codebase. For instance, the Chrome Enterprise Companion app’s net-worker (chrome/enterprise_companion/app/app_net_worker.cc) explicitly drops privileges to nobody using setgid and setuid if it detects it is running as root.

Potential Trigger Steps

Note: These are theoretical steps describing how the condition could be reached; no working exploit code has been executed by our tooling.

  1. A standard (non-admin) user is logged into the macOS console.
  2. The user registers a helper or proxy service under a well-known Mach/XPC service name (e.g., those consumed by system network libraries for proxy resolution) within their user bootstrap namespace.
  3. The system-scope Chrome updater experiences a network failure or error during an update check, triggering the fallback path to launch the out-of-process --net-worker via launchctl asuser.
  4. The --net-worker subprocess starts with root privileges inside the user’s bootstrap namespace and attempts to resolve network configuration or resolve dependencies via Mach lookups.
  5. The lookups resolve to the user-controlled shadow service instead of the secure system-level services.

Proposed Fix

To remediate this issue, the --net-worker helper process should drop privileges to an unprivileged user (such as nobody or the console user’s identity) before performing any network operations or IPC initialization, similar to the implementation in the enterprise companion app:

if (getuid() == 0) {
  // Clear supplementary groups and set unprivileged UID/GID
  if (initgroups("nobody", kNobodyGid) == 0) {
    setgid(kNobodyGid);
    setuid(kNobodyUid);
  }
}

Alternatively, ensure that the helper does not run inside the user’s bootstrap namespace if root privileges are strictly required, or drop privileges to the console user’s UID/GID after obtaining necessary resources.

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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