From 0636af21a468f04971bad11eb4571bcb94fdfa81 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 19 Sep 2025 14:04:55 +0700 Subject: [PATCH 1/3] Add shadow.h support --- src/header/grp/mod.rs | 16 +- src/header/mod.rs | 1 + src/header/shadow/cbindgen.toml | 12 ++ src/header/shadow/mod.rs | 271 ++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 src/header/shadow/cbindgen.toml create mode 100644 src/header/shadow/mod.rs diff --git a/src/header/grp/mod.rs b/src/header/grp/mod.rs index ec013240c5..75b7479710 100644 --- a/src/header/grp/mod.rs +++ b/src/header/grp/mod.rs @@ -38,6 +38,8 @@ const SEPARATOR: char = ':'; #[cfg(target_os = "redox")] const SEPARATOR: char = ';'; +const GROUP_FILE: &core::ffi::CStr = c"/etc/group"; + #[derive(Clone, Copy, Debug)] struct DestBuffer { ptr: *mut u8, @@ -249,7 +251,7 @@ fn parse_grp(line: String, destbuf: Option) -> Result *mut group { - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return ptr::null_mut(); }; @@ -272,7 +274,7 @@ pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group { // MT-Unsafe race:grnam locale #[no_mangle] pub unsafe extern "C" fn getgrnam(name: *const c_char) -> *mut group { - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return ptr::null_mut(); }; @@ -314,7 +316,7 @@ pub unsafe extern "C" fn getgrgid_r( *result = ptr::null_mut(); } - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return ENOENT; }; @@ -368,7 +370,7 @@ pub unsafe extern "C" fn getgrnam_r( buflen: usize, result: *mut *mut group, ) -> c_int { - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return ENOENT; }; @@ -409,7 +411,7 @@ pub unsafe extern "C" fn getgrent() -> *mut group { let mut line_reader = unsafe { &mut *LINE_READER.get() }; if line_reader.is_none() { - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return ptr::null_mut(); }; *line_reader = Some(BufReader::new(db).lines()); @@ -445,7 +447,7 @@ pub unsafe extern "C" fn endgrent() { #[no_mangle] pub unsafe extern "C" fn setgrent() { let mut line_reader = unsafe { &mut *LINE_READER.get() }; - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return; }; *line_reader = Some(BufReader::new(db).lines()); @@ -471,7 +473,7 @@ pub unsafe extern "C" fn getgrouplist( return 0; }; - let Ok(db) = File::open(c"/etc/group".into(), fcntl::O_RDONLY) else { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { return 0; }; diff --git a/src/header/mod.rs b/src/header/mod.rs index 4ad22103f3..fec4f937b9 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -61,6 +61,7 @@ pub mod sched; pub mod semaphore; pub mod setjmp; pub mod sgtty; +pub mod shadow; pub mod signal; // TODO: spawn.h // TODO: stdalign.h (likely C implementation) diff --git a/src/header/shadow/cbindgen.toml b/src/header/shadow/cbindgen.toml new file mode 100644 index 0000000000..f826c22c6a --- /dev/null +++ b/src/header/shadow/cbindgen.toml @@ -0,0 +1,12 @@ +sys_includes = ["sys/types.h", "stdint.h"] +include_guard = "_RELIBC_SHADOW_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export] +include = ["group"] diff --git a/src/header/shadow/mod.rs b/src/header/shadow/mod.rs new file mode 100644 index 0000000000..369b81f019 --- /dev/null +++ b/src/header/shadow/mod.rs @@ -0,0 +1,271 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{ + cell::SyncUnsafeCell, + convert::TryInto, + mem, + ops::{Deref, DerefMut}, + pin::Pin, + ptr, + str::FromStr, +}; + +use alloc::{boxed::Box, string::String, vec::Vec}; + +use crate::{ + c_str::CStr, + fs::File, + header::{errno, fcntl, string::strlen}, + io::{prelude::*, BufReader, Lines}, + platform, + platform::types::*, +}; + +use super::errno::*; + +#[cfg(target_os = "linux")] +const SEPARATOR: char = ':'; + +#[cfg(target_os = "redox")] +const SEPARATOR: char = ';'; + +const SHADOW_FILE: &core::ffi::CStr = c"/etc/shadow"; + +#[derive(Clone, Copy, Debug)] +struct DestBuffer { + ptr: *mut u8, + len: usize, +} + +#[derive(Debug)] +enum MaybeAllocated { + Owned(Pin>), + Borrowed(DestBuffer), +} +impl Deref for MaybeAllocated { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts(dst.ptr, dst.len) + }, + } + } +} +impl DerefMut for MaybeAllocated { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts_mut(dst.ptr, dst.len) + }, + } + } +} + +static mut SHADOW_BUF: Option = None; +static mut SHADOW: spwd = spwd { + sp_namp: ptr::null_mut(), + sp_pwdp: ptr::null_mut(), + sp_lstchg: -1, + sp_min: -1, + sp_max: -1, + sp_warn: -1, + sp_inact: -1, + sp_expire: -1, + sp_flag: 0, +}; + +static LINE_READER: SyncUnsafeCell>>> = SyncUnsafeCell::new(None); + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct spwd { + pub sp_namp: *mut c_char, + pub sp_pwdp: *mut c_char, + pub sp_lstchg: c_long, + pub sp_min: c_long, + pub sp_max: c_long, + pub sp_warn: c_long, + pub sp_inact: c_long, + pub sp_expire: c_long, + pub sp_flag: c_ulong, +} + +#[derive(Debug)] +enum Error { + EOF, + BufTooSmall, + Syntax, +} + +#[derive(Debug)] +struct OwnedSpwd { + buffer: MaybeAllocated, + reference: spwd, +} + +impl OwnedSpwd { + fn into_global(self) -> *mut spwd { + unsafe { + SHADOW_BUF = Some(self.buffer); + SHADOW = self.reference; + &mut SHADOW + } + } +} +fn to_long(s: &str) -> c_long { + c_long::from_str(s).unwrap_or(-1) +} +fn to_ulong(s: &str) -> c_ulong { + c_ulong::from_str(s).unwrap_or(0) +} + +fn parse_spwd(line: String, destbuf: Option) -> Result { + let mut parts = line.split(SEPARATOR); + + let sp_namp_str = parts.next().ok_or(Error::Syntax)?; + let sp_pwdp_str = parts.next().ok_or(Error::Syntax)?; + + //TODO: these are not implemented redox-users crate + let sp_lstchg = to_long(parts.next().unwrap_or("")); + let sp_min = to_long(parts.next().unwrap_or("")); + let sp_max = to_long(parts.next().unwrap_or("")); + let sp_warn = to_long(parts.next().unwrap_or("")); + let sp_inact = to_long(parts.next().unwrap_or("")); + let sp_expire = to_long(parts.next().unwrap_or("")); + let sp_flag = to_ulong(parts.next().unwrap_or("")); + + let string_data_len = sp_namp_str.len() + 1 + sp_pwdp_str.len() + 1; + + let mut buffer = match destbuf { + Some(buf) => { + if buf.len < string_data_len { + platform::ERRNO.set(ERANGE); + return Err(Error::BufTooSmall); + } + MaybeAllocated::Borrowed(buf) + } + None => { + let mut vec = Vec::with_capacity(string_data_len); + vec.resize(string_data_len, 0); + MaybeAllocated::Owned(Box::into_pin(vec.into_boxed_slice())) + } + }; + + let (name_slice, rest) = buffer.split_at_mut(sp_namp_str.len() + 1); + name_slice[..sp_namp_str.len()].copy_from_slice(sp_namp_str.as_bytes()); + name_slice[sp_namp_str.len()] = 0; + + let (pwd_slice, _) = rest.split_at_mut(sp_pwdp_str.len() + 1); + pwd_slice[..sp_pwdp_str.len()].copy_from_slice(sp_pwdp_str.as_bytes()); + pwd_slice[sp_pwdp_str.len()] = 0; + + let sp_namp = name_slice.as_mut_ptr() as *mut c_char; + let sp_pwdp = pwd_slice.as_mut_ptr() as *mut c_char; + + let reference = spwd { + sp_namp, + sp_pwdp, + sp_lstchg, + sp_min, + sp_max, + sp_warn, + sp_inact, + sp_expire, + sp_flag, + }; + + Ok(OwnedSpwd { buffer, reference }) +} + +#[no_mangle] +pub unsafe extern "C" fn getspnam(name: *const c_char) -> *mut spwd { + let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else { + return ptr::null_mut(); + }; + let c_name = unsafe { CStr::from_ptr(name) }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { continue }; + if line.starts_with(c_name.to_str().unwrap_or("\0")) { + if let Ok(pwd) = parse_spwd(line, None) { + return pwd.into_global(); + } + } + } + ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn getspnam_r( + name: *const c_char, + result_buf: *mut spwd, + buffer: *mut c_char, + buflen: size_t, + result: *mut *mut spwd, +) -> c_int { + unsafe { *result = ptr::null_mut() }; + + let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else { + return ENOENT; + }; + let c_name = unsafe { CStr::from_ptr(name).to_str().unwrap_or("\0") }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { continue }; + if line.starts_with(c_name) { + let dest_buf = Some(DestBuffer { + ptr: buffer as *mut u8, + len: buflen, + }); + return match parse_spwd(line, dest_buf) { + Ok(sp) => { + unsafe { + *result_buf = sp.reference; + *result = result_buf; + } + 0 + } + Err(Error::BufTooSmall) => ERANGE, + _ => ENOENT, + }; + } + } + ENOENT +} + +#[no_mangle] +pub unsafe extern "C" fn setspent() { + let mut line_reader = unsafe { &mut *LINE_READER.get() }; + if let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) { + *line_reader = Some(BufReader::new(db).lines()); + } +} + +#[no_mangle] +pub unsafe extern "C" fn endspent() { + unsafe { + *LINE_READER.get() = None; + } +} + +#[no_mangle] +pub unsafe extern "C" fn getspent() -> *mut spwd { + let line_reader = unsafe { &mut *LINE_READER.get() }; + if line_reader.is_none() { + unsafe { + setspent(); + } + } + if let Some(lines) = line_reader { + if let Some(Ok(line)) = lines.next() { + if let Ok(sp) = parse_spwd(line, None) { + return sp.into_global(); + } + } + } + ptr::null_mut() +} From d3e44fa6b933cb350d36c9ef44dcf53095c1f08a Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 19 Sep 2025 14:37:49 +0700 Subject: [PATCH 2/3] Apparently need to import FILE constant --- src/header/shadow/cbindgen.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header/shadow/cbindgen.toml b/src/header/shadow/cbindgen.toml index f826c22c6a..8d36c91ba6 100644 --- a/src/header/shadow/cbindgen.toml +++ b/src/header/shadow/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h", "stdint.h"] +sys_includes = ["sys/types.h", "stdint.h", "stdio.h"] include_guard = "_RELIBC_SHADOW_H" language = "C" style = "Tag" From b8598f50ff4c5e544ef05a87ccb5e792b280e556 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Fri, 19 Sep 2025 15:41:05 +0700 Subject: [PATCH 3/3] Add argon2 crypt --- Cargo.lock | 22 ++++++++++++++++++++++ Cargo.toml | 1 + src/header/crypt/argon2.rs | 16 ++++++++++++++++ src/header/crypt/mod.rs | 19 +++++++++++-------- 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 src/header/crypt/argon2.rs diff --git a/Cargo.lock b/Cargo.lock index 83d70890c3..880db7f100 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -40,6 +52,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -498,6 +519,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "relibc" version = "0.2.5" dependencies = [ + "argon2", "base64ct", "bcrypt-pbkdf", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 239f8f16f0..18761f273e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ chrono = {version = "0.4", default-features = false, features = ["alloc"]} libm = "0.2" object = { version = "0.36.7", git = "https://gitlab.redox-os.org/andypython/object", default-features = false, features = ["elf", "read_core"] } spin = "0.9.8" +argon2 = "0.5.3" [dependencies.dlmalloc] path = "dlmalloc-rs" diff --git a/src/header/crypt/argon2.rs b/src/header/crypt/argon2.rs new file mode 100644 index 0000000000..87cb641f29 --- /dev/null +++ b/src/header/crypt/argon2.rs @@ -0,0 +1,16 @@ +use alloc::string::{String, ToString}; +use argon2::{ + password_hash::{PasswordHash, PasswordVerifier}, + Argon2, +}; + +pub fn crypt_argon2(key: &str, setting: &str) -> Option { + let hash = PasswordHash::new(setting).ok()?; + let argon2 = Argon2::default(); + + if argon2.verify_password(key.as_bytes(), &hash).is_ok() { + Some(setting.to_string()) + } else { + None + } +} diff --git a/src/header/crypt/mod.rs b/src/header/crypt/mod.rs index 32fe2359b7..b944d34255 100644 --- a/src/header/crypt/mod.rs +++ b/src/header/crypt/mod.rs @@ -19,6 +19,7 @@ use crate::{ platform::{self, types::*}, }; +mod argon2; mod blowfish; mod md5; mod pbkdf2; @@ -26,6 +27,7 @@ mod scrypt; mod sha; use self::{ + argon2::crypt_argon2, blowfish::crypt_blowfish, md5::crypt_md5, pbkdf2::crypt_pbkdf2, @@ -73,21 +75,22 @@ pub unsafe extern "C" fn crypt_r( let setting = unsafe { CStr::from_ptr(setting) } .to_str() .expect("setting must be utf-8"); - let setting_bytes = setting.as_bytes(); - let encoded = if setting_bytes[0] == b'$' && setting_bytes[1] != 0 && setting_bytes[2] != 0 { - if setting_bytes[1] == b'1' && setting_bytes[2] == b'$' { + let encoded = if setting.starts_with('$') { + if setting.starts_with("$1$") { crypt_md5(key, setting) - } else if setting_bytes[1] == b'2' && setting_bytes[3] == b'$' { + } else if setting.starts_with("$2") && setting.as_bytes().get(3) == Some(&b'$') { crypt_blowfish(key, setting) - } else if setting_bytes[1] == b'5' && setting_bytes[2] == b'$' { + } else if setting.starts_with("$5$") { crypt_sha(key, setting, Sha256) - } else if setting_bytes[1] == b'6' && setting_bytes[2] == b'$' { + } else if setting.starts_with("$6$") { crypt_sha(key, setting, Sha512) - } else if setting_bytes[1] == b'7' && setting_bytes[2] == b'$' { + } else if setting.starts_with("$7$") { crypt_scrypt(key, setting) - } else if setting_bytes[1] == b'8' && setting_bytes[2] == b'$' { + } else if setting.starts_with("$8$") { crypt_pbkdf2(key, setting) + } else if setting.starts_with("$argon2") { + crypt_argon2(key, setting) } else { platform::ERRNO.set(EINVAL); return ptr::null_mut();