mirror of
https://github.com/uutils/shadow.git
synced 2026-06-10 16:14:57 -07:00
Merge pull request #148 from shadow-utils-rs/fix/147-rustix-review-findings
shadow-core: fix 6 Copilot findings from rustix migration (#147)
This commit is contained in:
@@ -142,27 +142,45 @@ pub fn current_username() -> Result<String, crate::error::ShadowError> {
|
||||
lookup_username_by_uid(uid)
|
||||
}
|
||||
|
||||
/// Look up a username by UID from `/etc/passwd`.
|
||||
/// Look up a username by UID via NSS (`getpwuid_r`).
|
||||
///
|
||||
/// Uses the system name-service switch, so it works with LDAP, SSSD,
|
||||
/// systemd-homed, and other backends — not just `/etc/passwd`.
|
||||
pub fn lookup_username_by_uid(uid: u32) -> Result<String, crate::error::ShadowError> {
|
||||
let entries = crate::passwd::read_passwd_file(std::path::Path::new("/etc/passwd"))?;
|
||||
match entries.iter().find(|e| e.uid == uid) {
|
||||
Some(entry) => Ok(entry.name.clone()),
|
||||
None => Err(crate::error::ShadowError::Other(
|
||||
match crate::process::lookup_username(uid) {
|
||||
Ok(Some(name)) => Ok(name),
|
||||
Ok(None) => Err(crate::error::ShadowError::Other(
|
||||
format!("cannot determine username for uid {uid}").into(),
|
||||
)),
|
||||
Err(e) => Err(crate::error::ShadowError::Other(
|
||||
format!("NSS lookup failed for uid {uid}: {e}").into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up a passwd entry by UID from `/etc/passwd`.
|
||||
/// Look up a passwd entry by UID via NSS (`getpwuid_r`).
|
||||
///
|
||||
/// Uses the system name-service switch, so it works with LDAP, SSSD,
|
||||
/// systemd-homed, and other backends — not just `/etc/passwd`.
|
||||
pub fn lookup_passwd_entry_by_uid(
|
||||
uid: u32,
|
||||
) -> Result<crate::passwd::PasswdEntry, crate::error::ShadowError> {
|
||||
let entries = crate::passwd::read_passwd_file(std::path::Path::new("/etc/passwd"))?;
|
||||
match entries.into_iter().find(|e| e.uid == uid) {
|
||||
Some(entry) => Ok(entry),
|
||||
None => Err(crate::error::ShadowError::Other(
|
||||
match crate::process::getpwuid(uid) {
|
||||
Ok(Some(pw)) => Ok(crate::passwd::PasswdEntry {
|
||||
name: pw.name,
|
||||
passwd: pw.passwd,
|
||||
uid: pw.uid,
|
||||
gid: pw.gid,
|
||||
gecos: pw.gecos,
|
||||
home: pw.home,
|
||||
shell: pw.shell,
|
||||
}),
|
||||
Ok(None) => Err(crate::error::ShadowError::Other(
|
||||
format!("no passwd entry for uid {uid}").into(),
|
||||
)),
|
||||
Err(e) => Err(crate::error::ShadowError::Other(
|
||||
format!("NSS lookup failed for uid {uid}: {e}").into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ pub mod login_defs;
|
||||
#[cfg(feature = "subid")]
|
||||
pub mod subid;
|
||||
|
||||
// PAM and crypt are C libraries — FFI inherently requires unsafe.
|
||||
// These are the ONLY modules where unsafe_code is permitted.
|
||||
// PAM, crypt, and process are C library boundaries — FFI inherently
|
||||
// requires unsafe. These are the ONLY modules where unsafe_code is permitted.
|
||||
#[cfg(feature = "pam")]
|
||||
#[allow(unsafe_code)]
|
||||
pub mod pam;
|
||||
|
||||
+128
-10
@@ -2,14 +2,14 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore setuid seteuid setgid initgroups sigprocmask
|
||||
// spell-checker:ignore setuid seteuid setgid initgroups sigprocmask getpwuid
|
||||
|
||||
//! Process-level POSIX wrappers for setuid-root tools.
|
||||
//!
|
||||
//! These functions call libc directly because rustix intentionally does not
|
||||
//! provide process-wide `setuid`/`setgid`/`sigprocmask` (they require libc
|
||||
//! coordination for thread safety). The `libc` crate is already a dependency
|
||||
//! for PAM FFI.
|
||||
//! provide process-wide `setuid`/`setgid` or per-thread `sigprocmask` (they
|
||||
//! require libc coordination for thread safety). The `libc` crate is already
|
||||
//! a dependency for PAM FFI.
|
||||
//!
|
||||
//! This is one of the few modules that permits `unsafe` — all unsafe is
|
||||
//! confined to well-understood POSIX C library calls.
|
||||
@@ -98,10 +98,11 @@ pub fn execv(path: &CStr, argv: &[&CStr]) -> io::Error {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Signal blocking (process-wide via libc)
|
||||
// Signal blocking (per-thread via libc sigprocmask)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A saved signal mask, used by [`block_signals`] and [`restore_signals`].
|
||||
/// A saved signal mask, used by [`block_critical_signals`] and
|
||||
/// [`restore_signals`].
|
||||
///
|
||||
/// Wraps a `libc::sigset_t`.
|
||||
pub struct SavedSigSet {
|
||||
@@ -110,16 +111,27 @@ pub struct SavedSigSet {
|
||||
|
||||
/// Block `SIGINT`, `SIGTERM`, `SIGHUP` and return the previous signal mask.
|
||||
///
|
||||
/// Calls `sigprocmask`, which modifies the *calling thread's* signal mask.
|
||||
/// For single-threaded shadow-rs tools this is effectively process-wide.
|
||||
///
|
||||
/// Prevents these signals from interrupting a lock-modify-write sequence.
|
||||
pub fn block_critical_signals() -> io::Result<SavedSigSet> {
|
||||
// SAFETY: sigemptyset, sigaddset, and sigprocmask are standard POSIX
|
||||
// functions. We initialize the sigset_t with sigemptyset before use.
|
||||
unsafe {
|
||||
let mut block_set: libc::sigset_t = std::mem::zeroed();
|
||||
libc::sigemptyset(&raw mut block_set);
|
||||
libc::sigaddset(&raw mut block_set, libc::SIGINT);
|
||||
libc::sigaddset(&raw mut block_set, libc::SIGTERM);
|
||||
libc::sigaddset(&raw mut block_set, libc::SIGHUP);
|
||||
if libc::sigemptyset(&raw mut block_set) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if libc::sigaddset(&raw mut block_set, libc::SIGINT) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if libc::sigaddset(&raw mut block_set, libc::SIGTERM) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if libc::sigaddset(&raw mut block_set, libc::SIGHUP) != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut old_set: libc::sigset_t = std::mem::zeroed();
|
||||
let ret = libc::sigprocmask(libc::SIG_BLOCK, &raw const block_set, &raw mut old_set);
|
||||
@@ -147,3 +159,109 @@ pub fn restore_signals(saved: &SavedSigSet) -> io::Result<()> {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NSS user lookup (getpwuid_r)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Result of an NSS user lookup via `getpwuid_r`.
|
||||
///
|
||||
/// Contains the fields from `struct passwd` that shadow-rs tools need.
|
||||
/// Unlike reading `/etc/passwd` directly, this goes through NSS and works
|
||||
/// with LDAP, SSSD, systemd-homed, and other name-service backends.
|
||||
pub struct PwEntry {
|
||||
/// Login name.
|
||||
pub name: String,
|
||||
/// Encrypted password (usually `x`).
|
||||
pub passwd: String,
|
||||
/// Numeric user ID.
|
||||
pub uid: u32,
|
||||
/// Numeric primary group ID.
|
||||
pub gid: u32,
|
||||
/// GECOS / comment field.
|
||||
pub gecos: String,
|
||||
/// Home directory.
|
||||
pub home: String,
|
||||
/// Login shell.
|
||||
pub shell: String,
|
||||
}
|
||||
|
||||
/// Look up a user by UID via `getpwuid_r` (NSS-backed).
|
||||
///
|
||||
/// Returns `None` if no user exists for the given UID.
|
||||
/// Returns `Err` on system errors (e.g., I/O failure in NSS backend).
|
||||
pub fn getpwuid(uid: u32) -> io::Result<Option<PwEntry>> {
|
||||
// Start with a 1 KiB buffer; grow on ERANGE.
|
||||
const MAX_BUF: usize = 1024 * 1024;
|
||||
let mut buf_size: usize = 1024;
|
||||
|
||||
loop {
|
||||
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
|
||||
let mut buf: Vec<u8> = vec![0u8; buf_size];
|
||||
let mut result: *mut libc::passwd = std::ptr::null_mut();
|
||||
|
||||
// SAFETY: getpwuid_r is a POSIX thread-safe function. We pass a
|
||||
// properly sized buffer and a zeroed passwd struct. The result
|
||||
// pointer tells us whether an entry was found.
|
||||
let ret = unsafe {
|
||||
libc::getpwuid_r(
|
||||
uid,
|
||||
&raw mut pwd,
|
||||
buf.as_mut_ptr().cast::<libc::c_char>(),
|
||||
buf_size,
|
||||
&raw mut result,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == libc::ERANGE {
|
||||
// Buffer too small — double and retry.
|
||||
buf_size = buf_size.saturating_mul(2);
|
||||
if buf_size > MAX_BUF {
|
||||
return Err(io::Error::other(
|
||||
"getpwuid_r: ERANGE persists beyond 1 MiB buffer cap",
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ret != 0 {
|
||||
return Err(io::Error::from_raw_os_error(ret));
|
||||
}
|
||||
|
||||
if result.is_null() {
|
||||
// No entry found for this UID.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// SAFETY: getpwuid_r succeeded and `result` is non-null, so `pwd`
|
||||
// is populated. String fields should point into `buf`, but some NSS
|
||||
// backends may return null for optional fields — guard defensively.
|
||||
let entry = unsafe {
|
||||
let str_field = |ptr: *const libc::c_char| -> String {
|
||||
if ptr.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
||||
}
|
||||
};
|
||||
PwEntry {
|
||||
name: str_field(pwd.pw_name),
|
||||
passwd: str_field(pwd.pw_passwd),
|
||||
uid: pwd.pw_uid,
|
||||
gid: pwd.pw_gid,
|
||||
gecos: str_field(pwd.pw_gecos),
|
||||
home: str_field(pwd.pw_dir),
|
||||
shell: str_field(pwd.pw_shell),
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(Some(entry));
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up a username by UID via NSS (`getpwuid_r`).
|
||||
///
|
||||
/// Convenience wrapper that returns just the login name.
|
||||
pub fn lookup_username(uid: u32) -> io::Result<Option<String>> {
|
||||
Ok(getpwuid(uid)?.map(|e| e.name))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user