Skip to content
Snippets Groups Projects
mod.rs 12.4 KiB
Newer Older
//! grp implementation, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/grp.h.html
Jeremy Soller's avatar
Jeremy Soller committed

Jacob Schneider's avatar
Jacob Schneider committed
use core::{
    cell::SyncUnsafeCell,
Jacob Schneider's avatar
Jacob Schneider committed
    convert::{TryFrom, TryInto},
    mem::{self, MaybeUninit},
Jacob Lorentzon's avatar
Jacob Lorentzon committed
    num::ParseIntError,
Jacob Schneider's avatar
Jacob Schneider committed
    ops::{Deref, DerefMut},
    pin::Pin,
    primitive::str,
Jacob Lorentzon's avatar
Jacob Lorentzon committed
    ptr, slice,
    str::Matches,
Jacob Schneider's avatar
Jacob Schneider committed
};

Jacob Lorentzon's avatar
Jacob Lorentzon committed
use alloc::{
    borrow::ToOwned,
    string::{FromUtf8Error, String},
};
Jacob Schneider's avatar
Jacob Schneider committed

use crate::{
    c_str::CStr,
    fs::File,
Jeremy Soller's avatar
Jeremy Soller committed
    header::{errno, fcntl, limits, string::strlen, unistd},
Jacob Schneider's avatar
Jacob Schneider committed
    io,
    io::{prelude::*, BufReader, Lines},
    platform,
Jacob Lorentzon's avatar
Jacob Lorentzon committed
    platform::types::*,
    sync::Mutex,
Jacob Schneider's avatar
Jacob Schneider committed
};

use super::{errno::*, string::strncmp};
Jacob Schneider's avatar
Jacob Schneider committed

#[cfg(target_os = "linux")]
const SEPARATOR: char = ':';

#[cfg(target_os = "redox")]
const SEPARATOR: char = ';';

Jacob Schneider's avatar
Jacob Schneider committed
#[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(),
};

static LINE_READER: SyncUnsafeCell<Option<Lines<BufReader<File>>>> = SyncUnsafeCell::new(None);
Jeremy Soller's avatar
Jeremy Soller committed

Paul Sajna's avatar
Paul Sajna committed
#[repr(C)]
Jacob Schneider's avatar
Jacob Schneider committed
#[derive(Debug)]
Paul Sajna's avatar
Paul Sajna committed
pub struct group {
Jeremy Soller's avatar
Jeremy Soller committed
    pub gr_name: *mut c_char,
    pub gr_passwd: *mut c_char,
Paul Sajna's avatar
Paul Sajna committed
    pub gr_gid: gid_t,
Jeremy Soller's avatar
Jeremy Soller committed
    pub gr_mem: *mut *mut c_char,
Paul Sajna's avatar
Paul Sajna committed
}
Jeremy Soller's avatar
Jeremy Soller committed

Jacob Schneider's avatar
Jacob Schneider committed
#[derive(Debug)]
enum Error {
    EOF,
    SyntaxError,
    BufTooSmall,
    Misc(io::Error),
    FromUtf8Error(FromUtf8Error),
    ParseIntError(ParseIntError),
Jacob Lorentzon's avatar
Jacob Lorentzon committed
    Other,
Jacob Schneider's avatar
Jacob Schneider committed
}

#[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 gr_gid = match buf[0..mem::size_of::<gid_t>()].try_into() {
Jacob Schneider's avatar
Jacob Schneider committed
        Ok(buf) => gid_t::from_ne_bytes(buf),
Jacob Lorentzon's avatar
Jacob Lorentzon committed
        Err(err) => return None,
Jacob Schneider's avatar
Jacob Schneider committed
    };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

    // Get address of buffer for fixing up gr_mem
    let buf_addr = buf.as_ptr() as usize;

Jacob Schneider's avatar
Jacob Schneider committed
    // 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');
    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;
        }
    }
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
    Some(group {
        gr_name,
        gr_passwd,
        gr_gid,
        gr_mem: gr_mem as *mut *mut c_char,
Jacob Schneider's avatar
Jacob Schneider committed
    })
}

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 == SEPARATOR as u8 { b'\0' } else { i })
Jacob Schneider's avatar
Jacob Schneider committed
        .chain([b'\0'])
        .collect::<Vec<_>>();
