mirror of
https://github.com/uutils/shadow.git
synced 2026-06-10 16:14:57 -07:00
make install: default to standalone per-tool binaries (#138)
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.
This commit is contained in:
@@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Cargo.toml metadata aligned with uutils ecosystem conventions
|
||||
- Tool crate descriptions normalized to `"tool ~ (shadow-rs) verb phrase"` format
|
||||
- Edition 2024 consistently applied across root and workspace packages
|
||||
- `make install` now defaults to 14 standalone per-tool binaries with
|
||||
least-privilege setuid layout matching GNU shadow-utils (#138). Only
|
||||
`passwd`/`chfn`/`chsh`/`newgrp` are setuid-root; the other 10 are `0755`.
|
||||
The previous multicall install is available as `make install-multicall`.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -2,34 +2,60 @@ PREFIX ?= /usr/local
|
||||
BINDIR ?= $(PREFIX)/sbin
|
||||
PROFILE ?= release
|
||||
|
||||
TOOLS = passwd pwck useradd userdel usermod chpasswd chage \
|
||||
groupadd groupdel groupmod grpck chfn chsh newgrp
|
||||
# Tools that need setuid-root to allow non-root callers (change own password,
|
||||
# GECOS, shell, effective group).
|
||||
SETUID_TOOLS = passwd chfn chsh newgrp
|
||||
|
||||
.PHONY: all build test install uninstall clean
|
||||
# Root-only tools (no setuid; fail at getuid() check for non-root callers).
|
||||
ROOT_TOOLS = useradd userdel usermod chpasswd chage \
|
||||
groupadd groupdel groupmod pwck grpck
|
||||
|
||||
ALL_TOOLS = $(SETUID_TOOLS) $(ROOT_TOOLS)
|
||||
|
||||
.PHONY: all build build-multicall test install install-multicall uninstall clean
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
cargo build --profile $(PROFILE)
|
||||
@for tool in $(ALL_TOOLS); do \
|
||||
cargo build --profile $(PROFILE) -p uu_$$tool --bin $$tool || exit 1; \
|
||||
done
|
||||
|
||||
build-multicall:
|
||||
cargo build --profile $(PROFILE) --bin shadow-rs
|
||||
|
||||
test:
|
||||
cargo test --workspace
|
||||
|
||||
# Default install: 14 standalone per-tool binaries with least-privilege setuid
|
||||
# layout matching GNU shadow-utils. Only passwd/chfn/chsh/newgrp are setuid.
|
||||
install: build
|
||||
install -Dm755 target/$(PROFILE)/shadow-rs $(DESTDIR)$(BINDIR)/shadow-rs
|
||||
@for tool in $(TOOLS); do \
|
||||
ln -sf shadow-rs $(DESTDIR)$(BINDIR)/$$tool; \
|
||||
@for tool in $(ALL_TOOLS); do \
|
||||
install -Dm755 target/$(PROFILE)/$$tool $(DESTDIR)$(BINDIR)/$$tool; \
|
||||
done
|
||||
@# Setuid-root for tools that need it. chmod on symlinks affects the
|
||||
@# underlying multicall binary — this is intentional for drop-in
|
||||
@# replacement since all traditional shadow-utils tools are setuid-root.
|
||||
@for tool in passwd chfn chsh newgrp; do \
|
||||
@for tool in $(SETUID_TOOLS); do \
|
||||
chmod 4755 $(DESTDIR)$(BINDIR)/$$tool 2>/dev/null || true; \
|
||||
done
|
||||
@echo "Installed shadow-rs + $(words $(TOOLS)) symlinks to $(DESTDIR)$(BINDIR)/"
|
||||
@echo "Installed $(words $(ALL_TOOLS)) standalone binaries to $(DESTDIR)$(BINDIR)/"
|
||||
@echo " setuid (4755): $(SETUID_TOOLS)"
|
||||
@echo " root-only (0755): $(ROOT_TOOLS)"
|
||||
|
||||
# Opt-in install: single multicall binary with symlinks. Smaller footprint but
|
||||
# larger setuid attack surface — chmod on any setuid symlink follows through to
|
||||
# the ELF, so all tools end up running with euid=root. Intended for
|
||||
# container/embedded use where disk savings matter and attack surface does not.
|
||||
install-multicall: build-multicall
|
||||
install -Dm755 target/$(PROFILE)/shadow-rs $(DESTDIR)$(BINDIR)/shadow-rs
|
||||
@for tool in $(ALL_TOOLS); do \
|
||||
ln -sf shadow-rs $(DESTDIR)$(BINDIR)/$$tool; \
|
||||
done
|
||||
@for tool in $(SETUID_TOOLS); do \
|
||||
chmod 4755 $(DESTDIR)$(BINDIR)/$$tool 2>/dev/null || true; \
|
||||
done
|
||||
@echo "Installed multicall shadow-rs + $(words $(ALL_TOOLS)) symlinks to $(DESTDIR)$(BINDIR)/"
|
||||
|
||||
uninstall:
|
||||
@for tool in $(TOOLS); do \
|
||||
@for tool in $(ALL_TOOLS); do \
|
||||
rm -f $(DESTDIR)$(BINDIR)/$$tool; \
|
||||
done
|
||||
rm -f $(DESTDIR)$(BINDIR)/shadow-rs
|
||||
|
||||
@@ -85,6 +85,25 @@ docker compose build debian
|
||||
docker compose run --rm debian cargo build --release
|
||||
```
|
||||
|
||||
### Install
|
||||
|
||||
Default install: 14 standalone per-tool binaries with least-privilege setuid
|
||||
layout matching GNU shadow-utils. Only `passwd`, `chfn`, `chsh`, `newgrp` are
|
||||
installed setuid-root; the other 10 are plain `0755`.
|
||||
|
||||
```shell
|
||||
sudo make install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
Alternative: single multicall binary with symlinks. Smaller footprint (~14×
|
||||
disk savings) but larger setuid attack surface, since `chmod` on a setuid
|
||||
symlink follows through to the underlying ELF — all 14 tools end up running
|
||||
with `euid=root` when invoked. Intended for container/embedded use cases.
|
||||
|
||||
```shell
|
||||
sudo make install-multicall PREFIX=/usr/local
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
All builds and tests run inside Docker containers to isolate from the host
|
||||
|
||||
Reference in New Issue
Block a user