Fix UID race condition in parallel downloads#21838
Queued
Conversation
On systems where EUID != UID (e.g. setuid wrappers), `Utils::UID.drop_euid` calls `Process::Sys.seteuid` which is process-wide on macOS, causing a race condition when parallel download threads write to directories owned by the effective user while another thread has temporarily dropped the EUID. Add a `run_as_real_uid:` option to `SystemCommand` that, in the forked child only, calls `Process::UID.change_privilege(Process.uid)` to run as the real UID. Use this in `GitHub::API.github_cli_token` and `keychain_username_password` instead of wrapping the `system_command` call in `Utils::UID.drop_euid`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a safer mechanism to run selected subprocesses under the real UID (in the forked child only) to avoid process-wide EUID changes that can race with parallel operations (e.g., parallel downloads) on macOS.
Changes:
- Add
run_as_real_uid:keyword option plumbed throughsystem_command/SystemCommand.runAPIs. - In
SystemCommand’s forked child, optionally callProcess::UID.change_privilege(Process.uid)wheneuid != uid. - Switch GitHub credential retrieval helpers to use
run_as_real_uid:instead ofUtils::UID.drop_euid.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| Library/Homebrew/utils/github/api.rb | Updates credential/token subprocess invocations to run as real UID without process-wide EUID toggling. |
| Library/Homebrew/system_command.rb | Introduces and implements the run_as_real_uid: option in the subprocess execution path. |
Comments suppressed due to low confidence (1)
Library/Homebrew/system_command.rb:199
run_as_real_uidandreset_uidare both accepted and precedence is currently implicit (run_as_real_uidwins). To avoid surprising behavior for future callers, consider raising an ArgumentError when both options are set (or documenting/encoding the intended precedence explicitly).
def initialize(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: false,
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil,
reset_uid: false, run_as_real_uid: false, timeout: nil)
require "extend/ENV"
@executable = executable
@args = args
raise ArgumentError, "`sudo_as_root` cannot be set if sudo is false" if !sudo && sudo_as_root
if print_stdout.is_a?(Symbol) && print_stdout != :debug
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6 tasks
Member
Author
|
These two cannot be used at the same time. When `:run_as_real_uid` is set, `#exec3` just ignores `:reset_uid`, so we should prevent callers from trying to use them simultaneously.
Any commits made after this event will not be merged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
brew lgtm(style, typechecking and tests) with your changes locally?On systems where EUID != UID (e.g. setuid wrappers),
Utils::UID.drop_euidcalls
Process::Sys.seteuidwhich is process-wide on macOS, causing a racecondition when parallel download threads write to directories owned by the
effective user while another thread has temporarily dropped the EUID.
Add a
run_as_real_uid:option toSystemCommandthat, in the forked childonly, calls
Process::UID.change_privilege(Process.uid)to run as the realUID. Use this in
GitHub::API.github_cli_tokenandkeychain_username_passwordinstead of wrapping the
system_commandcall inUtils::UID.drop_euid.Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
As seen above, I used Claude to generate the changes here and the commit. I reviewed the changes carefully. Previously,
brew install gccwould reliably produce errors due to the aforementioned race condition. After this change, it no longer does.More generally,
Utils::UID.drop_euidseems like something that is not safe to do with parallelism, so I intend to fix its remaining caller and remove this method.Note: this is best reviewed while ignoring whitespace changes.