mirror of
https://github.com/uutils/shadow.git
synced 2026-06-10 16:14:57 -07:00
shadow-core, tools: migrate nix to rustix — phase 1 (#140)
Replace straightforward nix API calls with rustix equivalents across 15 files. rustix is already a transitive dependency (via clap) and provides raw syscall access without libc overhead. Phase 1 covers simple 1:1 replacements: - UID/GID: getuid/geteuid/getgid → rustix::process - File ops: fsync, umask, chownat → rustix::fs - Process: getpid, kill (stale lock detection) → rustix::process - Filesystem: chroot, chdir → rustix::process 6 crates fully off nix: groupadd, groupdel, groupmod, useradd, userdel, usermod. 4 crates partially migrated (nix kept for setuid()): chpasswd, chage, chfn, chsh. 2 crates cleaned of unused nix/libc deps: pwck, grpck. Phase 2 (hardening.rs, passwd.rs, newgrp.rs, pam.rs) deferred — these have complex signal/termios/User::from_uid usage requiring more care. Tested on debian/alpine/fedora — all pass.
This commit is contained in:
@@ -60,6 +60,7 @@ thiserror = "2"
|
||||
|
||||
# Unix/Linux
|
||||
nix = { version = "0.30", features = ["user", "fs", "process", "signal", "term", "resource"] }
|
||||
rustix = { version = "1", features = ["process", "fs"] }
|
||||
libc = "0.2"
|
||||
|
||||
# Security
|
||||
|
||||
@@ -17,6 +17,7 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
libc = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
subtle = { workspace = true }
|
||||
|
||||
@@ -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<std::rc::Rc<()>>,
|
||||
);
|
||||
struct UmaskGuard(rustix::fs::Mode, std::marker::PhantomData<std::rc::Rc<()>>);
|
||||
|
||||
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(())
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -305,6 +306,8 @@ 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");
|
||||
// Uses nix for this test since rustix lacks fcntl_getfd. The nix
|
||||
// dependency remains in shadow-core for hardening.rs and pam.rs.
|
||||
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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
shadow-core = { workspace = true, features = ["shadow"] }
|
||||
uucore = { workspace = true }
|
||||
|
||||
|
||||
@@ -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,7 +536,7 @@ where
|
||||
{
|
||||
// Consolidate real + effective UID to root for file operations.
|
||||
// Some filesystem configurations check real UID.
|
||||
if nix::unistd::geteuid().is_root() {
|
||||
if rustix::process::geteuid().is_root() {
|
||||
let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
shadow-core = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
|
||||
|
||||
@@ -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,7 +160,7 @@ where
|
||||
F: FnOnce(&mut PasswdEntry) -> Result<(), String>,
|
||||
{
|
||||
// Consolidate real + effective UID to root for file operations.
|
||||
if nix::unistd::geteuid().is_root() {
|
||||
if rustix::process::geteuid().is_root() {
|
||||
let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,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 }
|
||||
|
||||
@@ -304,7 +304,7 @@ fn apply_password_changes(
|
||||
hash_config: Option<&(shadow_core::crypt::CryptMethod, Option<u32>)>,
|
||||
) -> UResult<()> {
|
||||
// Consolidate real + effective UID to root for file operations.
|
||||
if nix::unistd::geteuid().is_root() {
|
||||
if rustix::process::geteuid().is_root() {
|
||||
let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0));
|
||||
}
|
||||
|
||||
@@ -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}"))
|
||||
})?;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
shadow-core = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
|
||||
|
||||
@@ -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,7 +174,7 @@ fn mutate_passwd<F>(root: &SysRoot, username: &str, mutate: F) -> UResult<()>
|
||||
where
|
||||
F: FnOnce(&mut PasswdEntry) -> Result<(), String>,
|
||||
{
|
||||
if nix::unistd::geteuid().is_root() {
|
||||
if rustix::process::geteuid().is_root() {
|
||||
let _ = nix::unistd::setuid(nix::unistd::Uid::from_raw(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -456,7 +456,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn skip_unless_root() -> bool {
|
||||
!nix::unistd::geteuid().is_root()
|
||||
!rustix::process::geteuid().is_root()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn skip_unless_root() -> bool {
|
||||
!nix::unistd::geteuid().is_root()
|
||||
!rustix::process::geteuid().is_root()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn skip_unless_root() -> bool {
|
||||
!nix::unistd::geteuid().is_root()
|
||||
!rustix::process::geteuid().is_root()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user