Page MenuHomeFreeBSD

mdo(1): Add support and shortcuts for fully specifying users and groups
ClosedPublic

Authored by olce on Fri, Sep 19, 12:43 PM.
Tags
None
Referenced Files
Unknown Object (File)
Sun, Oct 12, 10:29 AM
Unknown Object (File)
Fri, Oct 10, 4:05 PM
Unknown Object (File)
Fri, Oct 10, 10:08 AM
Unknown Object (File)
Fri, Oct 10, 10:08 AM
Unknown Object (File)
Fri, Oct 10, 10:08 AM
Unknown Object (File)
Fri, Oct 10, 10:08 AM
Unknown Object (File)
Fri, Oct 10, 3:40 AM
Unknown Object (File)
Tue, Oct 7, 2:03 PM
Subscribers

Details

Summary

While preserving compatibility ('root' implied if not user is specified,
option '-i' not setting groups), introduce options to control finely
which user and group IDs are set in the launched process.

To minimize the risks of user error, mdo(1) by default enforces that all
user and group IDs are specified, either with explicit values from the
command-line or, if a known user name is passed with '-u', from the
corresponding content of the password and group databases. The other
main type of use cases is to start from from the current process'
credentials, only amending part of them. It is now also possible to
blend both approaches, where some parts must be specified and the others
can just be amended or left as is.

Options:

  • As before:

-u: Specifies a user name or ID to change all user IDs to. If a known

name is passed, also automatically sets all groups as per the
password and group databases.

-i: Starts from the current groups, instead of having to specify them

by using '-u' with a known user name or explicitly.
  • New:

-k: Starts from the current users (incompatible with '-u'). Implies '-i'.
-g: Sets/overrides the primary group IDs with the passed group name or ID.
-G: Change the supplementary groups set to exactly the passed one, as a list

of comma-separated names or IDs.

-s: Modify the supplementary groups set according to the list of

comma-separated directives from the following:
- @: Empties the set.  Must be the first directive.  Incompatible with '-G'.
- +<group>: Add a group to the set.
- -<group>: Remove a group from the set.  Takes precedence over -<group>.

--euid: Overrides the effective user ID.
--ruid: Overrides the real user ID.
--svuid: Overrides the saved user ID.
--egid: Overrides the effective group ID.
--rgid: Overrides the real group ID.
--svgid: Overrides the saved group ID.

Option '-k' was introduced to still allow to omit '-u' to switch to
'root'. In order to avoid user confusion, if one but not all of the
user overrides are specified, then mdo(1) enforces that either '-u' or
'-k' is specified (not defaulting to '-u root' in this case).

Some base supplementary groups set is needed when '-s' is used without
directive '@'. It can be an explicit one specified with '-G',
effectively meaning that '-G' is processed before '-s'. Else, it is
determined from the password/group database (see initgroups(3)) if '-u'
with a user name was passed, or is simply the current set if '-i' (or
'-k') was specified. Other cases require specifying the full set (using
'-G' or '-s' with '@'), and will fail otherwise.

Note for MFC to stable/14: As initgroups() has its old behavior,
consistently with it, remove the effective GID from being passed also as
a supplementary group.

Sponsored by: The FreeBSD Foundation
Sponsored by: Google LLC (GSoC 2025)
Co-authored-by: Kushagra Srivastava <kushagra1403@gmail.com>

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

olce requested review of this revision.Fri, Sep 19, 12:43 PM

A man page is missing, but with the commit message, the inline help text (and if necessary the code comments), I hope you'll be able to figure out how this works (else it may be a sign that something is wrong/too complicated).

Quite a bunch of combinations were tested by hand, except for '-s' with '-' directives.

My GSoC student, Kushagra, started working on unit tests, and I'll base tests (currently missing) on these.

Goal is to have this in 15 if possible before the releng/15.0 branch. The rest can be added during the convergence phase.

I was already thinking about some refinement: Automatically setting the saved UID (GID) to the chosen effective UID (GID) (to mimick what setuid() and setgid() and co. do), but then I'd probably have to introduce two new options to deactivate that behavior. Although it makes sense, that would be also one more non-straightforward behavior, which we may prefer to avoid when overriding specific users or primary groups. For UIDs, such a refinement would make sense only on '-k', and if '--euid' is specified and '--svuid' is not; for GIDs, only on '-i' (and thus also '-k') and not '-g', and if '--egid' is and '--svgid' is not.

olce edited the summary of this revision. (Show Details)
  • Fix an error message in the "nor '-u' nor '-k' and user overrides" check.
  • Simplify primary groups code (no function change intended).
  • Fix '-s' (remove_groups() was buggy).

I think it is really better to impose '-k' or an explicit '-u' as soon as some
other user/group specification appears on the command line (so, extend the check
mentioned above), else users won't think that all groups not overridden come
from the passwd/group database for the "root" user.

