Skip to content

Commit c680d90

Browse files
committed
Fix root-as-dropped-euid exec env and restore Binary.load_or_install
- Binary.load_or_install: restore the "try load(), fall back to install()" helper that abx-plugins hooks rely on after it got inadvertently dropped. - BinProvider.exec: when running as root and dropping privileges to a non-root target UID, use sudo_env (target user's HOME/LOGNAME/USER) instead of fallback_env so the dropped subprocess finds its own cache/ config dirs (fixes brew "Permission denied ~/.cache/Homebrew" under root). - BrewProvider._refresh_bin_link: when running as root but about to drop to brew's owner UID, chmod the managed shim dir's ancestors to be traversable by that UID so version probes from the dropped-privilege subprocess can reach the symlink. - README: fix cargo_root alias typo in the CargoProvider section.
1 parent 322aaaf commit c680d90

4 files changed

Lines changed: 63 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ PATH = "" # prepends cargo_root/bin and cargo_home/bi
952952
cargo_root = None # set this for hermetic installs
953953
```
954954

955-
- Install root: set `install_root=Path(...)` or `install_root=Path(...)` for isolated installs under `<cargo_root>/bin`; otherwise installs go through `cargo_home`.
955+
- Install root: set `install_root=Path(...)` or `cargo_root=Path(...)` for isolated installs under `<cargo_root>/bin`; otherwise installs go through `cargo_home`.
956956
- Auto-switching: none.
957957
- `dry_run`: shared behavior.
958958
- Security: `min_release_age` and `postinstall_scripts=False` are unsupported and are ignored with a warning if explicitly requested.

abxpkg/binary.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,39 @@ def load(
451451
)
452452
raise BinaryLoadError(self.name, provider_names, errors) from inner_exc
453453

454+
@validate_call
455+
@log_method_call(include_result=True)
456+
def load_or_install(
457+
self,
458+
binproviders: list[BinProviderName] | None = None,
459+
no_cache: bool = False,
460+
dry_run: bool | None = None,
461+
postinstall_scripts: bool | None = None,
462+
min_release_age: float | None = None,
463+
**extra_overrides,
464+
) -> Self:
465+
"""Try ``load()`` first; fall back to ``install()`` if no provider
466+
resolves the binary. Returns the loaded/installed copy of ``self``.
467+
"""
468+
try:
469+
loaded = self.load(
470+
binproviders=binproviders,
471+
no_cache=no_cache,
472+
**extra_overrides,
473+
)
474+
except BinaryLoadError:
475+
loaded = self
476+
if loaded.is_valid and not no_cache:
477+
return loaded
478+
return self.install(
479+
binproviders=binproviders,
480+
no_cache=no_cache,
481+
dry_run=dry_run,
482+
postinstall_scripts=postinstall_scripts,
483+
min_release_age=min_release_age,
484+
**extra_overrides,
485+
)
486+
454487
@validate_call
455488
@log_method_call(include_result=True)
456489
def update(

abxpkg/binprovider.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1732,10 +1732,18 @@ def drop_privileges():
17321732
sudo_proc.stderr,
17331733
)
17341734

1735+
# When running as root but dropping to a non-root user (e.g. brew),
1736+
# use the target user's HOME/LOGNAME/USER env so the dropped-privilege
1737+
# subprocess finds its own cache/config dirs instead of root's.
1738+
dropped_env = (
1739+
sudo_env
1740+
if current_euid == 0 and run_as_uid != current_euid
1741+
else fallback_env
1742+
)
17351743
proc = subprocess.run(
17361744
cmd,
17371745
cwd=str(cwd_path),
1738-
env=fallback_env,
1746+
env=dropped_env,
17391747
preexec_fn=drop_privileges,
17401748
**kwargs,
17411749
)

abxpkg/binprovider_brew.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,26 @@ def _refresh_bin_link(
163163
link_path = self._linked_bin_path(bin_name)
164164
assert link_path is not None, "_refresh_bin_link requires bin_dir to be set"
165165
link_path.parent.mkdir(parents=True, exist_ok=True)
166+
# When running as root but brew itself drops to its owner uid via
167+
# ``self.exec``, we also need the managed shim dir (and its parents)
168+
# traversable by that target uid so version probes / load() calls from
169+
# the dropped-privilege subprocess can reach the symlink.
170+
current_euid = os.geteuid()
171+
target_uid = self.EUID
172+
if current_euid == 0 and target_uid not in (0, current_euid):
173+
try:
174+
pw_record = self.get_pw_record(target_uid)
175+
os.chown(link_path.parent, target_uid, pw_record.pw_gid)
176+
except Exception:
177+
pass
178+
walk_path = link_path.parent
179+
while walk_path != walk_path.parent:
180+
try:
181+
mode = walk_path.stat().st_mode
182+
walk_path.chmod(mode | 0o055)
183+
except Exception:
184+
break
185+
walk_path = walk_path.parent
166186
if link_path.exists() or link_path.is_symlink():
167187
link_path.unlink(missing_ok=True)
168188
link_path.symlink_to(target)

0 commit comments

Comments
 (0)