security: fix setuid-root privilege bypass (getuid vs geteuid)

Found by GitHub Copilot review on PRs #9, #10, #17.

CRITICAL: is_root() used geteuid() which is ALWAYS 0 when passwd is
installed setuid-root. Any unprivileged user could lock/unlock/delete
passwords and skip PAM authentication.

Fix: is_root() now uses getuid() (real UID) to check if the *caller*
is actually root, not just running a setuid binary.

Also:
- get_current_username() uses getuid() (real UID) for same reason
- Removed unused thiserror dependency from uu_passwd
- Fixed SECURITY.md placeholder text
This commit is contained in:
Pierre Warnier
2026-03-23 14:29:06 +01:00
parent 472e950cae
commit 92bf912a09
3 changed files with 9 additions and 6 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ reporting feature:
2. Click "New draft security advisory"
3. Fill in the details
Or email the maintainers directly (add contact email when established).
If private advisory reporting is unavailable, open an issue tagged `security`.
## What to Include
-1
View File
@@ -19,7 +19,6 @@ clap = { workspace = true }
libc = { workspace = true }
nix = { workspace = true }
shadow-core = { workspace = true, features = ["shadow", "login-defs"] }
thiserror = { workspace = true }
uucore = { workspace = true }
[features]
+8 -4
View File
@@ -500,14 +500,18 @@ fn resolve_target_user(matches: &clap::ArgMatches) -> Result<String, PasswdError
}
}
/// Check if the effective user is root.
/// Check if the *real* user (caller) is root.
///
/// Uses `getuid()` (real UID), NOT `geteuid()` (effective UID).
/// When passwd is installed setuid-root, euid is always 0 for all callers.
/// The real UID tells us if the caller is actually root or a regular user.
fn is_root() -> bool {
nix::unistd::geteuid().is_root()
nix::unistd::getuid().is_root()
}
/// Return the current user's username (from euid).
/// Return the current user's username (from real UID).
fn get_current_username() -> Result<String, PasswdError> {
let uid = nix::unistd::geteuid();
let uid = nix::unistd::getuid();
match nix::unistd::User::from_uid(uid) {
Ok(Some(user)) => Ok(user.name),
Ok(None) => Err(PasswdError::UnexpectedFailure(format!(