Skip to main content

relibc/header/grp/
mod.rs

1//! `grp.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/grp.h.html>.
4
5use core::{
6    cell::SyncUnsafeCell,
7    convert::TryInto,
8    mem::{self, MaybeUninit},
9    num::ParseIntError,
10    ops::{Deref, DerefMut},
11    pin::Pin,
12    ptr, slice,
13};
14
15use alloc::string::{FromUtf8Error, String};
16
17use crate::{
18    error::ResultExt,
19    fs::File,
20    header::{errno, fcntl, limits, string::strlen},
21    io::{self, BufReader, Lines, prelude::*},
22    platform::{
23        self, Pal, Sys,
24        types::{c_char, c_int, c_void, gid_t, size_t},
25    },
26};
27
28use super::{
29    errno::{EINVAL, EIO, ENOENT, ERANGE},
30    string::strncmp,
31};
32
33/// cbindgen:ignore
34#[cfg(target_os = "linux")]
35const SEPARATOR: char = ':';
36
37/// cbindgen:ignore
38#[cfg(target_os = "redox")]
39const SEPARATOR: char = ';';
40
41/// cbindgen:ignore
42const GROUP_FILE: &core::ffi::CStr = c"/etc/group";
43
44#[derive(Clone, Copy, Debug)]
45struct DestBuffer {
46    ptr: *mut u8,
47    len: usize,
48}
49
50// Shamelessly stolen from pwd/mod.rs
51#[derive(Debug)]
52enum MaybeAllocated {
53    Owned(Pin<Box<[u8]>>),
54    Borrowed(DestBuffer),
55}
56impl Deref for MaybeAllocated {
57    type Target = [u8];
58
59    fn deref(&self) -> &Self::Target {
60        match self {
61            MaybeAllocated::Owned(boxed) => boxed,
62            MaybeAllocated::Borrowed(dst) => unsafe {
63                core::slice::from_raw_parts(dst.ptr, dst.len)
64            },
65        }
66    }
67}
68impl DerefMut for MaybeAllocated {
69    fn deref_mut(&mut self) -> &mut Self::Target {
70        match self {
71            MaybeAllocated::Owned(boxed) => boxed,
72            MaybeAllocated::Borrowed(dst) => unsafe {
73                core::slice::from_raw_parts_mut(dst.ptr, dst.len)
74            },
75        }
76    }
77}
78
79/// cbindgen:ignore
80static mut GROUP_BUF: Option<MaybeAllocated> = None;
81/// cbindgen:ignore
82static mut GROUP: group = group {
83    gr_name: ptr::null_mut(),
84    gr_passwd: ptr::null_mut(),
85    gr_gid: 0,
86    gr_mem: ptr::null_mut(),
87};
88
89/// cbindgen:ignore
90static LINE_READER: SyncUnsafeCell<Option<Lines<BufReader<File>>>> = SyncUnsafeCell::new(None);
91
92/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/grp.h.html>.
93#[allow(non_camel_case_types)]
94#[repr(C)]
95#[derive(Debug)]
96pub struct group {
97    /// The name of the group.
98    pub gr_name: *mut c_char,
99    /// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getgrnam.3.html>.
100    ///
101    /// Group password.
102    pub gr_passwd: *mut c_char,
103    /// Numerical group ID.
104    pub gr_gid: gid_t,
105    /// Pointer to a null-terminated array of character pointers to member
106    /// names.
107    pub gr_mem: *mut *mut c_char,
108}
109
110#[derive(Debug)]
111enum Error {
112    EOF,
113    Syntax,
114    BufTooSmall,
115    Misc(io::Error),
116    FromUtf8(FromUtf8Error),
117    ParseInt(ParseIntError),
118    Other,
119}
120
121#[derive(Debug)]
122struct OwnedGrp {
123    buffer: MaybeAllocated,
124    reference: group,
125}
126
127impl OwnedGrp {
128    fn into_global(self) -> *mut group {
129        unsafe {
130            GROUP_BUF = Some(self.buffer);
131            GROUP = self.reference;
132            &raw mut GROUP
133        }
134    }
135}
136
137fn split(buf: &mut [u8]) -> Option<group> {
138    let gr_gid = match buf[0..mem::size_of::<gid_t>()].try_into() {
139        Ok(buf) => gid_t::from_ne_bytes(buf),
140        Err(err) => return None,
141    };
142
143    // Get address of buffer for fixing up gr_mem
144    let buf_addr = buf.as_ptr() as usize;
145
146    // We moved the gid to the beginning of the byte buffer so we can do this.
147    let mut parts = buf[mem::size_of::<gid_t>()..].split_mut(|&c| c == b'\0');
148    let gr_name = parts.next()?.as_mut_ptr().cast::<c_char>();
149    let gr_passwd = parts.next()?.as_mut_ptr().cast::<c_char>();
150    let gr_mem = parts.next()?.as_mut_ptr().cast::<usize>();
151
152    // Adjust gr_mem address by buffer base address
153    // TODO: max group members length?
154    for i in 0..4096 {
155        unsafe {
156            if *gr_mem.add(i) == 0 {
157                // End of gr_mem pointer array
158                break;
159            }
160            *gr_mem.add(i) += buf_addr;
161        }
162    }
163
164    Some(group {
165        gr_name,
166        gr_passwd,
167        gr_gid,
168        gr_mem: gr_mem.cast::<*mut c_char>(),
169    })
170}
171
172fn parse_grp(line: String, destbuf: Option<DestBuffer>) -> Result<OwnedGrp, Error> {
173    let buffer = line.clone().into_bytes();
174
175    let mut buffer = buffer
176        .into_iter()
177        .map(|i| if i == SEPARATOR as u8 { b'\0' } else { i })
178        .chain(*b"\0")
179        .collect::<Vec<_>>();
180    let mut buffer = buffer.split_mut(|i| *i == b'\0');
181
182    let strings = {
183        let mut vec: Vec<u8> = Vec::new();
184
185        let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec();
186        let gr_passwd = buffer.next().ok_or(Error::EOF)?.to_vec();
187        let gr_gid = String::from_utf8(buffer.next().ok_or(Error::EOF)?.to_vec())
188            .map_err(Error::FromUtf8)?
189            .parse::<gid_t>()
190            .map_err(Error::ParseInt)?;
191
192        // Place the gid at the beginning of the byte buffer to make getting it back out again later, much faster.
193
194        vec.extend(gr_gid.to_ne_bytes());
195        vec.extend(gr_name);
196        vec.push(0);
197        vec.extend(gr_passwd);
198        vec.push(0);
199
200        let members = buffer.next().ok_or(Error::EOF)?;
201
202        // Get the offset of the members array
203        let member_array_start = vec.len();
204
205        // Push enough null pointers to fit all members
206        for _member in members
207            .split(|b| *b == b',')
208            .filter(|member| !member.is_empty())
209        {
210            vec.extend(0usize.to_ne_bytes());
211        }
212        let member_array_end = vec.len();
213        // Push a null pointer to terminate the members array
214        vec.extend(0usize.to_ne_bytes());
215
216        // Fill in member names
217        for (i, member) in members
218            .split(|b| *b == b',')
219            .filter(|member| !member.is_empty())
220            .enumerate()
221        {
222            let cur_offset = vec.len();
223
224            // This must be recomputed each time, because `vec` is undergoing extensions and so
225            // its backing memory might be reallocated and moved and its old memory deallocated.
226            let member_array = &mut vec[member_array_start..member_array_end];
227            let member_ptr = {
228                const SIZEOF_PTR: usize = mem::size_of::<*mut c_void>();
229                let start = i * SIZEOF_PTR;
230                let end = start + SIZEOF_PTR;
231                &mut member_array[start..end]
232            };
233
234            // Store offset to start of member, MUST BE ADJUSTED LATER BASED ON THE ADDRESS OF THE BUFFER
235            member_ptr.copy_from_slice(&cur_offset.to_ne_bytes());
236
237            vec.extend(member);
238            vec.push(0);
239        }
240
241        vec
242    };
243
244    let mut buffer = match destbuf {
245        None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())),
246        Some(buf) => {
247            let mut buf = MaybeAllocated::Borrowed(buf);
248
249            if buf.len() < strings.len() {
250                platform::ERRNO.set(errno::ERANGE);
251                return Err(Error::BufTooSmall);
252            }
253
254            buf[..strings.len()].copy_from_slice(&strings);
255            buf
256        }
257    };
258    let reference = split(&mut buffer).ok_or(Error::Other)?;
259
260    Ok(OwnedGrp { buffer, reference })
261}
262
263/// MT-Unsafe race:grgid locale
264///
265/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgrgid.html>.
266///
267/// Searches the group database for an entry with a matching `gid`.
268#[unsafe(no_mangle)]
269pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group {
270    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
271        return ptr::null_mut();
272    };
273
274    for line in BufReader::new(db).lines() {
275        let Ok(line) = line else {
276            return ptr::null_mut();
277        };
278        let Ok(grp) = parse_grp(line, None) else {
279            return ptr::null_mut();
280        };
281
282        if grp.reference.gr_gid == gid {
283            return grp.into_global();
284        }
285    }
286
287    ptr::null_mut()
288}
289
290/// MT-Unsafe race:grnam locale
291///
292/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgrnam.html>.
293///
294/// Searches the group database for an entry with a matching `name`.
295#[unsafe(no_mangle)]
296pub unsafe extern "C" fn getgrnam(name: *const c_char) -> *mut group {
297    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
298        return ptr::null_mut();
299    };
300
301    for line in BufReader::new(db).lines() {
302        let Ok(line) = line else {
303            return ptr::null_mut();
304        };
305
306        let Ok(grp) = parse_grp(line, None) else {
307            return ptr::null_mut();
308        };
309
310        // Attempt to prevent BO vulnerabilities
311        if unsafe {
312            strncmp(
313                grp.reference.gr_name,
314                name,
315                strlen(grp.reference.gr_name).min(strlen(name)),
316            ) == 0
317        } {
318            return grp.into_global();
319        }
320    }
321
322    ptr::null_mut()
323}
324
325/// MT-Safe locale
326///
327/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgrgid_r.html>.
328///
329/// Updates the `group` structure pointed to by `result_buf` and store a
330/// pointer to that structure at the location pointed to by `result`.
331#[unsafe(no_mangle)]
332pub unsafe extern "C" fn getgrgid_r(
333    gid: gid_t,
334    result_buf: *mut group,
335    buffer: *mut c_char,
336    buflen: size_t,
337    result: *mut *mut group,
338) -> c_int {
339    // In case of error or the requested entry is not found.
340    unsafe {
341        *result = ptr::null_mut();
342    }
343
344    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
345        return ENOENT;
346    };
347
348    for line in BufReader::new(db).lines() {
349        let Ok(line) = line else { return EINVAL };
350        let grp = match parse_grp(
351            line,
352            Some(DestBuffer {
353                ptr: buffer.cast::<u8>(),
354                len: buflen,
355            }),
356        ) {
357            Ok(grp) => grp,
358            Err(err) => {
359                return match err {
360                    Error::BufTooSmall => ERANGE,
361                    Error::EOF
362                    | Error::Syntax
363                    | Error::FromUtf8(_)
364                    | Error::ParseInt(_)
365                    | Error::Other => EINVAL,
366                    Error::Misc(io_err) => match io_err.kind() {
367                        io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => EINVAL,
368                        io::ErrorKind::NotFound => ENOENT,
369                        _ => EIO,
370                    },
371                };
372            }
373        };
374
375        if grp.reference.gr_gid == gid {
376            unsafe {
377                *result_buf = grp.reference;
378                *result = result_buf;
379            }
380
381            return 0;
382        }
383    }
384
385    // The requested entry was not found.
386    0
387}
388
389/// MT-Safe locale
390///
391/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getgrnam_r.html>.
392///
393/// Updates the `group` structure pointed to by `result_buf` and store a
394/// pointer to that structure at the location pointed to by `result`.
395#[unsafe(no_mangle)]
396pub unsafe extern "C" fn getgrnam_r(
397    name: *const c_char,
398    result_buf: *mut group,
399    buffer: *mut c_char,
400    buflen: size_t,
401    result: *mut *mut group,
402) -> c_int {
403    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
404        return ENOENT;
405    };
406
407    for line in BufReader::new(db).lines() {
408        let Ok(line) = line else { return EINVAL };
409        let Ok(grp) = parse_grp(
410            line,
411            Some(DestBuffer {
412                ptr: buffer.cast::<u8>(),
413                len: buflen,
414            }),
415        ) else {
416            return EINVAL;
417        };
418
419        if unsafe {
420            strncmp(
421                grp.reference.gr_name,
422                name,
423                strlen(grp.reference.gr_name).min(strlen(name)),
424            ) == 0
425        } {
426            unsafe {
427                *result_buf = grp.reference;
428                *result = result_buf;
429            }
430
431            return 0;
432        }
433    }
434
435    ENOENT
436}
437
438/// MT-Unsafe race:grent race:grentbuf locale
439///
440/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endgrent.html>.
441///
442/// Returns a pointer to a structure containing the broken-out fields of an
443/// entry in the group database.
444#[unsafe(no_mangle)]
445pub unsafe extern "C" fn getgrent() -> *mut group {
446    let mut line_reader = unsafe { &mut *LINE_READER.get() };
447
448    if line_reader.is_none() {
449        let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
450            return ptr::null_mut();
451        };
452        *line_reader = Some(BufReader::new(db).lines());
453    }
454
455    if let Some(lines) = line_reader.deref_mut() {
456        let Some(line) = lines.next() else {
457            return ptr::null_mut();
458        };
459        let Ok(line) = line else {
460            return ptr::null_mut();
461        };
462
463        if let Ok(grp) = parse_grp(line, None) {
464            grp.into_global()
465        } else {
466            ptr::null_mut()
467        }
468    } else {
469        ptr::null_mut()
470    }
471}
472
473/// MT-Unsafe race:grent locale
474///
475/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endgrent.html>.
476///
477/// Closes the group database.
478#[unsafe(no_mangle)]
479pub unsafe extern "C" fn endgrent() {
480    unsafe {
481        *LINE_READER.get() = None;
482    }
483}
484
485/// MT-Unsafe race:grent locale
486///
487/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endgrent.html>.
488///
489/// Rewinds the group database so that the next `getgrent()` call returns the
490/// first entry, allowing repeated searches.
491#[unsafe(no_mangle)]
492pub unsafe extern "C" fn setgrent() {
493    let line_reader = unsafe { &mut *LINE_READER.get() };
494    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
495        return;
496    };
497    *line_reader = Some(BufReader::new(db).lines());
498}
499
500// TODO should be guarded by `_DEFAULT_SOURCE`
501/// MT-Safe locale
502/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/getgrouplist.3.html>.
503///
504/// Get a list of groups to which a user belongs.
505#[unsafe(no_mangle)]
506pub unsafe extern "C" fn getgrouplist(
507    user: *const c_char,
508    group: gid_t,
509    groups: *mut gid_t,
510    ngroups: *mut c_int,
511) -> c_int {
512    let grps = unsafe {
513        slice::from_raw_parts_mut(groups.cast::<MaybeUninit<gid_t>>(), ngroups.read() as usize)
514    };
515
516    // FIXME: This API probably expects the group database to already exist in memory, as it
517    // doesn't seem to have any documented error handling.
518
519    let Ok(user) = (unsafe { crate::c_str::CStr::from_ptr(user).to_str() }) else {
520        return 0;
521    };
522
523    let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else {
524        return 0;
525    };
526
527    let mut groups_found: c_int = 0;
528
529    for line in BufReader::new(db).lines() {
530        let Ok(line) = line else {
531            return 0;
532        };
533
534        let mut parts = line.split(SEPARATOR);
535
536        let group_name = parts.next().unwrap_or("");
537        let group_password = parts.next().unwrap_or("");
538        let group_id = parts.next().unwrap_or("-1").parse::<c_int>().unwrap();
539        let members = parts
540            .next()
541            .unwrap_or("")
542            .split(",")
543            .map(|i| i.trim())
544            .collect::<Vec<_>>();
545
546        if !members.contains(&user) {
547            continue;
548        }
549
550        if let Some(dst) = grps.get_mut(groups_found as usize) {
551            dst.write(group_id);
552        }
553
554        groups_found = match groups_found.checked_add(1) {
555            Some(g) => g,
556            None => break,
557        };
558    }
559
560    unsafe {
561        ngroups.write(groups_found);
562    }
563
564    if groups_found as usize > grps.len() {
565        -1
566    } else {
567        groups_found
568    }
569}
570
571// TODO should be guarded by `_DEFAULT_SOURCE`
572/// MT-Safe locale
573/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/initgroups.3.html>.
574///
575/// Initialize the supplementary group access list.
576#[unsafe(no_mangle)]
577pub unsafe extern "C" fn initgroups(user: *const c_char, gid: gid_t) -> c_int {
578    let mut groups = [0; limits::NGROUPS_MAX];
579    let mut count = groups.len() as c_int;
580    if unsafe { getgrouplist(user, gid, groups.as_mut_ptr(), &raw mut count) < 0 } {
581        return -1;
582    }
583    unsafe { setgroups(count as size_t, groups.as_ptr()) }
584}
585
586// TODO should be guarded by `_DEFAULT_SOURCE`
587/// Non-POSIX, see <https://www.man7.org/linux/man-pages/man2/setgroups.2.html>.
588///
589/// Set a list of supplementary group IDs.
590#[unsafe(no_mangle)]
591pub unsafe extern "C" fn setgroups(size: size_t, list: *const gid_t) -> c_int {
592    unsafe { Sys::setgroups(size, list) }
593        .map(|()| 0)
594        .or_minus_one_errno()
595}