I am puzzled here, yes I like this, as it makes mdo(1) way more versatile, but I am also wondering if we are not here a little bit over thinking this, I get the completeness of it, but I fail at seeing a practical use case for this, and this adds lots of complexity to the code. Caan you share example on how you expect this to be used?

Before all of this, the 2 only evolution I had in mind was to be able to set a default user different from "root" via sysctl and adding logging/auditability.

Other then this, I don't see anything to argue with in the code, so you can consider the technical review ok from me.

This comment was removed by olce.

I am puzzled here, yes I like this, as it makes mdo(1) way more versatile, but I am also wondering if we are not here a little bit over thinking this, I get the completeness of it, but I fail at seeing a practical use case for this, and this adds lots of complexity to the code. Caan you share example on how you expect this to be used?

Yes, that was in part my dilemma, as this is undoubtedly more complexity (even if that is not rocket science either), but I see a bunch of practical use cases that become possible and in a very simple manner for operators/users.

Let's just exclude the --*uid and --*gid from the discussion, as they do not think they add much complexity, and are just indeed overrides for completeness (that are actually quite useful for testing, and could even be used in our test cases about DAC).

First use case: Do not give by default some capability to some user, because it is too strong/dangerous, but allow him to explicitly acquire it when necessary. Example: Allow someone to enter the wheel group or the operator group, but don't place them in from the start because that's considered too dangerous:
mdo -k -s +operator <command> (if already that user)
mdo -u <user> -s +operator <command> (if having to switch to that user)

Second use case: setgid-executable emulation, without having the set-group-ID bit set (well, not exactly, as that changes also the real group ID; perfect emulation is still possible by adding --rgid to the picture)
mdo -k -g <new_group> <command>
(setuid-executable emulation was already possible before with:
mdo -u <user> -i)

Third use case: Remove a supplementary group that is used as a tag to deny access with ugidfw(8).
mdo -k -s -restricted <command>

I don't know if these are very compelling use cases, but personally I would use them (already using some).

The advantage of completeness is also that people can start doing some creative things with permissions, and actually test those easily.

A large part of the complexity also comes from the fact that mdo(1) can start either with the current credentials, or from those for a specific user in the password/group databases, and I tend to think that's too good to get rid of.

Before all of this, the 2 only evolution I had in mind was to be able to set a default user different from "root" via sysctl and adding logging/auditability.

Yes, I'd like to add logging too. Actually, although probably not the logging you are talking about, I think the next immediate thing for mdo(1) is to add an option to display the final credentials the operator actually required (so it can be sure of what he is doing).

Having a different default user configured by sysctl avoids having a configuration file, which is good to avoid the confused deputy problems with chroot() (I've always found slightly strange that some config maintained by the kernel does only apply to userland, but I think that makes sense here).

Could you elaborate on what you had in mind here? I consider the default user feature to be dangerous, and have been planning to limit its applicability. For example, currently, mdo(1) will reject using any of --ruid, --euid and --svuid if -u is not explicit (and -k is not specified). I was planning to be even more restrictive, by similarly rejecting -g, -G and -s if neither -u nor -k are present, on the ground that the user writing this probably meant to use -k but forgot to specify it. It seems to me that things would be safer if we completely get rid of the implicit user special case (so operators have to specify -u, else -k is implicit). Is the default user case really compelling?

Other then this, I don't see anything to argue with in the code, so you can consider the technical review ok from me.

Ok, thanks!

The main goal of mdo(1) I had in mind when I implemented is cases where on some system operators have to run commands as another user, like I connect ssh me@machine, then I need to do some things as nextcloud user. so basically every people in the "admin" group can run a command as nextcloud.
being able to have on this machine nextcloud the default user mdo switches to make it more user friendly, but yes this is sugar, not a strong requirement.

  • An explicit -u is now required (on no -k) as soon as any override is present.
  • Supplementary groups: Fix guard on calling getgroups().
  • Make the getopt_long() values for long options symbolic (no functional change).

(snip)
For example, currently, mdo(1) will reject using any of --ruid, --euid and --svuid if -u is not explicit (and -k is not specified). I was planning to be even more restrictive, by similarly rejecting -g, -G and -s if neither -u nor -k are present, on the ground that the user writing this probably meant to use -k but forgot to specify it. It seems to me that things would be safer if we completely get rid of the implicit user special case (so operators have to specify -u, else -k is implicit).
(snip)

(snip) but yes this is sugar, not a strong requirement.

I've left the root default if neither -u nor -k are specified, but it is now even more restricted: There must be no overrides on the command-line. So basically, mdo has to be run without options or -i (as before), the presence of any other option will trigger an error saying to be more explicit. I did the exercise of completely removing the default case, and it does barely bring any simplification.

This revision was not accepted when it landed; it landed in state Needs Review.Mon, Sep 29, 6:20 PM
This revision was automatically updated to reflect the committed changes.