-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background:
Starting processes as different users is one of the most fundamental needed to do several useful things - especially so for automation, script execution and privilege de-escalation in services and sandboxing. This is even more so true on Linux, where you can achieve a lot with just running commands as various users, and where the overall boot process follows the fork, impersonate and exec pattern.
How it works on Windows:
The System.Diagnostics.Process.StartInfo instance member of type System.Diagnostics.ProcessStartInfo takes a username, domain and password for the target process's credentials. The credentials are then used to impersonate as the user (Windows itself implements this through credential providers) and create a new process (Windows succeeds it after local authority checks) in that context.
Problems with current workaround in Linux:
We can currently workaround this by launching another broker process (with set-uid and set-gid bits set) as root and then let it impersonate and exec the target executable into itself. This requires extreme care to implement safely, especially when you want password less authentication because you are already root or satisfy other constraints (see man 2/3 set(e)uid/set(e)gid), and will potentially redo what the "su" command does. Using su itself comes with nuances on how to pass the password string and wait for the process status and defeats the purpose of the language (C#).
Another way is to do a set(e)uid/set(e)gid before the fork() and exec() in Process.Start(). Doing this before the fork() comes with it's own nuances - you cannot use the libc call exposed by Mono.Unix directly (at least always) since it propagates it to all threads, potentially disrupting their I/O. You have to instead do a syscall() and undo it very reliably, and hope that threads don't switch in the meanwhile due to TPL, etc..
Overall there is no clean and reliable solution in Linux that's at par with Windows.
Proposal for Linux:
We need a similar mechanism in Linux, and it can work with the same API conventions as Windows for an explicit username-password based process invocation. However, the full credential specification (see man 7 credential) of a Linux process has more/different identities than an NT process token. At the minimum, we need to support the groupname (gid) of the new process along with the username (uid). So we'll need a ProcessStartInfo.GroupName that defaults to the current group, a ProcessStartInfo.UserName that defaults to the current user.
Implementation outline in Linux:
The presence or default of ProcessStartInfo.Password affects the behaviour:
If it is default (null), then we should try to invoke through a normal set(e)uid() + set(e)guid() and fail if we don't have permission appropriately. This works only for the superuser/same target credential as calling process.
If it is present, then we should do an equivalent of Windows - authenticate with various PAM modules and then launch the process. The "su" program explicitly does this.
The su process also takes care of various checks, order of impersonation, sanitizing the environment as per command line options, whether to invoke a new shell and create a terminal/logon session, etc.. See su.c and su-common.c in https://www.kernel.org/pub/linux/utils/util-linux/v2.31/ for source.
Finally, we can either do a fork(), set(e)gid(), set(e)uid() and exec*() in the managed process for the no password case or have a dotnet in-box broker for PAM authentication.