Change:
Commit made libstd drops ancillary groups when uid == 0: Before that it unconditionally dropped group membership. The new logic is wrong on Linux: it doesn't account for processes whose uid != 0 but have the CAPSETGID capability. Such processes can and should drop ancillary groups, otherwise child processes inherit permissions they otherwise wouldn't have. Suggested change:
This seems like a potentially reasonable approach. It'd be a behavior change, but technically the previous change was a behavior change in the case of CAPSETGID. (It seems worth noting that CAPSETGID is generally root-equivalent, so this doesn't actually seem like a security issue of any kind.)
Whatever we do, we need to reconcile this with as it becomes documented. I agree the suggested change makes sense though.
IMO, it is. Scenario: non-root, sudo-like program that: CAPSETGID + CAP_SETUID in order to switch users when spawning child processes, and a member of one or more supplementary groups in order to read or write sensitive files Not dropping group membership is a catastrophic security breach in that setup.
Wouldn't it need to drop that capability though, before the exec? In any case, this does still need fixing if possible. Semantically, though, I don't think we should unconditionally drop supplementary groups; that would be a bug as well. Consider an ordinary user, who has a pile of supplementary groups for permission to various devices (e.g. audio). A Rust program runs a child process, and that process should still have those supplementary groups. One possible fix: getauxval for AT_SECURE, which is the standard way of detecting "this process gained special privileges when run". That covers setuid/setgid/setcaps. (It doesn't cover programs that start out as root and drop some-but-not-all privileges, but such programs need to take care when running other programs and should already know to drop their remaining privileges in such cases.)
Not necessarily, it depends on the capability set (threads have several) the capability is part of. Some persist across execve() calls, some don't. (I wanted to write up a short explanation of the algorithm but I can't hope to do it justice in a few words, it's complicated.) I'm pessimistic checking ATSECURE is sufficient. claims the following: It's set only when . prctl() with one of the PRCAPxxx flags might work but that gets complicated fast and I'd argue dropping group membership unless the user indicates otherwise is traditional and expected UNIX behavior. It's and a .
My concern is that the user should not have to retrieve and re-set the supplementary groups just to have them not cleared. Also, I'd like to avoid adding an unexpected source of additional complexity; I think inheriting will be the common case.
Note that clearing the groups only happens if the user asked to change to a different UID. I think it makes sense in this case to always drop the groups since they are tied to the previous UID and most likely aren't supposed to be kept for the new UID. If you really want to preserve the groups while changing UID (unlikely anyone would ever want that...) then you can explicitly pass the list of groups of the current process. I agree with this change, but is it really necessary to ignore an from ? If you have permissions to change UID then you should also have permissions to change groups. I would feel more comfortable if we documented as requiring both UID and GID changing permissions. fcp merge
Team member has proposed to merge this. The next step is review by the rest of the tagged team members: [x] [x] [ ] [ ] [x] No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See for info about what commands tagged team members can give me.
:bell: This is now entering its final comment period, as per the . :bell:
The final comment period, with a disposition to merge, as per the , is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon.
We have accepted this change, would you be willing to write up a PR for it?
I could write a PR for this and maybe also bundle in a redo of .
Please go ahead. I'm not working on this at the moment.
Sorry scratch that I'm having trouble with my dev environment and don't have the time to fix it right now. I might pick this back up but I'm no longer claiming it.

Discussion:
/// Sets the child process's user ID. This translates to a /// `setuid` call in the child process. Failure in the `setuid` /// call will cause the spawn to fail. <ins> /// /// # Notes /// /// This will also trigger a call to `setgroups(0, NULL)` in the child /// process if no groups have been specified. /// This removes supplementary groups that might have given the child /// unwanted permissions. </ins> #[stable(feature = "rust1", since = "1.0.0")] fn uid(&mut self, id: UserId) -> &mut process::Command;

if let Some(u) = self.get_uid() { // When dropping privileges from root, the `setgroups` call // will remove any extraneous groups. We only drop groups <del> // if the current uid is 0 and we weren't given an explicit </del> <ins> // if we have CAP_SETGID and we weren't given an explicit </ins> // set of groups. If we don't call this, then even though our // uid has dropped, we may still have groups that enable us to // do super-user things. //FIXME: Redox kernel does not support setgroups yet #[cfg(not(target_os = "redox"))] <del> if libc::getuid() == 0 && self.get_groups().is_none() { cvt(libc::setgroups(0, crate::ptr::null()))?; </del> <ins> if self.get_groups().is_none() { let res = cvt(libc::setgroups(0, crate::ptr::null())); if let Err(e) = res { // Here we ignore the case of not having CAP_SETGID. // An alternative would be to require CAP_SETGID (in // addition to CAP_SETUID) for setting the UID. if e.raw_os_error() != Some(libc::EPERM) { return Err(e.into()); } } </ins> } cvt(libc::setuid(u as uid_t))?; }