make install: default to standalone per-tool binaries (#138)#139
Conversation
Switch the default install to build and install 14 standalone per-tool binaries with a least-privilege setuid layout matching GNU shadow-utils. Only passwd/chfn/chsh/newgrp are setuid-root; the other 10 tools are 0755. The previous multicall install is preserved as `make install-multicall` for container/embedded use cases where the ~14x disk savings matter and the enlarged setuid attack surface is acceptable. The old layout chmod'd symlinks pointing to the multicall binary, which (because chmod follows symlinks) marked the underlying ELF setuid-root. That meant all 14 tools ran with euid=0 when invoked via symlink. Each tool's internal getuid() check was defense-in-depth, not OS-level least privilege. The standalone layout gives the latter. Raised by @oech3 in uutils/coreutils#11828.
There was a problem hiding this comment.
Pull request overview
Updates the project’s installation workflow to default to a least-privilege layout by installing 14 standalone tool binaries, avoiding the “setuid-on-multicall-ELF” problem caused by chmod-following symlinks. Keeps the previous multicall + symlinks install as an opt-in target for footprint-sensitive environments.
Changes:
- Change
make installto build/install 14 per-tool binaries and apply setuid only topasswd,chfn,chsh,newgrp. - Add
make install-multicall(andbuild-multicall) to preserve the legacy multicall + symlink installation behavior. - Document the new default/alternative install flows in
README.mdand note the change inCHANGELOG.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| README.md | Documents default standalone install vs. opt-in multicall install and the security rationale. |
| Makefile | Implements standalone per-tool build/install by default; adds multicall build/install targets. |
| CHANGELOG.md | Records the install default change and mentions the multicall alternative target. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address review comments on #139: - build: replace 14-call loop with a single `cargo build --workspace --bins --exclude shadow-rs` invocation, eliminating per-tool Cargo startup/metadata overhead. Matches the uutils/coreutils GNUmakefile pattern. - install: use `install -Dm4755` / `install -Dm0755` to set mode atomically during install instead of post-install chmod. Errors no longer swallowed: if install can't set the setuid bit, `make install` now fails loudly. - install-multicall: same — `install -Dm4755` sets the shadow-rs binary setuid in one step.
There was a problem hiding this comment.
Pull request overview
This PR changes the default make install behavior to install standalone per-tool binaries with least-privilege permissions, reducing the setuid-root footprint compared to the previous multicall+symlink layout.
Changes:
- Make default
installdeploy 14 standalone binaries with4755only forpasswd/chfn/chsh/newgrpand0755for the remaining tools. - Add opt-in
install-multicall(setuid multicall binary + symlinks) andbuild-multicalltargets. - Document the new install modes in
README.mdand record the behavior change inCHANGELOG.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| README.md | Documents default standalone install and alternative multicall install. |
| Makefile | Implements standalone install by default, adds multicall build/install targets, updates uninstall to remove all tools. |
| CHANGELOG.md | Notes the new default make install layout and the install-multicall fallback. |
Comments suppressed due to low confidence (1)
Makefile:57
uninstallnow removes all standalone tool binaries/symlinks (andshadow-rs), but the status line only says "Uninstalled shadow-rs". This is misleading for the new default standalone layout; consider updating the message to reflect what was actually removed (e.g., tools + multicall binary) or at least make it layout-agnostic.
uninstall:
@for tool in $(ALL_TOOLS); do \
rm -f $(DESTDIR)$(BINDIR)/$$tool; \
done
rm -f $(DESTDIR)$(BINDIR)/shadow-rs
@echo "Uninstalled shadow-rs from $(DESTDIR)$(BINDIR)/"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review round 2: - Remove PROFILE variable and hardcode --release / target/release/. Cargo maps "dev" profile to target/debug/ (not target/dev/), so PROFILE=dev would silently break install. Nobody installs debug builds of setuid-root tools. - Fix stale comments in Makefile and README that referenced the old "chmod on symlink follows through" approach. The current code uses install -Dm4755 directly.
There was a problem hiding this comment.
Pull request overview
Updates the install workflow to default to per-tool standalone binaries so setuid is limited to the small subset of tools that require it, reducing the privilege footprint compared to the multicall + symlink approach.
Changes:
- Change
make installto build/install 14 standalone tool binaries with setuid only onpasswd/chfn/chsh/newgrp. - Add
build-multicall/install-multicalltargets to preserve the multicall + symlink installation as an opt-in. - Document the new default and multicall alternative in README and CHANGELOG.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| README.md | Documents default standalone install vs opt-in multicall install. |
| Makefile | Implements standalone install as default; adds multicall build/install targets. |
| CHANGELOG.md | Notes the change in default install behavior and availability of multicall install. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Switch the default
make installto build and install 14 standalone per-tool binaries with a least-privilege setuid layout matching GNU shadow-utils. Raised by @oech3 in uutils/coreutils#11828.Fixes #138.
The problem
The old layout installed one multicall
shadow-rsbinary with 14 symlinks, thenchmod 4755on thepasswd/chfn/chsh/newgrpsymlinks. Becausechmodon a symlink follows through to the ELF target, the underlying binary ended up setuid root — souseradd,usermod,userdel, etc. all ran with `euid=0` when invoked via symlink. Each tool's internal `getuid() == 0` check was defense-in-depth, not OS-level least privilege.The fix
`make install` now builds each tool as its own binary (each crate already has a `[[bin]]` entry) and installs them individually:
This matches GNU shadow-utils' install layout exactly.
The previous multicall install is preserved as `make install-multicall` for container/embedded use cases where the ~14× disk savings matter and the enlarged setuid attack surface is acceptable.
Test plan
Verified in Docker (debian image):