From 7fff6d51aacc1a957c95873fc603b7945a934199 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 19:55:48 +0200 Subject: [PATCH 01/13] unistd: Remove trailing whitespace --- src/unistd.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/unistd.rs b/src/unistd.rs index 401357d3da..42c6d5835d 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -804,7 +804,7 @@ pub enum Whence { SeekCur = libc::SEEK_CUR, /// Specify an offset relative to the end of the file. SeekEnd = libc::SEEK_END, - /// Specify an offset relative to the next location in the file greater than or + /// Specify an offset relative to the next location in the file greater than or /// equal to offset that contains some data. If offset points to /// some data, then the file offset is set to offset. #[cfg(any(target_os = "dragonfly", target_os = "freebsd", @@ -813,7 +813,7 @@ pub enum Whence { target_arch = "mips64")))))] SeekData = libc::SEEK_DATA, /// Specify an offset relative to the next hole in the file greater than - /// or equal to offset. If offset points into the middle of a hole, then + /// or equal to offset. If offset points into the middle of a hole, then /// the file offset should be set to offset. If there is no hole past offset, /// then the file offset should be adjusted to the end of the file (i.e., there /// is an implicit hole at the end of any file). @@ -1361,7 +1361,7 @@ pub enum SysconfVar { OPEN_MAX = libc::_SC_OPEN_MAX, #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", target_os="linux", target_os = "macos", target_os="openbsd"))] - /// The implementation supports the Advisory Information option. + /// The implementation supports the Advisory Information option. _POSIX_ADVISORY_INFO = libc::_SC_ADVISORY_INFO, #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", target_os="linux", target_os = "macos", target_os="netbsd", @@ -1380,7 +1380,7 @@ pub enum SysconfVar { target_os="openbsd"))] /// The implementation supports the Process CPU-Time Clocks option. _POSIX_CPUTIME = libc::_SC_CPUTIME, - /// The implementation supports the File Synchronization option. + /// The implementation supports the File Synchronization option. _POSIX_FSYNC = libc::_SC_FSYNC, #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", target_os="linux", target_os = "macos", target_os="openbsd"))] @@ -1495,7 +1495,7 @@ pub enum SysconfVar { target_os="linux", target_os = "macos", target_os="openbsd"))] /// The implementation supports timeouts. _POSIX_TIMEOUTS = libc::_SC_TIMEOUTS, - /// The implementation supports timers. + /// The implementation supports timers. _POSIX_TIMERS = libc::_SC_TIMERS, #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios", target_os="linux", target_os = "macos", target_os="openbsd"))] From a154b26e49062ecd4975b690378327969d73192e Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:07:12 +0200 Subject: [PATCH 02/13] unistd: Add setgroups() --- src/unistd.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/unistd.rs b/src/unistd.rs index 42c6d5835d..28220e5d14 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -464,7 +464,7 @@ pub fn mkdir<P: ?Sized + NixPath>(path: &P, mode: Mode) -> Result<()> { /// fn main() { /// let tmp_dir = TempDir::new("test_fifo").unwrap(); /// let fifo_path = tmp_dir.path().join("foo.pipe"); -/// +/// /// // create new fifo and give read, write and execute rights to the owner /// match unistd::mkfifo(&fifo_path, stat::S_IRWXU) { /// Ok(_) => println!("created {:?}", fifo_path), @@ -1047,6 +1047,51 @@ pub fn setgid(gid: Gid) -> Result<()> { Errno::result(res).map(drop) } +/// Set the list of supplementary group IDs for the calling process. +/// +/// *Note:* On macOS, `getgroups()` may not return the same group list set by +/// calling `setgroups()`. The use of `setgroups()` on macOS is 'highly +/// discouraged' by Apple. Developers are referred to the `opendirectoryd` +/// daemon and its set of APIs. +/// +/// [Further reading](http://man7.org/linux/man-pages/man2/getgroups.2.html) +/// +/// # Examples +/// +/// `setgroups` can be used when dropping privileges from the root user to a +/// specific user and group. For example, given the user `www-data` with UID +/// `33` and the group `backup` with the GID `34`, one could switch user as +/// follows: +/// ``` +/// let uid = Uid::from_raw(33); +/// let gid = Gid::from_raw(34); +/// setgroups(&[gid])?; +/// setgid(gid)?; +/// setuid(uid)?; +/// ``` +pub fn setgroups(groups: &[Gid]) -> Result<()> { + cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] { + type setgroups_ngroups_t = c_int; + } else { + type setgroups_ngroups_t = size_t; + } + } + // FIXME: On the platforms we currently support, the `Gid` struct has the + // same representation in memory as a bare `gid_t`. This is not necessarily + // the case on all Rust platforms, though. See RFC 1785. + let res = unsafe { + libc::setgroups(groups.len() as setgroups_ngroups_t, groups.as_ptr() as *const gid_t) + }; + + Errno::result(res).map(|_| ()) +} + /// Suspend the thread until a signal is received /// /// See also [pause(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html) From 5ff0c35998e1bfd9907e2e9c73681acc99143431 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:07:56 +0200 Subject: [PATCH 03/13] unistd: Add getgroups() --- src/unistd.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/unistd.rs b/src/unistd.rs index 28220e5d14..38f52e4ec2 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1047,6 +1047,49 @@ pub fn setgid(gid: Gid) -> Result<()> { Errno::result(res).map(drop) } +/// Get the list of supplementary group IDs of the calling process. +/// +/// *Note:* On macOS, `getgroups()` behavior differs somewhat from other Unix +/// platforms. It returns the current group access list for the user associated +/// with the effective user id of the process; the group access list may change +/// over the lifetime of the process, and it is not affected by calls to +/// `setgroups()`. +/// +/// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html) +pub fn getgroups() -> Result<Vec<Gid>> { + // First get the number of groups so we can size our Vec + use std::ptr; + let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; + let mut size = Errno::result(ret)?; + + // Now actually get the groups. We try multiple times in case the number of + // groups has changed since the first call to getgroups() and the buffer is + // now too small + let mut groups = Vec::<Gid>::with_capacity(size as usize); + loop { + // FIXME: On the platforms we currently support, the `Gid` struct has + // the same representation in memory as a bare `gid_t`. This is not + // necessarily the case on all Rust platforms, though. See RFC 1785. + let ret = unsafe { libc::getgroups(size, groups.as_mut_ptr() as *mut gid_t) }; + + match Errno::result(ret) { + Ok(s) => { + unsafe { groups.set_len(s as usize) }; + return Ok(groups); + }, + Err(Error::Sys(Errno::EINVAL)) => { + // EINVAL indicates that size was too small, so trigger a + // resize of the groups Vec and try again... + let cap = groups.capacity(); + unsafe { groups.set_len(cap) }; + groups.reserve(1); + size = groups.capacity() as c_int; + }, + Err(e) => return Err(e) + } + } +} + /// Set the list of supplementary group IDs for the calling process. /// /// *Note:* On macOS, `getgroups()` may not return the same group list set by From 89b705519e34396319fb2869a7102c511543387c Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:08:24 +0200 Subject: [PATCH 04/13] test_unistd: Add test for getgroups/setgroups() --- test/test_unistd.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 627eb09b0e..365ec00d8c 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -122,6 +122,29 @@ mod linux_android { } } +#[test] +// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +fn test_setgroups() { + // Skip this test when not run as root as `setgroups()` requires root. + if !Uid::current().is_root() { + return + } + + // Save the existing groups + let old_groups = getgroups().unwrap(); + + // Set some new made up groups + let groups = [Gid::from_raw(123), Gid::from_raw(456)]; + setgroups(&groups).unwrap(); + + let new_groups = getgroups().unwrap(); + assert_eq!(new_groups, groups); + + // Revert back to the old groups + setgroups(&old_groups).unwrap(); +} + macro_rules! execve_test_factory( ($test_name:ident, $syscall:ident, $exe: expr) => ( #[test] From 7263c5a185e3b3f4991719d417995711f0cb8745 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:09:18 +0200 Subject: [PATCH 05/13] unistd: Add initgroups() --- src/unistd.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/unistd.rs b/src/unistd.rs index 38f52e4ec2..ea4862c288 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1135,6 +1135,41 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { Errno::result(res).map(|_| ()) } +/// Initialize the supplementary group access list. Sets the supplementary +/// group IDs for the calling process using all groups that `user` is a member +/// of. The additional group `group` is also added to the list. +/// +/// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html) +/// +/// # Examples +/// +/// `initgroups` can be used when dropping privileges from the root user to +/// another user. For example, given the user `www-data`, we could look up the +/// UID and GID for the user in the system's password database (usually found +/// in `/etc/passwd`). If the `www-data` user's UID and GID were `33` and `33`, +/// respectively, one could switch user as follows: +/// ``` +/// let user = CString::new("www-data").unwrap(); +/// let uid = Uid::from_raw(33); +/// let gid = Gid::from_raw(33); +/// initgroups(&user, gid)?; +/// setgid(gid)?; +/// setuid(uid)?; +/// ``` +pub fn initgroups(user: &CString, group: Gid) -> Result<()> { + cfg_if! { + if #[cfg(any(target_os = "ios", target_os = "macos"))] { + type initgroups_group_t = c_int; + } else { + type initgroups_group_t = gid_t; + } + } + let gid: gid_t = group.into(); + let res = unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) }; + + Errno::result(res).map(|_| ()) +} + /// Suspend the thread until a signal is received /// /// See also [pause(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html) From 4a2d65ea65b0394921eb81f051e1efc6b89f5c93 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:09:51 +0200 Subject: [PATCH 06/13] unistd: Add getgrouplist() --- src/unistd.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/unistd.rs b/src/unistd.rs index ea4862c288..21a398990f 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1135,6 +1135,70 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { Errno::result(res).map(|_| ()) } +/// Calculate the supplementary group access list. Gets the group IDs of all +/// groups that `user` is a member of. The additional group `group` is also +/// added to the list. +/// +/// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html) +/// +/// # Errors +/// +/// Although the `getgrouplist()` call does not return any specific +/// errors on any known platforms, this implementation will return a system +/// error of `EINVAL` if the number of groups to be fetched exceeds the +/// `NGROUPS_MAX` sysconf value. This mimics the behaviour of `getgroups()` +/// and `setgroups()`. Additionally, while some implementations will return a +/// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation +/// will only ever return the complete list or else an error. +pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> { + let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { + Ok(Some(n)) => n as c_int, + Ok(None) | Err(_) => <c_int>::max_value(), + }; + use std::cmp::min; + let mut ngroups = min(ngroups_max, 8); + let mut groups = Vec::<Gid>::with_capacity(ngroups as usize); + cfg_if! { + if #[cfg(any(target_os = "ios", target_os = "macos"))] { + type getgrouplist_group_t = c_int; + } else { + type getgrouplist_group_t = gid_t; + } + } + let gid: gid_t = group.into(); + loop { + let ret = unsafe { + libc::getgrouplist(user.as_ptr(), + gid as getgrouplist_group_t, + groups.as_mut_ptr() as *mut getgrouplist_group_t, + &mut ngroups) + }; + // BSD systems only return 0 or -1, Linux returns ngroups on success. + if ret >= 0 { + unsafe { groups.set_len(ngroups as usize) }; + return Ok(groups); + } else if ret == -1 { + // Returns -1 if ngroups is too small, but does not set errno. + // BSD systems will still fill the groups buffer with as many + // groups as possible, but Linux manpages do not mention this + // behavior. + + let cap = groups.capacity(); + if cap >= ngroups_max as usize { + // We already have the largest capacity we can, give up + return Err(Error::invalid_argument()); + } + + // Reserve space for at least ngroups + groups.reserve(ngroups as usize); + + // Even if the buffer gets resized to bigger than ngroups_max, + // don't ever ask for more than ngroups_max groups + ngroups = min(ngroups_max, groups.capacity() as c_int); + } + } +} + /// Initialize the supplementary group access list. Sets the supplementary /// group IDs for the calling process using all groups that `user` is a member /// of. The additional group `group` is also added to the list. From 688667eff8fe2fd9c5d3d91e4bf53e99284d894e Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:10:27 +0200 Subject: [PATCH 07/13] test_unistd: Add test for getgrouplist/initgroups() --- test/test_unistd.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 365ec00d8c..73e8b8ab31 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -145,6 +145,37 @@ fn test_setgroups() { setgroups(&old_groups).unwrap(); } +#[test] +// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +fn test_initgroups() { + // Skip this test when not run as root as `initgroups()` and `setgroups()` + // require root. + if !Uid::current().is_root() { + return + } + + // Save the existing groups + let old_groups = getgroups().unwrap(); + + // It doesn't matter if the root user is not called "root" or if a user + // called "root" doesn't exist. We are just checking that the extra, + // made-up group, `123`, is set. + // FIXME: This only tests half of initgroups' functionality. + let user = CString::new("root").unwrap(); + let group = Gid::from_raw(123); + let group_list = getgrouplist(&user, group).unwrap(); + assert!(group_list.contains(&group)); + + initgroups(&user, group).unwrap(); + + let new_groups = getgroups().unwrap(); + assert_eq!(new_groups, group_list); + + // Revert back to the old groups + setgroups(&old_groups).unwrap(); +} + macro_rules! execve_test_factory( ($test_name:ident, $syscall:ident, $exe: expr) => ( #[test] From e36d4afc34e0c97b99ac7f1cbb4a3aeaf14e0ab4 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 27 Aug 2017 20:13:06 +0200 Subject: [PATCH 08/13] Add changelog entry for #733 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 351f7deca7..f09b4bed34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#771](https://github.com/nix-rust/nix/pull/771)) - Added `nix::sys::uio::{process_vm_readv, process_vm_writev}` on Linux ([#568](https://github.com/nix-rust/nix/pull/568)) +- Added `nix::unistd::{getgroups, setgroups, getgrouplist, initgroups}`. ([#733](https://github.com/nix-rust/nix/pull/733)) ### Changed - Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692)) From a7ebd89b428930c492d7cd2905cc0741c108a4ed Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sat, 9 Sep 2017 21:18:44 +0200 Subject: [PATCH 09/13] groups tests: Add groups mutex and print message when tests skipped Fix groups mutex name --- test/test.rs | 3 +++ test/test_unistd.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/test/test.rs b/test/test.rs index 1b73c4ffa4..f4942d3dc1 100644 --- a/test/test.rs +++ b/test/test.rs @@ -43,6 +43,9 @@ lazy_static! { /// Any test that changes the process's current working directory must grab /// this mutex pub static ref CWD_MTX: Mutex<()> = Mutex::new(()); + /// Any test that changes the process's supplementary groups must grab this + /// mutex + pub static ref GROUPS_MTX: Mutex<()> = Mutex::new(()); /// Any test that creates child processes must grab this mutex, regardless /// of what it does with those children. pub static ref FORK_MTX: Mutex<()> = Mutex::new(()); diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 73e8b8ab31..2bb432d0d1 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -128,9 +128,16 @@ mod linux_android { fn test_setgroups() { // Skip this test when not run as root as `setgroups()` requires root. if !Uid::current().is_root() { + use std::io; + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!(handle, "test_setgroups requires root privileges. Skipping test.").unwrap(); return } + #[allow(unused_variables)] + let m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test"); + // Save the existing groups let old_groups = getgroups().unwrap(); @@ -152,9 +159,16 @@ fn test_initgroups() { // Skip this test when not run as root as `initgroups()` and `setgroups()` // require root. if !Uid::current().is_root() { + use std::io; + let stderr = io::stderr(); + let mut handle = stderr.lock(); + writeln!(handle, "test_initgroups requires root privileges. Skipping test.").unwrap(); return } + #[allow(unused_variables)] + let m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test"); + // Save the existing groups let old_groups = getgroups().unwrap(); From 57246cfe56559e067fa9ee507746c42e4a92a958 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Thu, 21 Sep 2017 12:53:06 +0200 Subject: [PATCH 10/13] unistd: Make getgrouplist and initgroups take &CStr Don't need to call as_c_str() --- src/unistd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/unistd.rs b/src/unistd.rs index 21a398990f..8feb70ee22 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1150,7 +1150,7 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { /// and `setgroups()`. Additionally, while some implementations will return a /// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation /// will only ever return the complete list or else an error. -pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> { +pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { Ok(Some(n)) => n as c_int, Ok(None) | Err(_) => <c_int>::max_value(), @@ -1220,7 +1220,7 @@ pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> { /// setgid(gid)?; /// setuid(uid)?; /// ``` -pub fn initgroups(user: &CString, group: Gid) -> Result<()> { +pub fn initgroups(user: &CStr, group: Gid) -> Result<()> { cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { type initgroups_group_t = c_int; From a75f3fa04c12d5190b39fb9c03f67c301f61f8a8 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sat, 30 Sep 2017 21:09:06 +0200 Subject: [PATCH 11/13] unistd: groups: Respond to minor PR feedback items --- src/unistd.rs | 41 ++++++++++++++++++++++------------------- test/test_unistd.rs | 15 +++++++-------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/unistd.rs b/src/unistd.rs index 8feb70ee22..f596b75178 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -6,14 +6,13 @@ use fcntl::{fcntl, OFlag, O_CLOEXEC, FD_CLOEXEC}; use fcntl::FcntlArg::F_SETFD; use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t}; -use std::mem; +use std::{self, fmt, mem}; use std::ffi::{CString, CStr, OsString, OsStr}; use std::os::unix::ffi::{OsStringExt, OsStrExt}; use std::os::unix::io::RawFd; use std::path::{PathBuf}; use void::Void; use sys::stat::Mode; -use std::fmt; #[cfg(any(target_os = "android", target_os = "linux"))] pub use self::pivot_root::*; @@ -1058,19 +1057,19 @@ pub fn setgid(gid: Gid) -> Result<()> { /// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html) pub fn getgroups() -> Result<Vec<Gid>> { // First get the number of groups so we can size our Vec - use std::ptr; - let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; - let mut size = Errno::result(ret)?; + let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) }; // Now actually get the groups. We try multiple times in case the number of // groups has changed since the first call to getgroups() and the buffer is - // now too small - let mut groups = Vec::<Gid>::with_capacity(size as usize); + // now too small. + let mut groups = Vec::<Gid>::with_capacity(Errno::result(ret)? as usize); loop { // FIXME: On the platforms we currently support, the `Gid` struct has // the same representation in memory as a bare `gid_t`. This is not // necessarily the case on all Rust platforms, though. See RFC 1785. - let ret = unsafe { libc::getgroups(size, groups.as_mut_ptr() as *mut gid_t) }; + let ret = unsafe { + libc::getgroups(groups.capacity() as c_int, groups.as_mut_ptr() as *mut gid_t) + }; match Errno::result(ret) { Ok(s) => { @@ -1078,12 +1077,12 @@ pub fn getgroups() -> Result<Vec<Gid>> { return Ok(groups); }, Err(Error::Sys(Errno::EINVAL)) => { - // EINVAL indicates that size was too small, so trigger a - // resize of the groups Vec and try again... + // EINVAL indicates that the buffer size was too small. Trigger + // the internal buffer resizing logic of `Vec` by requiring + // more space than the current capacity. let cap = groups.capacity(); unsafe { groups.set_len(cap) }; groups.reserve(1); - size = groups.capacity() as c_int; }, Err(e) => return Err(e) } @@ -1103,7 +1102,7 @@ pub fn getgroups() -> Result<Vec<Gid>> { /// /// `setgroups` can be used when dropping privileges from the root user to a /// specific user and group. For example, given the user `www-data` with UID -/// `33` and the group `backup` with the GID `34`, one could switch user as +/// `33` and the group `backup` with the GID `34`, one could switch the user as /// follows: /// ``` /// let uid = Uid::from_raw(33); @@ -1135,9 +1134,10 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { Errno::result(res).map(|_| ()) } -/// Calculate the supplementary group access list. Gets the group IDs of all -/// groups that `user` is a member of. The additional group `group` is also -/// added to the list. +/// Calculate the supplementary group access list. +/// +/// Gets the group IDs of all groups that `user` is a member of. The additional +/// group `group` is also added to the list. /// /// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html) /// @@ -1173,6 +1173,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { groups.as_mut_ptr() as *mut getgrouplist_group_t, &mut ngroups) }; + // BSD systems only return 0 or -1, Linux returns ngroups on success. if ret >= 0 { unsafe { groups.set_len(ngroups as usize) }; @@ -1199,9 +1200,11 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { } } -/// Initialize the supplementary group access list. Sets the supplementary -/// group IDs for the calling process using all groups that `user` is a member -/// of. The additional group `group` is also added to the list. +/// Initialize the supplementary group access list. +/// +/// Sets the supplementary group IDs for the calling process using all groups +/// that `user` is a member of. The additional group `group` is also added to +/// the list. /// /// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html) /// @@ -1211,7 +1214,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { /// another user. For example, given the user `www-data`, we could look up the /// UID and GID for the user in the system's password database (usually found /// in `/etc/passwd`). If the `www-data` user's UID and GID were `33` and `33`, -/// respectively, one could switch user as follows: +/// respectively, one could switch the user as follows: /// ``` /// let user = CString::new("www-data").unwrap(); /// let uid = Uid::from_raw(33); diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 2bb432d0d1..672cbb9c69 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -4,7 +4,7 @@ use nix::unistd::*; use nix::unistd::ForkResult::*; use nix::sys::wait::*; use nix::sys::stat; -use std::{env, iter}; +use std::{self, env, iter}; use std::ffi::CString; use std::fs::File; use std::io::Write; @@ -128,11 +128,10 @@ mod linux_android { fn test_setgroups() { // Skip this test when not run as root as `setgroups()` requires root. if !Uid::current().is_root() { - use std::io; - let stderr = io::stderr(); + let stderr = std::io::stderr(); let mut handle = stderr.lock(); writeln!(handle, "test_setgroups requires root privileges. Skipping test.").unwrap(); - return + return; } #[allow(unused_variables)] @@ -159,11 +158,10 @@ fn test_initgroups() { // Skip this test when not run as root as `initgroups()` and `setgroups()` // require root. if !Uid::current().is_root() { - use std::io; - let stderr = io::stderr(); + let stderr = std::io::stderr(); let mut handle = stderr.lock(); writeln!(handle, "test_initgroups requires root privileges. Skipping test.").unwrap(); - return + return; } #[allow(unused_variables)] @@ -175,7 +173,8 @@ fn test_initgroups() { // It doesn't matter if the root user is not called "root" or if a user // called "root" doesn't exist. We are just checking that the extra, // made-up group, `123`, is set. - // FIXME: This only tests half of initgroups' functionality. + // FIXME: Test the other half of initgroups' functionality: whether the + // groups that the user belongs to are also set. let user = CString::new("root").unwrap(); let group = Gid::from_raw(123); let group_list = getgrouplist(&user, group).unwrap(); From d4e7761923b71aac253675814ae6da723d74b59c Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 12 Nov 2017 11:48:26 +0200 Subject: [PATCH 12/13] unistd: groups: Disable functions on Apple platforms --- src/unistd.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/unistd.rs b/src/unistd.rs index f596b75178..2b2f3751b3 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1048,13 +1048,12 @@ pub fn setgid(gid: Gid) -> Result<()> { /// Get the list of supplementary group IDs of the calling process. /// -/// *Note:* On macOS, `getgroups()` behavior differs somewhat from other Unix -/// platforms. It returns the current group access list for the user associated -/// with the effective user id of the process; the group access list may change -/// over the lifetime of the process, and it is not affected by calls to -/// `setgroups()`. -/// /// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html) +/// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, checking group membership should be achieved via communication +/// with the `opendirectoryd` service. +#[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn getgroups() -> Result<Vec<Gid>> { // First get the number of groups so we can size our Vec let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) }; @@ -1091,13 +1090,12 @@ pub fn getgroups() -> Result<Vec<Gid>> { /// Set the list of supplementary group IDs for the calling process. /// -/// *Note:* On macOS, `getgroups()` may not return the same group list set by -/// calling `setgroups()`. The use of `setgroups()` on macOS is 'highly -/// discouraged' by Apple. Developers are referred to the `opendirectoryd` -/// daemon and its set of APIs. -/// /// [Further reading](http://man7.org/linux/man-pages/man2/getgroups.2.html) /// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, group membership management should be achieved via communication +/// with the `opendirectoryd` service. +/// /// # Examples /// /// `setgroups` can be used when dropping privileges from the root user to a @@ -1111,6 +1109,7 @@ pub fn getgroups() -> Result<Vec<Gid>> { /// setgid(gid)?; /// setuid(uid)?; /// ``` +#[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn setgroups(groups: &[Gid]) -> Result<()> { cfg_if! { if #[cfg(any(target_os = "dragonfly", @@ -1141,6 +1140,10 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { /// /// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html) /// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, checking group membership should be achieved via communication +/// with the `opendirectoryd` service. +/// /// # Errors /// /// Although the `getgrouplist()` call does not return any specific @@ -1150,6 +1153,7 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { /// and `setgroups()`. Additionally, while some implementations will return a /// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation /// will only ever return the complete list or else an error. +#[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { Ok(Some(n)) => n as c_int, @@ -1208,6 +1212,10 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { /// /// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html) /// +/// **Note:** This function is not available for Apple platforms. On those +/// platforms, group membership management should be achieved via communication +/// with the `opendirectoryd` service. +/// /// # Examples /// /// `initgroups` can be used when dropping privileges from the root user to @@ -1223,6 +1231,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> { /// setgid(gid)?; /// setuid(uid)?; /// ``` +#[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn initgroups(user: &CStr, group: Gid) -> Result<()> { cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { From 0c786dfe62dc43b7a463d00fd9c37475ee5a1ce3 Mon Sep 17 00:00:00 2001 From: Jamie Hewland <jhewland@gmail.com> Date: Sun, 12 Nov 2017 12:10:41 +0200 Subject: [PATCH 13/13] unistd: Rework import of std::ptr --- src/unistd.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/unistd.rs b/src/unistd.rs index 2b2f3751b3..dc0d5d077c 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -6,7 +6,7 @@ use fcntl::{fcntl, OFlag, O_CLOEXEC, FD_CLOEXEC}; use fcntl::FcntlArg::F_SETFD; use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t}; -use std::{self, fmt, mem}; +use std::{fmt, mem, ptr}; use std::ffi::{CString, CStr, OsString, OsStr}; use std::os::unix::ffi::{OsStringExt, OsStrExt}; use std::os::unix::io::RawFd; @@ -553,9 +553,6 @@ pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<Uid>, group: Option<Gi } fn to_exec_array(args: &[CString]) -> Vec<*const c_char> { - use std::ptr; - use libc::c_char; - let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect(); args_p.push(ptr::null()); args_p @@ -1056,7 +1053,7 @@ pub fn setgid(gid: Gid) -> Result<()> { #[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn getgroups() -> Result<Vec<Gid>> { // First get the number of groups so we can size our Vec - let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) }; + let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; // Now actually get the groups. We try multiple times in case the number of // groups has changed since the first call to getgroups() and the buffer is