//! grp implementation, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/grp.h.html use core::{ cell::SyncUnsafeCell, convert::{TryFrom, TryInto}, mem::{self, MaybeUninit}, num::ParseIntError, ops::{Deref, DerefMut}, pin::Pin, primitive::str, ptr, slice, str::Matches, }; use alloc::{ borrow::ToOwned, string::{FromUtf8Error, String}, }; use crate::{ c_str::CStr, fs::File, header::{errno, fcntl, limits, string::strlen, unistd}, io, io::{prelude::*, BufReader, Lines}, platform, platform::types::*, sync::Mutex, }; use super::{errno::*, string::strncmp}; #[cfg(target_os = "linux")] const SEPARATOR: char = ':'; #[cfg(target_os = "redox")] const SEPARATOR: char = ';'; #[derive(Clone, Copy, Debug)] struct DestBuffer { ptr: *mut u8, len: usize, } // Shamelessly stolen from pwd/mod.rs #[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 GROUP_BUF: Option = None; static mut GROUP: group = group { gr_name: ptr::null_mut(), gr_passwd: ptr::null_mut(), gr_gid: 0, gr_mem: ptr::null_mut(), }; static LINE_READER: SyncUnsafeCell>>> = SyncUnsafeCell::new(None); #[repr(C)] #[derive(Debug)] pub struct group { pub gr_name: *mut c_char, pub gr_passwd: *mut c_char, pub gr_gid: gid_t, pub gr_mem: *mut *mut c_char, } #[derive(Debug)] enum Error { EOF, SyntaxError, BufTooSmall, Misc(io::Error), FromUtf8Error(FromUtf8Error), ParseIntError(ParseIntError), Other, } #[derive(Debug)] struct OwnedGrp { buffer: MaybeAllocated, reference: group, } impl OwnedGrp { fn into_global(self) -> *mut group { unsafe { GROUP_BUF = Some(self.buffer); GROUP = self.reference; &mut GROUP } } } fn split(buf: &mut [u8]) -> Option { let gr_gid = match buf[0..mem::size_of::()].try_into() { Ok(buf) => gid_t::from_ne_bytes(buf), Err(err) => return None, }; // Get address of buffer for fixing up gr_mem let buf_addr = buf.as_ptr() as usize; // We moved the gid to the beginning of the byte buffer so we can do this. let mut parts = buf[mem::size_of::()..].split_mut(|&c| c == b'\0'); let gr_name = parts.next()?.as_mut_ptr() as *mut c_char; let gr_passwd = parts.next()?.as_mut_ptr() as *mut c_char; let gr_mem = parts.next()?.as_mut_ptr() as *mut usize; // Adjust gr_mem address by buffer base address // TODO: max group members length? for i in 0..4096 { unsafe { if *gr_mem.add(i) == 0 { // End of gr_mem pointer array break; } *gr_mem.add(i) += buf_addr; } } Some(group { gr_name, gr_passwd, gr_gid, gr_mem: gr_mem as *mut *mut c_char, }) } fn parse_grp(line: String, destbuf: Option) -> Result { let mut buffer = line.to_owned().into_bytes(); let mut buffer = buffer .into_iter() .map(|i| if i == SEPARATOR as u8 { b'\0' } else { i }) .chain([b'\0']) .collect::>(); let mut buffer = buffer.split_mut(|i| *i == b'\0'); let mut gr_gid: gid_t = 0; let strings = { let mut vec: Vec = Vec::new(); let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec(); let gr_passwd = if cfg!(target_os = "redox") { Vec::new() } else { buffer.next().ok_or(Error::EOF)?.to_vec() }; gr_gid = String::from_utf8(buffer.next().ok_or(Error::EOF)?.to_vec()) .map_err(|err| Error::FromUtf8Error(err))? .parse::() .map_err(|err| Error::ParseIntError(err))?; // Place the gid at the beginning of the byte buffer to make getting it back out again later, much faster. vec.extend(gr_gid.to_ne_bytes()); vec.extend(gr_name); vec.push(0); vec.extend(gr_passwd); vec.push(0); let members = buffer.next().ok_or(Error::EOF)?; // Get the pointer to the members array let member_array_ptr = unsafe { vec.as_mut_ptr().add(vec.len()) as *mut usize }; // Push enough null pointers to fit all members for _member in members .split(|b| *b == b',') .filter(|member| !member.is_empty()) { vec.extend(0usize.to_ne_bytes()); } // Push a null pointer to terminate the members array vec.extend(0usize.to_ne_bytes()); // Fill in member names for (i, member) in members .split(|b| *b == b',') .filter(|member| !member.is_empty()) .enumerate() { // Store offset to start of member, MUST BE ADJUSTED LATER BASED ON THE ADDRESS OF THE BUFFER unsafe { *member_array_ptr.add(i) = vec.len(); } vec.extend(member); vec.push(0); } vec }; let mut buffer = match destbuf { None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())), Some(buf) => { let mut buf = MaybeAllocated::Borrowed(buf); if buf.len() < buf.len() { platform::ERRNO.set(errno::ERANGE); return Err(Error::BufTooSmall); } buf[..strings.len()].copy_from_slice(&strings); buf } }; let reference = split(&mut buffer).ok_or(Error::Other)?; Ok(OwnedGrp { buffer, reference }) } // MT-Unsafe race:grgid locale #[no_mangle] pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group { let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut(); }; for line in BufReader::new(db).lines() { let Ok(line) = line else { return ptr::null_mut(); }; let Ok(grp) = parse_grp(line, None) else { return ptr::null_mut(); }; if grp.reference.gr_gid == gid { return grp.into_global(); } } return ptr::null_mut(); } // 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_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut(); }; for line in BufReader::new(db).lines() { let Ok(line) = line else { return ptr::null_mut(); }; let Ok(grp) = parse_grp(line, None) else { return ptr::null_mut(); }; // Attempt to prevent BO vulnerabilities if strncmp( grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name)), ) > 0 { return grp.into_global(); } } return ptr::null_mut(); } // MT-Safe locale #[no_mangle] pub unsafe extern "C" fn getgrgid_r( gid: gid_t, result_buf: *mut group, buffer: *mut c_char, buflen: usize, result: *mut *mut group, ) -> c_int { let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ENOENT; }; for line in BufReader::new(db).lines() { let Ok(line) = line else { return EINVAL }; let Ok(mut grp) = parse_grp( line, Some(DestBuffer { ptr: buffer as *mut u8, len: buflen, }), ) else { return EINVAL; }; if grp.reference.gr_gid == gid { *result_buf = grp.reference; *result = result_buf; return 0; } } return ENOENT; } // MT-Safe locale #[no_mangle] pub unsafe extern "C" fn getgrnam_r( name: *const c_char, result_buf: *mut group, buffer: *mut c_char, buflen: usize, result: *mut *mut group, ) -> c_int { let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ENOENT; }; for line in BufReader::new(db).lines() { let Ok(line) = line else { return EINVAL }; let Ok(mut grp) = parse_grp( line, Some(DestBuffer { ptr: buffer as *mut u8, len: buflen, }), ) else { return EINVAL; }; if strncmp( grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name)), ) > 0 { *result_buf = grp.reference; *result = result_buf; return 0; } } return ENOENT; } // MT-Unsafe race:grent race:grentbuf locale #[no_mangle] pub unsafe extern "C" fn getgrent() -> *mut group { let mut line_reader = &mut *LINE_READER.get(); if line_reader.is_none() { let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut(); }; *line_reader = Some(BufReader::new(db).lines()); } if let Some(lines) = line_reader.deref_mut() { let Some(line) = lines.next() else { return ptr::null_mut(); }; let Ok(line) = line else { return ptr::null_mut(); }; if let Ok(grp) = parse_grp(line, None) { return grp.into_global(); } else { return ptr::null_mut(); } } else { return ptr::null_mut(); } } // MT-Unsafe race:grent locale #[no_mangle] pub unsafe extern "C" fn endgrent() { *(&mut *LINE_READER.get()) = None; } // MT-Unsafe race:grent locale #[no_mangle] pub unsafe extern "C" fn setgrent() { let mut line_reader = &mut *LINE_READER.get(); let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return; }; *line_reader = Some(BufReader::new(db).lines()); } // MT-Safe locale // Not POSIX #[no_mangle] pub unsafe extern "C" fn getgrouplist( user: *const c_char, group: gid_t, groups: *mut gid_t, ngroups: *mut c_int, ) -> c_int { let mut grps = slice::from_raw_parts_mut(groups.cast::>(), ngroups.read() as usize); // FIXME: This API probably expects the group database to already exist in memory, as it // doesn't seem to have any documented error handling. let Ok(user) = (crate::c_str::CStr::from_ptr(user).to_str()) else { return 0; }; let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return 0; }; let mut groups_found: c_int = 0; for line in BufReader::new(db).lines() { let Ok(line) = line else { return 0; }; let mut parts = line.split(SEPARATOR); let group_name = parts.next().unwrap_or(""); let group_password = parts.next().unwrap_or(""); let group_id = parts.next().unwrap_or("-1").parse::().unwrap(); let members = parts .next() .unwrap_or("") .split(",") .map(|i| i.trim()) .collect::>(); if !members.iter().any(|i| *i == user) { continue; } if let Some(dst) = grps.get_mut(groups_found as usize) { dst.write(group_id); } groups_found = match groups_found.checked_add(1) { Some(g) => g, None => break, }; } ngroups.write(groups_found); if groups_found as usize > grps.len() { -1 } else { grps.len() as c_int } } // MT-Safe locale // Not POSIX #[no_mangle] pub unsafe extern "C" fn initgroups(user: *const c_char, gid: gid_t) -> c_int { let mut groups = [0; limits::NGROUPS_MAX]; let mut count = groups.len() as c_int; if getgrouplist(user, gid, groups.as_mut_ptr(), &mut count) < 0 { return -1; } unistd::setgroups(count as size_t, groups.as_ptr()) }