Jacob Lorentzon's avatar
Jacob Lorentzon committed
    let mut buffer = buffer.split_mut(|i| *i == b'\0');
Jacob Schneider's avatar
Jacob Schneider committed

    let mut gr_gid: gid_t = 0;
    let strings = {
        let mut vec: Vec<u8> = Vec::new();
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        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()
        };
Jacob Schneider's avatar
Jacob Schneider committed
        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))?;
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        // Place the gid at the beginning of the byte buffer to make getting it back out again later, much faster.
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        vec.extend(gr_gid.to_ne_bytes());
        vec.extend(gr_name);
        vec.push(0);
        vec.extend(gr_passwd);
        vec.push(0);
Jacob Lorentzon's avatar
Jacob Lorentzon committed

        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
Jacob Schneider's avatar
Jacob Schneider committed
            .split(|b| *b == b',')
            .filter(|member| !member.is_empty())
Jacob Lorentzon's avatar
Jacob Lorentzon committed
        {
            vec.extend(0usize.to_ne_bytes());
Jacob Schneider's avatar
Jacob Schneider committed
        }
        // Push a null pointer to terminate the members array
Jacob Schneider's avatar
Jacob Schneider committed
        vec.extend(0usize.to_ne_bytes());
Jacob Lorentzon's avatar
Jacob Lorentzon committed

        // 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);
        }

Jacob Schneider's avatar
Jacob Schneider committed
        vec
    };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
    let mut buffer = match destbuf {
        None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())),
        Some(buf) => {
            let mut buf = MaybeAllocated::Borrowed(buf);
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
            if buf.len() < buf.len() {
Jacob Lorentzon's avatar
Jacob Lorentzon committed
                unsafe {
                    platform::errno = errno::ERANGE;
                }
Jacob Schneider's avatar
Jacob Schneider committed
                return Err(Error::BufTooSmall);
            }
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
            buf[..strings.len()].copy_from_slice(&strings);
            buf
        }
    };
    let reference = split(&mut buffer).ok_or(Error::Other)?;
Jacob Lorentzon's avatar
Jacob Lorentzon committed

    Ok(OwnedGrp { buffer, reference })
Jacob Schneider's avatar
Jacob Schneider committed
}

// MT-Unsafe race:grgid locale
Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group {
Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return ptr::null_mut();
    };
Jacob Schneider's avatar
Jacob Schneider committed

    for line in BufReader::new(db).lines() {
Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(line) = line else {
            return ptr::null_mut();
        };
        let Ok(grp) = parse_grp(line, None) else {
            return ptr::null_mut();
        };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        if grp.reference.gr_gid == gid {
            return grp.into_global();
        }
    }

    return ptr::null_mut();
Paul Sajna's avatar
Paul Sajna committed
}

// MT-Unsafe race:grnam locale
Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
pub unsafe extern "C" fn getgrnam(name: *const c_char) -> *mut group {
Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return ptr::null_mut();
    };
Jacob Schneider's avatar
Jacob Schneider committed

    for line in BufReader::new(db).lines() {
Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(line) = line else {
            return ptr::null_mut();
        };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(grp) = parse_grp(line, None) else {
            return ptr::null_mut();
        };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        // Attempt to prevent BO vulnerabilities
Jacob Lorentzon's avatar
Jacob Lorentzon committed
        if strncmp(
            grp.reference.gr_name,
            name,
            strlen(grp.reference.gr_name).min(strlen(name)),
        ) > 0
        {
Jacob Schneider's avatar
Jacob Schneider committed
            return grp.into_global();
        }
    }

    return ptr::null_mut();
Paul Sajna's avatar
Paul Sajna committed
}

Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
Jacob Lorentzon's avatar
Jacob Lorentzon committed
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 {
Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return ENOENT;
    };
Jacob Schneider's avatar
Jacob Schneider committed

    for line in BufReader::new(db).lines() {
        let Ok(line) = line else { return EINVAL };
Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(mut grp) = parse_grp(
            line,
            Some(DestBuffer {
                ptr: buffer as *mut u8,
                len: buflen,
            }),
        ) else {
            return EINVAL;
        };
Jacob Schneider's avatar
Jacob Schneider committed

        if grp.reference.gr_gid == gid {
            *result_buf = grp.reference;
            *result = result_buf;
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
            return 0;
        }
    }
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
    return ENOENT;
