diff --git a/Cargo.toml b/Cargo.toml index a366691..125de20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ uucore = "0.7" thiserror = "2" # Unix/Linux -nix = { version = "0.30", features = ["user", "fs", "process", "signal", "term", "resource"] } +rustix = { version = "1", features = ["process", "fs", "termios"] } libc = "0.2" # Security @@ -205,7 +205,7 @@ usermod = { version = "0.0.1", package = "uu_usermod", path = "src/uu/usermod" } pwck = { version = "0.0.1", package = "uu_pwck", path = "src/uu/pwck" } grpck = { version = "0.0.1", package = "uu_grpck", path = "src/uu/grpck" } shadow-core = { path = "src/shadow-core", features = ["shadow"] } -nix = { workspace = true } +rustix = { workspace = true } tempfile = { workspace = true } [profile.release] diff --git a/src/shadow-core/Cargo.toml b/src/shadow-core/Cargo.toml index b602c0c..8bb8a97 100644 --- a/src/shadow-core/Cargo.toml +++ b/src/shadow-core/Cargo.toml @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] libc = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } thiserror = { workspace = true } zeroize = { workspace = true } subtle = { workspace = true } diff --git a/src/shadow-core/src/atomic.rs b/src/shadow-core/src/atomic.rs index ecb6cec..888eb56 100644 --- a/src/shadow-core/src/atomic.rs +++ b/src/shadow-core/src/atomic.rs @@ -30,16 +30,13 @@ use crate::error::ShadowError; /// `umask(2)` is a process-wide operation. This guard is NOT safe to use /// from multiple threads concurrently. All shadow-rs tools are /// single-threaded, so this is not an issue in practice. -struct UmaskGuard( - nix::sys::stat::Mode, - std::marker::PhantomData>, -); +struct UmaskGuard(rustix::fs::Mode, std::marker::PhantomData>); impl UmaskGuard { /// Set umask to zero and return a guard that restores the original. fn zero() -> Self { Self( - nix::sys::stat::umask(nix::sys::stat::Mode::empty()), + rustix::process::umask(rustix::fs::Mode::empty()), std::marker::PhantomData, ) } @@ -47,7 +44,7 @@ impl UmaskGuard { impl Drop for UmaskGuard { fn drop(&mut self) { - nix::sys::stat::umask(self.0); + rustix::process::umask(self.0); } } @@ -151,7 +148,7 @@ where tmp_file .flush() .map_err(|e| ShadowError::IoPath(e, tmp_path.clone()))?; - nix::unistd::fsync(&tmp_file) + rustix::fs::fsync(&tmp_file) .map_err(|e| ShadowError::IoPath(io::Error::from(e), tmp_path.clone()))?; // Atomic rename. @@ -162,7 +159,7 @@ where // Fsync the parent directory to ensure the rename is durable. if let Ok(dir_fd) = File::open(dir) { - let _ = nix::unistd::fsync(&dir_fd); + let _ = rustix::fs::fsync(&dir_fd); } Ok(()) diff --git a/src/shadow-core/src/hardening.rs b/src/shadow-core/src/hardening.rs index 2dde351..cb1f33e 100644 --- a/src/shadow-core/src/hardening.rs +++ b/src/shadow-core/src/hardening.rs @@ -14,8 +14,16 @@ /// A core dump from a setuid-root process could expose password hashes /// and plaintext passwords. pub fn suppress_core_dumps() { - let _ = nix::sys::resource::setrlimit(nix::sys::resource::Resource::RLIMIT_CORE, 0, 0); - // PR_SET_DUMPABLE via nix::sys::prctl (no raw unsafe needed). + use rustix::process::{Resource, Rlimit, setrlimit}; + + let _ = setrlimit( + Resource::Core, + Rlimit { + current: Some(0), + maximum: Some(0), + }, + ); + // PR_SET_DUMPABLE via prctl (no raw unsafe needed). // nix doesn't expose prctl directly, so we skip it rather than use unsafe. // RLIMIT_CORE=0 is sufficient to prevent core dumps. } @@ -25,10 +33,14 @@ pub fn suppress_core_dumps() { /// A malicious caller could `ulimit -f 1` before invoking a setuid-root /// tool, causing `/etc/shadow` to be truncated mid-write. pub fn raise_file_size_limit() { - let _ = nix::sys::resource::setrlimit( - nix::sys::resource::Resource::RLIMIT_FSIZE, - nix::sys::resource::RLIM_INFINITY, - nix::sys::resource::RLIM_INFINITY, + use rustix::process::{Resource, Rlimit, setrlimit}; + + let _ = setrlimit( + Resource::Fsize, + Rlimit { + current: None, + maximum: None, + }, ); } @@ -121,19 +133,35 @@ pub fn harden_process() -> Vec<(String, String)> { /// `geteuid()` is 0 for all callers, but the real UID identifies who /// actually invoked the program. pub fn caller_is_root() -> bool { - nix::unistd::getuid().is_root() + rustix::process::getuid().is_root() } /// Return the current user's username from the real UID. pub fn current_username() -> Result { - let uid = nix::unistd::getuid(); - match nix::unistd::User::from_uid(uid) { - Ok(Some(user)) => Ok(user.name), - Ok(None) => Err(crate::error::ShadowError::Other( + let uid = rustix::process::getuid().as_raw(); + lookup_username_by_uid(uid) +} + +/// Look up a username by UID from `/etc/passwd`. +pub fn lookup_username_by_uid(uid: u32) -> Result { + 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( format!("cannot determine username for uid {uid}").into(), )), - Err(e) => Err(crate::error::ShadowError::Other( - format!("cannot determine username: {e}").into(), + } +} + +/// Look up a passwd entry by UID from `/etc/passwd`. +pub fn lookup_passwd_entry_by_uid( + uid: u32, +) -> Result { + 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( + format!("no passwd entry for uid {uid}").into(), )), } } @@ -149,35 +177,22 @@ pub fn current_username() -> Result { /// inconsistent state or holding a stale lock. The original signal mask /// is restored when the guard is dropped. pub struct SignalBlocker { - old_mask: nix::sys::signal::SigSet, + saved: crate::process::SavedSigSet, } impl SignalBlocker { /// Block `SIGINT`, `SIGTERM`, `SIGHUP` to prevent partial file writes. pub fn block_critical() -> Result { - use nix::sys::signal::{SigSet, SigmaskHow, Signal}; + let saved = crate::process::block_critical_signals().map_err(|e| { + crate::error::ShadowError::Other(format!("cannot block signals: {e}").into()) + })?; - let mut block_set = SigSet::empty(); - block_set.add(Signal::SIGINT); - block_set.add(Signal::SIGTERM); - block_set.add(Signal::SIGHUP); - - let mut old_mask = SigSet::empty(); - nix::sys::signal::sigprocmask(SigmaskHow::SIG_BLOCK, Some(&block_set), Some(&mut old_mask)) - .map_err(|e| { - crate::error::ShadowError::Other(format!("cannot block signals: {e}").into()) - })?; - - Ok(Self { old_mask }) + Ok(Self { saved }) } } impl Drop for SignalBlocker { fn drop(&mut self) { - let _ = nix::sys::signal::sigprocmask( - nix::sys::signal::SigmaskHow::SIG_SETMASK, - Some(&self.old_mask), - None, - ); + let _ = crate::process::restore_signals(&self.saved); } } diff --git a/src/shadow-core/src/lib.rs b/src/shadow-core/src/lib.rs index 3e0fefd..643fb19 100644 --- a/src/shadow-core/src/lib.rs +++ b/src/shadow-core/src/lib.rs @@ -40,6 +40,10 @@ pub mod crypt; #[cfg(feature = "selinux")] pub mod selinux; +// Process-level POSIX wrappers (setuid, sigprocmask, etc.) — FFI requires unsafe. +#[allow(unsafe_code)] +pub mod process; + pub mod atomic; pub mod audit; pub mod hardening; diff --git a/src/shadow-core/src/lock.rs b/src/shadow-core/src/lock.rs index 0140d30..76194f9 100644 --- a/src/shadow-core/src/lock.rs +++ b/src/shadow-core/src/lock.rs @@ -19,8 +19,6 @@ use std::path::{Path, PathBuf}; use std::thread; use std::time::{Duration, Instant}; -use nix::unistd; - use crate::error::ShadowError; /// Default lock timeout (matches GNU shadow-utils `LOCK_TIMEOUT`). @@ -187,7 +185,7 @@ fn write_pid_file(tmp_path: &Path) -> Result<(), ShadowError> { } }; - let pid = unistd::getpid(); + let pid = rustix::process::getpid(); write!(file, "{pid}").map_err(|e| { ShadowError::Lock(format!("cannot write {}: {e}", tmp_path.display()).into()) })?; @@ -195,7 +193,7 @@ fn write_pid_file(tmp_path: &Path) -> Result<(), ShadowError> { file.flush().map_err(|e| { ShadowError::Lock(format!("cannot flush {}: {e}", tmp_path.display()).into()) })?; - nix::unistd::fsync(&file).map_err(|e| { + rustix::fs::fsync(&file).map_err(|e| { ShadowError::Lock(format!("cannot fsync {}: {e}", tmp_path.display()).into()) })?; @@ -217,13 +215,16 @@ fn is_stale_lock(lock_path: &Path) -> bool { return true; } + let Some(pid) = rustix::process::Pid::from_raw(pid) else { + return true; + }; + // Signal 0 checks if the process exists without actually sending a signal. // Only ESRCH means "no such process". EPERM means the process exists but // we lack permission to signal it — that is a valid lock holder. - let pid = nix::unistd::Pid::from_raw(pid); matches!( - nix::sys::signal::kill(pid, None), - Err(nix::errno::Errno::ESRCH) + rustix::process::test_kill_process(pid), + Err(rustix::io::Errno::SRCH) ) } @@ -296,6 +297,8 @@ mod tests { #[test] fn test_lock_file_has_cloexec() { + use std::os::unix::io::AsFd; + // Rust's stdlib sets O_CLOEXEC by default on Linux. // Verify the lock file FD won't leak to child processes. let dir = tempfile::tempdir().expect("tempdir creation failed"); @@ -305,9 +308,11 @@ mod tests { let lock = FileLock::acquire(&file).expect("failed to acquire lock"); let f = fs::File::open(&lock.lock_path).expect("failed to open lock file"); - let flags = - nix::fcntl::fcntl(&f, nix::fcntl::FcntlArg::F_GETFD).expect("fcntl F_GETFD failed"); - assert!(flags & libc::FD_CLOEXEC != 0, "FD should have CLOEXEC set"); + let flags = rustix::io::fcntl_getfd(f.as_fd()).expect("fcntl F_GETFD failed"); + assert!( + flags.contains(rustix::io::FdFlags::CLOEXEC), + "FD should have CLOEXEC set" + ); lock.release().expect("failed to release lock"); } diff --git a/src/shadow-core/src/pam.rs b/src/shadow-core/src/pam.rs index ebcb5e8..899bd04 100644 --- a/src/shadow-core/src/pam.rs +++ b/src/shadow-core/src/pam.rs @@ -372,7 +372,7 @@ fn prompt_for_input( /// /// Opens `/dev/tty` once with read+write mode (not stdin) to ensure we talk /// to the real terminal even if stdin has been redirected. Uses -/// `nix::sys::termios` to disable `ECHO` for password prompts and restores +/// `rustix::termios` to disable `ECHO` for password prompts and restores /// the original settings afterward (including on error, via a drop guard). fn read_from_tty(message: &PamMessage, echo: bool) -> io::Result> { let mut tty = File::options().read(true).write(true).open("/dev/tty")?; @@ -482,26 +482,28 @@ fn free_responses(responses: *mut PamResponse, count: usize) { /// RAII guard that disables terminal echo and restores it on drop. /// -/// Uses `nix::sys::termios` to manipulate the terminal's local flags. The +/// Uses `rustix::termios` to manipulate the terminal's local flags. The /// original settings are saved and restored when the guard is dropped, even /// if the caller returns early or panics. struct EchoGuard { fd: libc::c_int, - original: nix::sys::termios::Termios, + original: rustix::termios::Termios, } impl EchoGuard { /// Disable echo on the given terminal file. fn disable(tty: &File) -> io::Result { - use nix::sys::termios::{self, LocalFlags, SetArg}; + use std::os::unix::io::AsFd; let fd = tty.as_raw_fd(); - let original = termios::tcgetattr(tty).map_err(io::Error::other)?; + let original = rustix::termios::tcgetattr(tty.as_fd()).map_err(io::Error::other)?; let mut noecho = original.clone(); - noecho.local_flags &= !(LocalFlags::ECHO | LocalFlags::ECHONL); + noecho.local_modes &= + !(rustix::termios::LocalModes::ECHO | rustix::termios::LocalModes::ECHONL); - termios::tcsetattr(tty, SetArg::TCSANOW, &noecho).map_err(io::Error::other)?; + rustix::termios::tcsetattr(tty.as_fd(), rustix::termios::OptionalActions::Now, &noecho) + .map_err(io::Error::other)?; Ok(Self { fd, original }) } @@ -516,7 +518,7 @@ impl Drop for EchoGuard { use std::os::unix::io::BorrowedFd; let fd = unsafe { BorrowedFd::borrow_raw(self.fd) }; let _ = - nix::sys::termios::tcsetattr(fd, nix::sys::termios::SetArg::TCSANOW, &self.original); + rustix::termios::tcsetattr(fd, rustix::termios::OptionalActions::Now, &self.original); } } diff --git a/src/shadow-core/src/process.rs b/src/shadow-core/src/process.rs new file mode 100644 index 0000000..840c854 --- /dev/null +++ b/src/shadow-core/src/process.rs @@ -0,0 +1,149 @@ +// This file is part of the shadow-rs package. +// +// 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 + +//! 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. +//! +//! This is one of the few modules that permits `unsafe` — all unsafe is +//! confined to well-understood POSIX C library calls. + +use std::ffi::CStr; +use std::io; + +// --------------------------------------------------------------------------- +// UID / GID manipulation (process-wide via libc) +// --------------------------------------------------------------------------- + +/// `setuid(uid)` — set the real and effective user ID of the calling process. +/// +/// This calls the libc `setuid()` which is process-wide (unlike the raw +/// syscall which is per-thread on Linux). +pub fn setuid(uid: u32) -> io::Result<()> { + // SAFETY: setuid is a standard POSIX function. The only precondition + // is that uid is a valid UID value, which u32 always satisfies. + let ret = unsafe { libc::setuid(uid) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +/// `seteuid(uid)` — set the effective user ID of the calling process. +/// +/// This calls the libc `seteuid()` which is process-wide. +pub fn seteuid(uid: u32) -> io::Result<()> { + // SAFETY: seteuid is a standard POSIX function. + let ret = unsafe { libc::seteuid(uid) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +/// `setgid(gid)` — set the real and effective group ID of the calling process. +/// +/// This calls the libc `setgid()` which is process-wide. +pub fn setgid(gid: u32) -> io::Result<()> { + // SAFETY: setgid is a standard POSIX function. + let ret = unsafe { libc::setgid(gid) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +/// `initgroups(user, gid)` — initialize the supplementary group list. +/// +/// Sets the supplementary groups for `user` plus `gid`. +pub fn initgroups(user: &CStr, gid: u32) -> io::Result<()> { + // SAFETY: initgroups is a standard POSIX function. `user` is a valid + // null-terminated CStr. + let ret = unsafe { libc::initgroups(user.as_ptr(), gid) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +// --------------------------------------------------------------------------- +// exec +// --------------------------------------------------------------------------- + +/// `execv(path, argv)` — replace the current process image. +/// +/// On success this function never returns. On failure it returns an error. +pub fn execv(path: &CStr, argv: &[&CStr]) -> io::Error { + // Build a null-terminated array of pointers for execv. + let mut argv_ptrs: Vec<*const libc::c_char> = argv.iter().map(|s| s.as_ptr()).collect(); + argv_ptrs.push(std::ptr::null()); + + // SAFETY: execv is a standard POSIX function. The argv array is + // null-terminated and all CStr pointers are valid. + unsafe { + libc::execv(path.as_ptr(), argv_ptrs.as_ptr()); + } + // execv only returns on error. + io::Error::last_os_error() +} + +// --------------------------------------------------------------------------- +// Signal blocking (process-wide via libc) +// --------------------------------------------------------------------------- + +/// A saved signal mask, used by [`block_signals`] and [`restore_signals`]. +/// +/// Wraps a `libc::sigset_t`. +pub struct SavedSigSet { + set: libc::sigset_t, +} + +/// Block `SIGINT`, `SIGTERM`, `SIGHUP` and return the previous signal mask. +/// +/// Prevents these signals from interrupting a lock-modify-write sequence. +pub fn block_critical_signals() -> io::Result { + // 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); + + 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); + if ret != 0 { + return Err(io::Error::last_os_error()); + } + + Ok(SavedSigSet { set: old_set }) + } +} + +/// Restore a previously saved signal mask. +pub fn restore_signals(saved: &SavedSigSet) -> io::Result<()> { + // SAFETY: sigprocmask with SIG_SETMASK restores a previously captured mask. + let ret = unsafe { + libc::sigprocmask( + libc::SIG_SETMASK, + &raw const saved.set, + std::ptr::null_mut(), + ) + }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} diff --git a/src/shadow-core/src/skel.rs b/src/shadow-core/src/skel.rs index 7239032..0d01a92 100644 --- a/src/shadow-core/src/skel.rs +++ b/src/shadow-core/src/skel.rs @@ -91,7 +91,7 @@ mod tests { #[test] fn test_copy_files() { - if !nix::unistd::geteuid().is_root() { + if !rustix::process::geteuid().is_root() { return; } @@ -116,7 +116,7 @@ mod tests { #[test] fn test_copy_subdirectory() { - if !nix::unistd::geteuid().is_root() { + if !rustix::process::geteuid().is_root() { return; } @@ -135,7 +135,7 @@ mod tests { #[test] fn test_copy_symlink() { - if !nix::unistd::geteuid().is_root() { + if !rustix::process::geteuid().is_root() { return; } diff --git a/src/uu/chage/Cargo.toml b/src/uu/chage/Cargo.toml index 180169b..2e39749 100644 --- a/src/uu/chage/Cargo.toml +++ b/src/uu/chage/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow"] } uucore = { workspace = true } diff --git a/src/uu/chage/src/chage.rs b/src/uu/chage/src/chage.rs index 79f2322..8594d78 100644 --- a/src/uu/chage/src/chage.rs +++ b/src/uu/chage/src/chage.rs @@ -518,10 +518,10 @@ fn do_chroot(dir: &str) -> Result<(), ChageError> { } let path = Path::new(dir); - nix::unistd::chroot(path) + rustix::process::chroot(path) .map_err(|e| ChageError::UnexpectedFailure(format!("cannot chroot to '{dir}': {e}")))?; - nix::unistd::chdir("/").map_err(|e| { + rustix::process::chdir("/").map_err(|e| { ChageError::UnexpectedFailure(format!("cannot chdir to / after chroot: {e}")) })?; @@ -536,8 +536,8 @@ where { // Consolidate real + effective UID to root for file operations. // Some filesystem configurations check real UID. - if nix::unistd::geteuid().is_root() { - let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0)); + if rustix::process::geteuid().is_root() { + let _ = shadow_core::process::setuid(0); } // Block signals for the entire critical section (lock -> write -> unlock). diff --git a/src/uu/chfn/Cargo.toml b/src/uu/chfn/Cargo.toml index 93d8ffd..2a6736f 100644 --- a/src/uu/chfn/Cargo.toml +++ b/src/uu/chfn/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true } uucore = { workspace = true } diff --git a/src/uu/chfn/src/chfn.rs b/src/uu/chfn/src/chfn.rs index 93429f3..ec0876c 100644 --- a/src/uu/chfn/src/chfn.rs +++ b/src/uu/chfn/src/chfn.rs @@ -140,10 +140,10 @@ fn do_chroot(dir: &str) -> Result<(), ChfnError> { } let path = std::path::Path::new(dir); - nix::unistd::chroot(path) + rustix::process::chroot(path) .map_err(|e| ChfnError::Error(format!("cannot chroot to '{dir}': {e}")))?; - nix::unistd::chdir("/") + rustix::process::chdir("/") .map_err(|e| ChfnError::Error(format!("cannot chdir to / after chroot: {e}")))?; Ok(()) @@ -160,8 +160,8 @@ where F: FnOnce(&mut PasswdEntry) -> Result<(), String>, { // Consolidate real + effective UID to root for file operations. - if nix::unistd::geteuid().is_root() { - let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0)); + if rustix::process::geteuid().is_root() { + let _ = shadow_core::process::setuid(0); } let _signals = shadow_core::hardening::SignalBlocker::block_critical() diff --git a/src/uu/chpasswd/Cargo.toml b/src/uu/chpasswd/Cargo.toml index 96d46b1..cbc1d6a 100644 --- a/src/uu/chpasswd/Cargo.toml +++ b/src/uu/chpasswd/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "crypt"] } uucore = { workspace = true } zeroize = { workspace = true } diff --git a/src/uu/chpasswd/src/chpasswd.rs b/src/uu/chpasswd/src/chpasswd.rs index c8c0d37..38db226 100644 --- a/src/uu/chpasswd/src/chpasswd.rs +++ b/src/uu/chpasswd/src/chpasswd.rs @@ -304,8 +304,8 @@ fn apply_password_changes( hash_config: Option<&(shadow_core::crypt::CryptMethod, Option)>, ) -> UResult<()> { // Consolidate real + effective UID to root for file operations. - if nix::unistd::geteuid().is_root() { - let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0)); + if rustix::process::geteuid().is_root() { + let _ = shadow_core::process::setuid(0); } // Block signals for the entire critical section. @@ -419,10 +419,10 @@ fn do_chroot(dir: &str) -> Result<(), ChpasswdError> { } let path = Path::new(dir); - nix::unistd::chroot(path) + rustix::process::chroot(path) .map_err(|e| ChpasswdError::UnexpectedFailure(format!("cannot chroot to '{dir}': {e}")))?; - nix::unistd::chdir("/").map_err(|e| { + rustix::process::chdir("/").map_err(|e| { ChpasswdError::UnexpectedFailure(format!("cannot chdir to / after chroot: {e}")) })?; diff --git a/src/uu/chsh/Cargo.toml b/src/uu/chsh/Cargo.toml index 9532d2f..c6864c3 100644 --- a/src/uu/chsh/Cargo.toml +++ b/src/uu/chsh/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true } uucore = { workspace = true } diff --git a/src/uu/chsh/src/chsh.rs b/src/uu/chsh/src/chsh.rs index ff5aeb0..925655c 100644 --- a/src/uu/chsh/src/chsh.rs +++ b/src/uu/chsh/src/chsh.rs @@ -80,10 +80,10 @@ fn do_chroot(dir: &str) -> Result<(), ChshError> { } let path = std::path::Path::new(dir); - nix::unistd::chroot(path) + rustix::process::chroot(path) .map_err(|e| ChshError::Error(format!("cannot chroot to '{dir}': {e}")))?; - nix::unistd::chdir("/") + rustix::process::chdir("/") .map_err(|e| ChshError::Error(format!("cannot chdir to / after chroot: {e}")))?; Ok(()) @@ -174,8 +174,8 @@ fn mutate_passwd(root: &SysRoot, username: &str, mutate: F) -> UResult<()> where F: FnOnce(&mut PasswdEntry) -> Result<(), String>, { - if nix::unistd::geteuid().is_root() { - let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0)); + if rustix::process::geteuid().is_root() { + let _ = shadow_core::process::setuid(0); } let _signals = shadow_core::hardening::SignalBlocker::block_critical() diff --git a/src/uu/groupadd/Cargo.toml b/src/uu/groupadd/Cargo.toml index c87f409..3d31175 100644 --- a/src/uu/groupadd/Cargo.toml +++ b/src/uu/groupadd/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs"] } uucore = { workspace = true } diff --git a/src/uu/groupadd/src/groupadd.rs b/src/uu/groupadd/src/groupadd.rs index d809fcf..82e629d 100644 --- a/src/uu/groupadd/src/groupadd.rs +++ b/src/uu/groupadd/src/groupadd.rs @@ -456,7 +456,7 @@ mod tests { } fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/src/uu/groupdel/Cargo.toml b/src/uu/groupdel/Cargo.toml index 3312cef..e98a338 100644 --- a/src/uu/groupdel/Cargo.toml +++ b/src/uu/groupdel/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs"] } uucore = { workspace = true } diff --git a/src/uu/groupdel/src/groupdel.rs b/src/uu/groupdel/src/groupdel.rs index be5e737..8f09473 100644 --- a/src/uu/groupdel/src/groupdel.rs +++ b/src/uu/groupdel/src/groupdel.rs @@ -252,7 +252,7 @@ mod tests { } fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/src/uu/groupmod/Cargo.toml b/src/uu/groupmod/Cargo.toml index 79079c8..c0254b3 100644 --- a/src/uu/groupmod/Cargo.toml +++ b/src/uu/groupmod/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs"] } uucore = { workspace = true } diff --git a/src/uu/groupmod/src/groupmod.rs b/src/uu/groupmod/src/groupmod.rs index 82b4f29..1e91f11 100644 --- a/src/uu/groupmod/src/groupmod.rs +++ b/src/uu/groupmod/src/groupmod.rs @@ -329,7 +329,7 @@ mod tests { } fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/src/uu/grpck/Cargo.toml b/src/uu/grpck/Cargo.toml index 5aeaf33..6041110 100644 --- a/src/uu/grpck/Cargo.toml +++ b/src/uu/grpck/Cargo.toml @@ -20,8 +20,6 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -libc = { workspace = true } -nix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs"] } uucore = { workspace = true } diff --git a/src/uu/newgrp/Cargo.toml b/src/uu/newgrp/Cargo.toml index a6fa58e..a7acb4a 100644 --- a/src/uu/newgrp/Cargo.toml +++ b/src/uu/newgrp/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } zeroize = { workspace = true } shadow-core = { workspace = true, features = ["group", "gshadow", "crypt"] } uucore = { workspace = true } diff --git a/src/uu/newgrp/src/newgrp.rs b/src/uu/newgrp/src/newgrp.rs index 67a9787..03aea25 100644 --- a/src/uu/newgrp/src/newgrp.rs +++ b/src/uu/newgrp/src/newgrp.rs @@ -70,14 +70,11 @@ impl UError for NewgrpError { /// Get the current user's primary GID from the real UID. fn get_current_gid() -> Result { - let uid = nix::unistd::getuid(); - match nix::unistd::User::from_uid(uid) { - Ok(Some(user)) => Ok(user.gid.as_raw()), - Ok(None) => Err(NewgrpError::Error(format!( - "cannot determine current user for uid {uid}" - ))), + let uid = rustix::process::getuid().as_raw(); + match shadow_core::hardening::lookup_passwd_entry_by_uid(uid) { + Ok(entry) => Ok(entry.gid), Err(e) => Err(NewgrpError::Error(format!( - "cannot determine current user: {e}" + "cannot determine current user for uid {uid}: {e}" ))), } } @@ -87,14 +84,13 @@ fn get_current_gid() -> Result { /// Reads the shell field from `/etc/passwd` for the given UID rather /// than trusting `$SHELL`, which is attacker-controlled in a /// setuid-root context. -fn get_shell(uid: nix::unistd::Uid) -> String { - match nix::unistd::User::from_uid(uid) { - Ok(Some(user)) => { - let shell = user.shell.to_string_lossy().to_string(); - if shell.is_empty() { +fn get_shell(uid: u32) -> String { + match shadow_core::hardening::lookup_passwd_entry_by_uid(uid) { + Ok(entry) => { + if entry.shell.is_empty() { "/bin/sh".to_string() } else { - shell + entry.shell } } _ => "/bin/sh".to_string(), @@ -127,7 +123,7 @@ fn group_has_password(gshadow_path: &Path, group_name: &str) -> Option { /// RAII guard that restores terminal echo on drop. struct EchoGuard { tty: std::fs::File, - old_termios: nix::sys::termios::Termios, + old_termios: rustix::termios::Termios, } impl EchoGuard { @@ -135,14 +131,14 @@ impl EchoGuard { fn disable(tty: std::fs::File) -> Result { use std::os::unix::io::AsFd; - let old_termios = nix::sys::termios::tcgetattr(tty.as_fd()) + let old_termios = rustix::termios::tcgetattr(tty.as_fd()) .map_err(|e| NewgrpError::Error(format!("cannot get terminal attributes: {e}")))?; let mut new_termios = old_termios.clone(); - new_termios.local_flags &= !nix::sys::termios::LocalFlags::ECHO; - nix::sys::termios::tcsetattr( + new_termios.local_modes &= !rustix::termios::LocalModes::ECHO; + rustix::termios::tcsetattr( tty.as_fd(), - nix::sys::termios::SetArg::TCSANOW, + rustix::termios::OptionalActions::Now, &new_termios, ) .map_err(|e| NewgrpError::Error(format!("cannot disable echo: {e}")))?; @@ -154,9 +150,9 @@ impl EchoGuard { impl Drop for EchoGuard { fn drop(&mut self) { use std::os::unix::io::AsFd; - let _ = nix::sys::termios::tcsetattr( + let _ = rustix::termios::tcsetattr( self.tty.as_fd(), - nix::sys::termios::SetArg::TCSANOW, + rustix::termios::OptionalActions::Now, &self.old_termios, ); } @@ -288,21 +284,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; // Set the new GID. - let gid = nix::unistd::Gid::from_raw(target_gid); - nix::unistd::setgid(gid) + shadow_core::process::setgid(target_gid) .map_err(|e| NewgrpError::Error(format!("cannot set group ID to {target_gid}: {e}")))?; // Reset supplementary groups. POSIX requires newgrp to reinitialize // the group list. Without this, the new shell inherits stale groups. let username_cstr = std::ffi::CString::new(username.as_str()) .map_err(|_| NewgrpError::Error("invalid username".into()))?; - nix::unistd::initgroups(&username_cstr, gid) + shadow_core::process::initgroups(&username_cstr, target_gid) .map_err(|e| NewgrpError::Error(format!("cannot initialize groups: {e}")))?; // Drop back to the real UID (in case we are setuid-root). - let real_uid = nix::unistd::getuid(); - if nix::unistd::geteuid() != real_uid { - nix::unistd::setuid(real_uid) + let real_uid = rustix::process::getuid().as_raw(); + if rustix::process::geteuid().as_raw() != real_uid { + shadow_core::process::setuid(real_uid) .map_err(|e| NewgrpError::Error(format!("cannot drop privileges: {e}")))?; } @@ -320,12 +315,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let login_cstr = CString::new(login_name.as_str()) .map_err(|_| NewgrpError::Error("invalid shell name".into()))?; - // SAFETY: execv replaces the current process. The CStrings are valid - // and null-terminated. If execv fails, we return an error. - match nix::unistd::execv(&shell_cstr, &[login_cstr]) { - Ok(infallible) => match infallible {}, - Err(e) => Err(NewgrpError::Error(format!("cannot exec {shell}: {e}")).into()), - } + // execv replaces the current process. If it fails, we return an error. + let err = shadow_core::process::execv(&shell_cstr, &[&login_cstr]); + Err(NewgrpError::Error(format!("cannot exec {shell}: {err}")).into()) } /// Build the clap `Command` for `newgrp`. @@ -461,7 +453,7 @@ mod tests { #[test] fn test_get_shell_default() { // This test is environment-dependent but should at least not panic. - let uid = nix::unistd::getuid(); + let uid = rustix::process::getuid().as_raw(); let shell = get_shell(uid); assert!(!shell.is_empty()); } diff --git a/src/uu/passwd/Cargo.toml b/src/uu/passwd/Cargo.toml index 116c145..43df0aa 100644 --- a/src/uu/passwd/Cargo.toml +++ b/src/uu/passwd/Cargo.toml @@ -20,8 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -libc = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "login-defs", "landlock"] } uucore = { workspace = true } diff --git a/src/uu/passwd/src/passwd.rs b/src/uu/passwd/src/passwd.rs index 9f14060..b14bcff 100644 --- a/src/uu/passwd/src/passwd.rs +++ b/src/uu/passwd/src/passwd.rs @@ -479,16 +479,16 @@ fn cmd_status(root: &SysRoot, target_user: Option<&str>) -> UResult<()> { /// actual caller, not root. The destructor re-elevates. #[cfg_attr(not(feature = "pam"), allow(dead_code))] struct PrivDrop { - original_euid: nix::unistd::Uid, + original_euid: u32, } impl PrivDrop { /// Drop effective UID to the given UID. #[cfg_attr(not(feature = "pam"), allow(dead_code))] - fn drop_to(uid: nix::unistd::Uid) -> Result { - let original_euid = nix::unistd::geteuid(); + fn drop_to(uid: u32) -> Result { + let original_euid = rustix::process::geteuid().as_raw(); if original_euid != uid { - nix::unistd::seteuid(uid).map_err(|e| { + shadow_core::process::seteuid(uid).map_err(|e| { PasswdError::UnexpectedFailure(format!("cannot drop privileges: {e}")) })?; } @@ -498,7 +498,7 @@ impl PrivDrop { impl Drop for PrivDrop { fn drop(&mut self) { - if let Err(e) = nix::unistd::seteuid(self.original_euid) { + if let Err(e) = shadow_core::process::seteuid(self.original_euid) { // Failing to restore privileges is a critical error — log it loudly. // We can't return an error from Drop, so at least make it visible. let _ = writeln!( @@ -543,7 +543,7 @@ fn cmd_pam_change(matches: &clap::ArgMatches, _target_user: &str) -> UResult<()> // Drop privileges to caller's real UID during PAM conversation. // Re-elevate automatically when _priv_drop goes out of scope. - let _priv_drop = PrivDrop::drop_to(nix::unistd::getuid())?; + let _priv_drop = PrivDrop::drop_to(rustix::process::getuid().as_raw())?; // Non-root users changing their own password must authenticate first. if !shadow_core::hardening::caller_is_root() { @@ -571,7 +571,7 @@ fn cmd_pam_change(matches: &clap::ArgMatches, _target_user: &str) -> UResult<()> audit::log_user_event( "CHNG_PASSWD", _target_user, - nix::unistd::getuid().as_raw(), + rustix::process::getuid().as_raw(), true, ); @@ -598,16 +598,8 @@ fn resolve_target_user(matches: &clap::ArgMatches) -> Result Ok(user.name), - Ok(None) => Err(PasswdError::UnexpectedFailure(format!( - "cannot determine current username for uid {uid}" - ))), - Err(e) => Err(PasswdError::UnexpectedFailure(format!( - "cannot determine current username: {e}" - ))), - } + shadow_core::hardening::current_username() + .map_err(|e| PasswdError::UnexpectedFailure(e.to_string())) } /// Perform `chroot(2)` into the specified directory. @@ -622,10 +614,10 @@ fn do_chroot(dir: &str) -> Result<(), PasswdError> { } let path = std::path::Path::new(dir); - nix::unistd::chroot(path) + rustix::process::chroot(path) .map_err(|e| PasswdError::UnexpectedFailure(format!("cannot chroot to '{dir}': {e}")))?; - nix::unistd::chdir("/").map_err(|e| { + rustix::process::chdir("/").map_err(|e| { PasswdError::UnexpectedFailure(format!("cannot chdir to / after chroot: {e}")) })?; @@ -693,8 +685,8 @@ where { // Consolidate real + effective UID to root for file operations. // Some filesystem configurations check real UID. - if nix::unistd::geteuid().is_root() { - let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0)); + if rustix::process::geteuid().is_root() { + let _ = shadow_core::process::setuid(0); } // Block signals for the entire critical section (lock → write → unlock). @@ -763,7 +755,7 @@ where audit::log_user_event( "CHNG_PASSWD", username, - nix::unistd::getuid().as_raw(), + rustix::process::getuid().as_raw(), true, ); @@ -1045,7 +1037,7 @@ mod tests { /// and cross-user status tests now require euid 0. In CI these run inside /// a Docker container as root. fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } /// Helper to create a temp dir with an etc/shadow file. @@ -1431,23 +1423,33 @@ mod tests { #[test] fn test_core_dump_suppression() { + use rustix::process::{Resource, getrlimit}; // After calling suppress_core_dumps(), RLIMIT_CORE should be 0. shadow_core::hardening::suppress_core_dumps(); - let (soft, _hard) = - nix::sys::resource::getrlimit(nix::sys::resource::Resource::RLIMIT_CORE).unwrap(); - assert_eq!(soft, 0, "RLIMIT_CORE should be 0 after suppression"); + let rlim = getrlimit(Resource::Core); + assert_eq!( + rlim.current, + Some(0), + "RLIMIT_CORE should be 0 after suppression" + ); } #[test] fn test_raise_file_size_limit() { + use rustix::process::{Resource, getrlimit}; shadow_core::hardening::raise_file_size_limit(); - let (soft, _hard) = - nix::sys::resource::getrlimit(nix::sys::resource::Resource::RLIMIT_FSIZE).unwrap(); + let rlim = getrlimit(Resource::Fsize); // In environments where the hard limit is already restricted (containers, - // CI), we may not reach RLIM_INFINITY. Verify it's at least very large. + // CI), we may not reach RLIM_INFINITY. `None` means unlimited. + // Verify it's at least very large or unlimited. + let is_large = match rlim.current { + None => true, + Some(v) => v >= 1024 * 1024 * 1024, + }; assert!( - soft >= 1024 * 1024 * 1024 || soft == nix::sys::resource::RLIM_INFINITY, - "RLIMIT_FSIZE should be raised (got {soft})" + is_large, + "RLIMIT_FSIZE should be raised (got {:?})", + rlim.current ); } diff --git a/src/uu/pwck/Cargo.toml b/src/uu/pwck/Cargo.toml index bd08e5a..bfefe2e 100644 --- a/src/uu/pwck/Cargo.toml +++ b/src/uu/pwck/Cargo.toml @@ -20,7 +20,6 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "login-defs"] } uucore = { workspace = true } diff --git a/src/uu/useradd/Cargo.toml b/src/uu/useradd/Cargo.toml index 568d30a..e10dccc 100644 --- a/src/uu/useradd/Cargo.toml +++ b/src/uu/useradd/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs", "subid"] } uucore = { workspace = true } diff --git a/src/uu/useradd/src/useradd.rs b/src/uu/useradd/src/useradd.rs index 1ef3ba3..c555d92 100644 --- a/src/uu/useradd/src/useradd.rs +++ b/src/uu/useradd/src/useradd.rs @@ -1594,7 +1594,7 @@ mod tests { /// Skip tests that require root privileges. fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/src/uu/userdel/Cargo.toml b/src/uu/userdel/Cargo.toml index fe50486..95328c2 100644 --- a/src/uu/userdel/Cargo.toml +++ b/src/uu/userdel/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs", "subid"] } uucore = { workspace = true } diff --git a/src/uu/userdel/src/userdel.rs b/src/uu/userdel/src/userdel.rs index 6d4362e..075c0f7 100644 --- a/src/uu/userdel/src/userdel.rs +++ b/src/uu/userdel/src/userdel.rs @@ -96,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let root = SysRoot::new(prefix); // Must be root. - if !nix::unistd::getuid().is_root() { + if !rustix::process::getuid().is_root() { return Err(UserdelError::CantUpdatePasswd("Permission denied.".into()).into()); } @@ -434,7 +434,7 @@ mod tests { // Duplicated from tests/common/mod.rs — unit tests inside the crate // cannot import from the workspace-level tests directory. fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/src/uu/usermod/Cargo.toml b/src/uu/usermod/Cargo.toml index bca6c06..1370f3a 100644 --- a/src/uu/usermod/Cargo.toml +++ b/src/uu/usermod/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] clap = { workspace = true } -nix = { workspace = true } +rustix = { workspace = true } shadow-core = { workspace = true, features = ["shadow", "group", "gshadow", "login-defs", "subid"] } uucore = { workspace = true } diff --git a/src/uu/usermod/src/usermod.rs b/src/uu/usermod/src/usermod.rs index 7bab695..733fee5 100644 --- a/src/uu/usermod/src/usermod.rs +++ b/src/uu/usermod/src/usermod.rs @@ -98,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(Path::new); let root = SysRoot::new(prefix); - if !nix::unistd::getuid().is_root() { + if !rustix::process::getuid().is_root() { return Err(UsermodError::CantUpdate("Permission denied.".into()).into()); } @@ -331,7 +331,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /// Uses `fchownat` with `AT_SYMLINK_NOFOLLOW` so symlinks themselves are /// re-owned without following them. fn recursive_chown(path: &Path, old_uid: u32, new_uid: u32) { - use nix::fcntl::AtFlags; use std::os::unix::fs::MetadataExt; if let Ok(entries) = std::fs::read_dir(path) { @@ -339,12 +338,12 @@ fn recursive_chown(path: &Path, old_uid: u32, new_uid: u32) { let entry_path = entry.path(); if let Ok(meta) = std::fs::symlink_metadata(&entry_path) { if meta.uid() == old_uid { - let _ = nix::unistd::fchownat( - nix::fcntl::AT_FDCWD, + let _ = rustix::fs::chownat( + rustix::fs::CWD, &entry_path, - Some(nix::unistd::Uid::from_raw(new_uid)), + Some(rustix::process::Uid::from_raw(new_uid)), None, - AtFlags::AT_SYMLINK_NOFOLLOW, + rustix::fs::AtFlags::SYMLINK_NOFOLLOW, ); } if meta.is_dir() { @@ -357,12 +356,12 @@ fn recursive_chown(path: &Path, old_uid: u32, new_uid: u32) { if let Ok(meta) = std::fs::symlink_metadata(path) && meta.uid() == old_uid { - let _ = nix::unistd::fchownat( - nix::fcntl::AT_FDCWD, + let _ = rustix::fs::chownat( + rustix::fs::CWD, path, - Some(nix::unistd::Uid::from_raw(new_uid)), + Some(rustix::process::Uid::from_raw(new_uid)), None, - AtFlags::AT_SYMLINK_NOFOLLOW, + rustix::fs::AtFlags::SYMLINK_NOFOLLOW, ); } } @@ -526,7 +525,7 @@ mod tests { } fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() } #[test] diff --git a/tests/by-util/test_useradd.rs b/tests/by-util/test_useradd.rs index d24a1e4..9b11aef 100644 --- a/tests/by-util/test_useradd.rs +++ b/tests/by-util/test_useradd.rs @@ -110,7 +110,7 @@ fn test_defaults_flag() { // but we only care that clap parses it without error. If not root, we expect // exit 1 (permission denied). let code = run(&["useradd", "-D"]); - if nix::unistd::getuid().is_root() { + if rustix::process::getuid().is_root() { assert_eq!(code, 0, "-D should exit 0 when root"); } else { assert_eq!(code, 1, "-D should exit 1 when not root"); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index eabdfc9..54220ab 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -11,5 +11,5 @@ /// /// Returns `true` if the test should be skipped. pub fn skip_unless_root() -> bool { - !nix::unistd::geteuid().is_root() + !rustix::process::geteuid().is_root() }