diff --git a/src/header/grp/mod.rs b/src/header/grp/mod.rs index 6c5b0c16d4d449ae4350535c0702985d1260b815..ea1efd2dc94a578e97e1b127769e82735f21edad 100644 --- a/src/header/grp/mod.rs +++ b/src/header/grp/mod.rs @@ -1,8 +1,79 @@ //! grp implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/grp.h.html -use crate::platform::types::*; +use core::{ + convert::{TryFrom, TryInto}, + mem, + ops::{Deref, DerefMut}, + pin::Pin, + primitive::str, + ptr, slice, num::ParseIntError, str::Matches, +}; + +use lazy_static::lazy_static; + +use alloc::{borrow::ToOwned, string::{String, FromUtf8Error}}; +use libc::strncmp; + +use crate::{ + c_str::CStr, + fs::File, + header::{errno, fcntl, string::strlen}, + io, + io::{prelude::*, BufReader, Lines}, + platform::types::*, + platform, + sync::Mutex +}; + +use super::errno::*; + +#[derive(Clone, Copy, Debug)] +struct DestBuffer { + ptr: *mut u8, + len: usize, +} + +// Shamelessly stolen from pwd/mod.rs +#[derive(Debug)] +enum MaybeAllocated { + Owned(Pin<Box<[u8]>>), + 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<MaybeAllocated> = None; +static mut GROUP: group = group { + gr_name: ptr::null_mut(), + gr_passwd: ptr::null_mut(), + gr_gid: 0, + gr_mem: ptr::null_mut(), +}; + +lazy_static! { static ref LINE_READER: Mutex<Option<Lines<BufReader<File>>>> = Mutex::new(None); } #[repr(C)] +#[derive(Debug)] pub struct group { pub gr_name: *mut c_char, pub gr_passwd: *mut c_char, @@ -10,56 +81,264 @@ pub struct group { pub gr_mem: *mut *mut c_char, } -// #[no_mangle] +#[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<group> { + let gid = match buf[0..mem::size_of::<gid_t>()].try_into() { + Ok(buf) => gid_t::from_ne_bytes(buf), + Err(err) => return None + }; + + // We moved the gid to the beginning of the byte buffer so we can do this. + let mut parts = buf[mem::size_of::<gid_t>()..].split_mut(|&c| c == b'\0'); + + Some(group { + gr_name: parts.next()?.as_mut_ptr() as *mut i8, + gr_passwd: parts.next()?.as_mut_ptr() as *mut i8, + gr_gid: gid, + gr_mem: parts.next()?.as_mut_ptr() as *mut *mut c_char + // this will work because this points to the first string, which also happens to be the start of the array. The two are equivalent, just need to by typecast. + }) +} + +fn parse_grp(line: String, destbuf: Option<DestBuffer>) -> Result<OwnedGrp, Error> { + let mut buffer = line.to_owned().into_bytes(); + + let mut buffer = buffer + .into_iter() + .map(|i| if i == b':' { b'\0' } else { i }) + .chain([b'\0']) + .collect::<Vec<_>>(); + let mut buffer = buffer + .split_mut(|i| *i == b'\0'); + + let mut gr_gid: gid_t = 0; + let strings = { + let mut vec: Vec<u8> = Vec::new(); + + let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec(); + let gr_passwd = 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::<gid_t>() + .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); + + for i in buffer.next().ok_or(Error::EOF)? + .split(|b| *b == b',') + .filter(|i| i.len() > 0) { + + vec.extend(i.to_vec()); + vec.push(0); + } + + vec.extend(0usize.to_ne_bytes()); + + 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() { + unsafe { platform::errno = 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 + }) +} + +#[no_mangle] pub extern "C" fn getgrgid(gid: gid_t) -> *mut group { - unimplemented!(); + 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(); } -// #[no_mangle] +#[no_mangle] pub extern "C" fn getgrnam(name: *const c_char) -> *mut group { - unimplemented!(); + 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 unsafe { strncmp(grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name))) > 0 } { + return grp.into_global(); + } + } + + return ptr::null_mut(); } -// #[no_mangle] -pub extern "C" fn getgrgid_r( - gid: gid_t, - grp: *mut group, - buffer: *mut c_char, - bufsize: usize, - result: *mut *mut group, -) -> c_int { - unimplemented!(); +#[no_mangle] +pub 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 { + unsafe { + *result_buf = grp.reference; + *result = result_buf; + }; + + return 0; + } + } + + return ENOENT; } -// #[no_mangle] -pub extern "C" fn getgrnam_r( - name: *const c_char, - grp: *mut group, - buffer: *mut c_char, - bufsize: usize, - result: *mut *mut group, -) -> c_int { - unimplemented!(); +#[no_mangle] +pub 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 unsafe { strncmp(grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name))) > 0 } { + unsafe { + *result_buf = grp.reference; + *result = result_buf; + }; + + return 0; + } + } + + return ENOENT; } -// #[no_mangle] +#[no_mangle] pub extern "C" fn getgrent() -> *mut group { - unimplemented!(); + let mut line_reader = LINE_READER.lock(); + + 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(); + } } -// #[no_mangle] +#[no_mangle] pub extern "C" fn endgrent() { - unimplemented!(); + let mut line_reader = LINE_READER.lock(); + *line_reader = None; } -// #[no_mangle] +#[no_mangle] pub extern "C" fn setgrent() { - unimplemented!(); + let mut line_reader = LINE_READER.lock(); + let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return }; + *line_reader = Some(BufReader::new(db).lines()); } -/* #[no_mangle] -pub extern "C" fn func(args) -> c_int { - unimplemented!(); +pub extern "C" fn getgrouplist(user: *const c_char, group: gid_t, groups: *mut gid_t, ngroups: i32) -> i32 { + let mut grps = unsafe { Vec::<gid_t>::from_raw_parts(groups, 0, ngroups as usize) }; + let Ok(usr) = (unsafe { 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; }; + + for line in BufReader::new(db).lines() { + if grps.len() >= ngroups as usize { + return ngroups; + } + + match line { + Err(_) => return 0, + Ok(line) => { + let mut parts = line.split(':'); + + let group_name = parts.next().unwrap_or(""); + let group_password = parts.next().unwrap_or(""); + let group_id = parts.next().unwrap_or("-1").parse::<i32>().unwrap(); + let members = parts + .next() + .unwrap_or("") + .split(",") + .map(|i| i.trim()) + .collect::<Vec<_>>(); + + if members.iter().any(|i| *i == usr) { + grps.push(group_id); + } + } + }; + } + + if grps.len() <= 0 { + grps.push(group); + } + + return grps.len() as i32; } -*/ diff --git a/src/lib.rs b/src/lib.rs index a2c57c39d694513391597b20cea63680cfc856d4..fcddb73f0cdea430c738cde56a59cd0b1235b76b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ #![feature(stmt_expr_attributes)] #![feature(str_internals)] #![feature(thread_local)] +#![feature(vec_into_raw_parts)] #![allow(clippy::cast_lossless)] #![allow(clippy::cast_ptr_alignment)] #![allow(clippy::derive_hash_xor_eq)] diff --git a/tests/Makefile b/tests/Makefile index b9790c1eca596059843a5172e2b22301c48b71d0..47a54ada8fad63bdf5feedccf5f0711141070ab7 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -166,7 +166,11 @@ NAMES=\ pthread/barrier \ pthread/rwlock_trylock \ pthread/rwlock_randtest \ - pthread/mutex_recursive + pthread/mutex_recursive \ + grp/getgrouplist \ + grp/getgrgid_r \ + grp/getgrnam_r \ + grp/gr_iter \ # resource/getrusage # time/times diff --git a/tests/grp/getgrgid_r.c b/tests/grp/getgrgid_r.c new file mode 100644 index 0000000000000000000000000000000000000000..5792848d72380c5dd6d27d21d79f5ade159d341d --- /dev/null +++ b/tests/grp/getgrgid_r.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <grp.h> + +bool test_getgrgid(gid_t gid) { + struct group* out = getgrgid(gid); + + if (out == NULL) { + printf("Did not find a group %d", gid); + return false; + } + + printf("getgrgid\n"); + + char* start = out->gr_name; + int len = strlen(out->gr_name); + + printf(" %d = %s, GID: %d\n", gid, out->gr_name, out->gr_gid); + + return true; +} + +bool test_getgrgid_r(gid_t gid) { + char* buf[100]; + + struct group grp; + struct group* out = &grp; + struct group* tmp; + + int status = getgrgid_r(gid, out, buf, sizeof(buf), &tmp); + + if (out == NULL) { + printf("Did not find a group %d", gid); + return false; + } + + printf("getgrgid_r\n"); + + char* start = grp.gr_name; + int len = strlen(grp.gr_name); + + printf(" %d = %s, GID: %d\n", gid, grp.gr_name, grp.gr_gid); + + return true; +} + +int main(void) { + test_getgrgid(1050); + test_getgrgid_r(1050); +} \ No newline at end of file diff --git a/tests/grp/getgrnam_r.c b/tests/grp/getgrnam_r.c new file mode 100644 index 0000000000000000000000000000000000000000..2f0a3ac615d5514567542802c5300bd2b227d857 --- /dev/null +++ b/tests/grp/getgrnam_r.c @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <grp.h> + +bool test_getgrnam(char* gr_name) { + struct group* out = getgrnam(gr_name); + + if (out == NULL) { + printf("Did not find a group '%s'", gr_name); + return false; + } + + printf("getgrnam\n"); + + char* start = out->gr_name; + int len = strlen(out->gr_name); + + printf(" '%s' = %d\n", gr_name, out->gr_gid); + + return true; +} + +bool test_getgrnam_r(char* gr_name) { + char* buf[100]; + + struct group grp; + struct group* out = &grp; + struct group* tmp; + + int status = getgrnam_r(gr_name, out, buf, sizeof(buf), &tmp); + + if (out == NULL) { + printf("Did not find a group '%s'", gr_name); + return false; + } + + printf("getgrnam_r\n"); + + char* start = grp.gr_name; + int len = strlen(grp.gr_name); + + printf(" '%s' = %d\n", gr_name, out->gr_gid); + + return true; +} + +int main(void) { + test_getgrnam("lcake"); + test_getgrnam_r("lcake"); +} \ No newline at end of file diff --git a/tests/grp/getgrouplist.c b/tests/grp/getgrouplist.c new file mode 100644 index 0000000000000000000000000000000000000000..61e11d1a9aee853d87bd22d4fac5343404d60038 --- /dev/null +++ b/tests/grp/getgrouplist.c @@ -0,0 +1,15 @@ +#include <stdio.h> +#include <grp.h> + +int main(void) { + gid_t groups[20]; + int ngroup = 20; + int num_groups = getgrouplist("user", 1000, groups, &ngroup); + + printf("Num Groups: %d\n", num_groups); + + for (int i = 0; i < num_groups; i++) + printf("i: %d\n", groups[i]); + + return 0; +} \ No newline at end of file diff --git a/tests/grp/gr_iter.c b/tests/grp/gr_iter.c new file mode 100644 index 0000000000000000000000000000000000000000..dd7e59984feb35abcc3e48916516ef8be52f8482 --- /dev/null +++ b/tests/grp/gr_iter.c @@ -0,0 +1,10 @@ +#include <stdio.h> +#include <grp.h> +#include <string.h> + +int main(int argc, char** argv) { + printf("getgrent\n"); + + for (struct group* grp = getgrent(); grp != NULL; grp = getgrent()) + printf(" %s = %d\n", grp->gr_name, grp->gr_gid); +} \ No newline at end of file