Paul Sajna's avatar
Paul Sajna committed
}

Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
Jacob Lorentzon's avatar
Jacob Lorentzon committed
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 {
Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return ENOENT;
    };
Jacob Schneider's avatar
Jacob Schneider committed

    for line in BufReader::new(db).lines() {
        let Ok(line) = line else { return EINVAL };
Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(mut grp) = parse_grp(
            line,
            Some(DestBuffer {
                ptr: buffer as *mut u8,
                len: buflen,
            }),
        ) else {
            return EINVAL;
        };
Jacob Schneider's avatar
Jacob Schneider committed

Jacob Lorentzon's avatar
Jacob Lorentzon committed
        if strncmp(
            grp.reference.gr_name,
            name,
            strlen(grp.reference.gr_name).min(strlen(name)),
        ) > 0
        {
            *result_buf = grp.reference;
            *result = result_buf;
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
            return 0;
        }
    }
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
    return ENOENT;
Paul Sajna's avatar
Paul Sajna committed
}

// MT-Unsafe race:grent race:grentbuf locale
Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
pub unsafe extern "C" fn getgrent() -> *mut group {
    let mut line_reader = &mut *LINE_READER.get();
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
    if line_reader.is_none() {
Jeremy Soller's avatar
Jeremy Soller committed
        let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
            return ptr::null_mut();
        };
Jacob Schneider's avatar
Jacob Schneider committed
        *line_reader = Some(BufReader::new(db).lines());
    }
Jacob Lorentzon's avatar
Jacob Lorentzon committed

    if let Some(lines) = line_reader.deref_mut() {
Jeremy Soller's avatar
Jeremy Soller committed
        let Some(line) = lines.next() else {
            return ptr::null_mut();
        };
        let Ok(line) = line else {
            return ptr::null_mut();
        };
Jacob Lorentzon's avatar
Jacob Lorentzon committed

Jacob Schneider's avatar
Jacob Schneider committed
        if let Ok(grp) = parse_grp(line, None) {
            return grp.into_global();
Jacob Lorentzon's avatar
Jacob Lorentzon committed
        } else {
            return ptr::null_mut();
        }
Jacob Schneider's avatar
Jacob Schneider committed
    } else {
        return ptr::null_mut();
    }
Paul Sajna's avatar
Paul Sajna committed
}

// MT-Unsafe race:grent locale
Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
pub unsafe extern "C" fn endgrent() {
    *(&mut *LINE_READER.get()) = None;
Paul Sajna's avatar
Paul Sajna committed
}

// MT-Unsafe race:grent locale
Jacob Schneider's avatar
Jacob Schneider committed
#[no_mangle]
pub unsafe extern "C" fn setgrent() {
    let mut line_reader = &mut *LINE_READER.get();
Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return;
    };
Jacob Schneider's avatar
Jacob Schneider committed
    *line_reader = Some(BufReader::new(db).lines());
Paul Sajna's avatar
Paul Sajna committed
}
Jeremy Soller's avatar
Jeremy Soller committed

// MT-Safe locale
// Not POSIX
Jeremy Soller's avatar
Jeremy Soller committed
#[no_mangle]
Jacob Lorentzon's avatar
Jacob Lorentzon committed
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::<MaybeUninit<gid_t>>(), 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.

Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(user) = (crate::c_str::CStr::from_ptr(user).to_str()) else {
        return 0;
    };
Jacob Schneider's avatar
Jacob Schneider committed

Jeremy Soller's avatar
Jeremy Soller committed
    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else {
        return 0;
    };
Jacob Schneider's avatar
Jacob Schneider committed

    let mut groups_found: c_int = 0;

Jacob Schneider's avatar
Jacob Schneider committed
    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::<c_int>().unwrap();
        let members = parts
            .next()
            .unwrap_or("")
            .split(",")
            .map(|i| i.trim())
            .collect::<Vec<_>>();

        if !members.iter().any(|i| *i == user) {
            continue;
Jacob Schneider's avatar
Jacob Schneider committed
        }

        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,
Jacob Schneider's avatar
Jacob Schneider committed
        };
    }

    ngroups.write(groups_found);
Jacob Schneider's avatar
Jacob Schneider committed

    if groups_found as usize > grps.len() {
        -1
    } else {
        grps.len() as c_int
    }
Jeremy Soller's avatar
Jeremy Soller committed
}
Jeremy Soller's avatar
Jeremy Soller committed

// 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())
}