Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux: std::process, insecure dropping of ancillary groups #88716

Closed
bnoordhuis opened this issue Sep 7, 2021 · 14 comments · Fixed by #121650
Closed

linux: std::process, insecure dropping of ancillary groups #88716

bnoordhuis opened this issue Sep 7, 2021 · 14 comments · Fixed by #121650
Assignees
Labels
A-process Area: `std::process` and `std::env` C-bug Category: This is a bug. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. O-linux Operating system: Linux O-unix Operating system: Unix-like T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@bnoordhuis
Copy link
Contributor

Commit 22ddcd1 made libstd drops ancillary groups when uid == 0:

if libc::getuid() == 0 && self.get_groups().is_none() {
cvt(libc::setgroups(0, ptr::null()))?;
}

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 CAP_SETGID capability.

Such processes can and should drop ancillary groups, otherwise child processes inherit permissions they otherwise wouldn't have.

Suggested change:

if self.get_groups().is_none() {
    let _ = libc::setgroups(0, ptr::null()); // or return unless EPERM
}
@bnoordhuis bnoordhuis added the C-bug Category: This is a bug. label Sep 7, 2021
@cuviper cuviper added O-linux Operating system: Linux O-unix Operating system: Unix-like labels Sep 8, 2021
@m-ou-se m-ou-se added I-libs-nominated Nominated for discussion during a libs team meeting. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Oct 27, 2021
@m-ou-se m-ou-se removed the I-libs-nominated Nominated for discussion during a libs team meeting. label Oct 27, 2021
@joshtriplett
Copy link
Member

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 CAP_SETGID.

(It seems worth noting that CAP_SETGID is generally root-equivalent, so this doesn't actually seem like a security issue of any kind.)

@cuviper
Copy link
Member

cuviper commented Nov 10, 2021

Whatever we do, we need to reconcile this with #90292 as it becomes documented.
I agree the suggested change makes sense though.

@bnoordhuis
Copy link
Contributor Author

It seems worth noting that CAP_SETGID is generally root-equivalent, so this doesn't actually seem like a security issue of any kind.

IMO, it is. Scenario: non-root, sudo-like program that:

  1. has CAP_SETGID + CAP_SETUID in order to switch users when spawning child processes, and
  2. is 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.

@joshtriplett
Copy link
Member

joshtriplett commented Nov 10, 2021

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.)

@bnoordhuis
Copy link
Contributor Author

Wouldn't it need to drop that capability though, before the exec?

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 AT_SECURE is sufficient. man 7 capabilities claims the following:

If ambient capabilities cause a process's permitted and effective capabilities to increase during an execve(2), this does not trigger the secure-execution mode described in ld.so(8).

It's set only when permitted is a superset of ambient.

prctl() with one of the PR_CAPxxx 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 what libuv does and a CERT C recommendation.

@joshtriplett
Copy link
Member

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.

@Amanieu
Copy link
Member

Amanieu commented Nov 18, 2021

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 EPERM from setgroups? If you have permissions to change UID then you should also have permissions to change groups. I would feel more comfortable if we documented set_uid as requiring both UID and GID changing permissions.

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Nov 18, 2021

Team member @Amanieu has proposed to merge this. The next step is review by the rest of the tagged team members:

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 this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Nov 18, 2021
@rfcbot rfcbot added the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Dec 4, 2021
@rfcbot
Copy link

rfcbot commented Dec 4, 2021

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Dec 4, 2021
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Dec 14, 2021
@rfcbot
Copy link

rfcbot commented Dec 14, 2021

The final comment period, with a disposition to merge, as per the review above, 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.

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Dec 16, 2021
@Amanieu
Copy link
Member

Amanieu commented Dec 18, 2021

@bnoordhuis We have accepted this change, would you be willing to write up a PR for it?

@Elliot-Roberts
Copy link
Contributor

I could write a PR for this and maybe also bundle in a redo of #90292.

@bnoordhuis
Copy link
Contributor Author

@Elliot-Roberts Please go ahead. I'm not working on this at the moment.

@Elliot-Roberts
Copy link
Contributor

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.

@the8472 the8472 added A-process Area: `std::process` and `std::env` A-security Area: Security (example: address space layout randomization). and removed A-security Area: Security (example: address space layout randomization). labels Feb 8, 2022
@bors bors closed this as completed in eaa8daf Mar 15, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Mar 15, 2024
Rollup merge of rust-lang#121650 - GrigorenkoPV:cap_setgid, r=Amanieu

change std::process to drop supplementary groups based on CAP_SETGID

A trivial rebase of rust-lang#95982

Should fix rust-lang#39186 (from what I can tell)

Original description:

> Fixes rust-lang#88716
>
> * Before this change, when a process was given a uid via `std::os::unix::process::CommandExt.uid`, there would be a `setgroups` call (when the process runs) to clear supplementary groups for the child **if the parent was root** (to remove potentially unwanted permissions).
> * After this change, supplementary groups are cleared if we have permission to do so, that is, if we have the CAP_SETGID capability.
>
> This new behavior was agreed upon in rust-lang#88716 but there was a bit of uncertainty from `@Amanieu` here: [rust-lang#88716 (comment)](rust-lang#88716 (comment))
>
> > I agree with this change, but is it really necessary to ignore an EPERM from setgroups? If you have permissions to change UID then you should also have permissions to change groups. I would feel more comfortable if we documented set_uid as requiring both UID and GID changing permissions.
>
> The way I've currently written it, we ignore an EPERM as that's what rust-lang#88716 originally suggested. I'm not at all an expert in any of this so I'd appreciate feedback on whether that was the right way to go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-process Area: `std::process` and `std::env` C-bug Category: This is a bug. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. O-linux Operating system: Linux O-unix Operating system: Unix-like T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
9 participants