Firstly, the su program isn't standardized by POSIX, so everything we say about it is system-dependent.
On the systems where that trick is used, su is documented as having that feature.
When su is executed by root (so that its setuid bit is moot), it knows this, and allows a credentials change to a specified account without prompting for a password. This is a feature and the example cron jobs are using it correctly to obtain their desired effect of impersonating that user.
su is not "dropping privileges"; it's impersonating a specific user, in a command dispatched from a root cron job or other script.
Thus all that might be wrong is the rhetoric used to describe the scripts, not what the scripts are actually doing, and only if we are nitpicky.
"Dropping privileges" has a strict meaning, referring to the situation that some user bob invoked a setuid root executable, which then disrobes itself of the root privilege, changing its effective-root uid to just bob again.
When root changes to another account that is unrelated to the real user ID, nevertheless, that situation fits the description of "dropping privileges" because that account is strictly less privileged than root.
> The right way ... Create a small wrapper binary with C
That's a silly nonstarter.
> The new implementation called fork() and only dropped privileges in the child, retaining a privileged account parent process that could call the PAM "user session" cleanup function once the child exited.
Privileged parents waiting for unprivileged children to terminate is an established pattern in Unix-like systems.
E.g. PID 1 (init or systemd) is privileged and handles the termination of everything that has no parent.
The author is not connecting the dots here: reporting some actual privilege escalation bug in the use of "su bob command ...".
Interactively, when you are root, you can do this:
root # su bob
/home/bob$ exit
root #
This exit back to root is not a security hole, and it is not relevant to the scripted scenario at all. Bob has not actually gained root access. The order of messages on the TTY isn't what determines the security context semantics.Wait... Doing just "su bob" is vulnerable to a known decades old exploit. You should do either "exec su bob" or "su -P bob" or you just gave bob root rights on every Linux kernel older than 6.2 (and I think on 6.2 and more recent the default is still insecure?).
It's explained here with a simple PoC exploit (I tried the exploit and it works/worked) and it's been frontpage on HN in the past I think:
From su man page: "su is mostly designed for unprivileged users, the recommended solution for privileged users (e.g., scripts executed by root) is to use non-set-user-ID command runuser(1) that does not require authentication and provides separate PAM configuration. If the PAM session is not required at all then the recommended solution is to use command setpriv(1)."
Say you want to fix the problem, while continuing to support that dubious ioctl. What you need is the ability for the TTY driver to send a SIGKILL to every user bob process that has an open file descriptor to that TTY. After killing all those processes, then restore the original TTY settings before the su command was run using tcsetattr with TCSAFLUSH to throw away any pushed input.
Even then, there could be exploits that use weaknesses in the terminal emulator to reflect input.
Also, social engineering hacks. The same human that operated the root shell is su'd into bob, and malicious software installed by bob can fool that human. For instance, by simulating a system login prompt.
I use this C to knock down the privilege from root to the app client account before I execve() the target:
setfsgid(g);
setfsuid(u);
if(setgroups(NULL) || setresgid(g,g,g) || setresuid(u,u,u))
{
fprintf(stderr, "permissions error\n");
exit(1);
}
I hope that I did this correctly. Even if incorrect, it's worked since 2013.Interesting that the post's linked article does not even mention real, effective, and saved user/groupIDs.
Even worse, the article doesn't even mention TIOCSTI, which is another big reason, you should not use `su` this way (see e.g. https://www.errno.fr/TTYPushback.html).
The program run under su keeps the same TTY and can use the TIOCSTI ioctl to push data back into the TTY input buffer. When the program exits and the parent reads from stdin, it receives those bytes. If the parent happens to be a root shell from which you dropped privileges using su, you have a problem.
Only when one is in a terminal login session and using su to elevate / add privileges, does TIOCSTI become relevant. But no-one here is saying not to use su to add privileges.
People blame su, sudo, and (as the person at https://github.com/slicer69/doas/issues/110 did) doas for this feature of operating system kernels. The right thing to do with TIOCSTI it to just eliminate it from the kernel. OpenBSD did back in version 6.
Sadly, the argument from Alan Cox, Linux developer, when this was proposed years ago was that it should stay in Linux, and all of the programs like su, sudo, and doas should have even more things to do in the parent process that sticks around, namely pump I/O to and from a controlling pseudo-terminal that su/sudo/doas sets up for the shell subprocess, breaking (as the maintainer of OpenDoas pointed out) the long-standing notion that the child processes belong to the same terminal session and share things like a single getlogname() with the login shell.
6 years after https://www.openwall.com/lists/kernel-hardening/2017/05/10/3... and https://www.openwall.com/lists/oss-security/2017/06/03/9, there is no sign of anyone doing anything of the sort in any su or doas implementation. (It was briefly in one su implementation, but taken out in 2017 for being a "stupid hack": https://github.com/util-linux/util-linux/commit/23f75093264a...)
Fortunately, some six months ago Linux developers finally made TIOCSTI removable and the right course of action is available to those that want it: https://lore.kernel.org/lkml/20221228205726.rfevry7ud6gmttg5...
It's good to have some explanation as to why su is unsuitable for this.
I typically use su to run some program in its own user account to ensure:
- it has its own homedir and doesn't fill mine with garbage.
- there is some level of isolation from the rest of the system for security, a basic "jail". I'm not trying to protect against targeted attacks from extremely competent threat actors here, but rather trying to stick software into its own user account so it can only access that account, with isolation of the same level as if I had manually logged in to a secondary account.
Can such programs break out of their "jail" when using su?
Or is the author of the article just angry for other reasons?
1. Depending on the environment, process supervision can break if there is an unexpected process sitting between the supervisor and the supervisee. The article describes how the switch to PAM forced the introduction of an in-the-middle process responsible for closing the PAM session. The old su used exec, which avoids an in-the-middle process.
2. Su uses the shell of the target user, and will outright not work if the target user has a "nologin" shell.
The article goes on to mention correct workarounds for this like daemontools setuidguid, Runit chpst or just rolling your own exec wrapper.
This is not a behavior you want of a jail. Use chroot, LXC or your own setuid wrapper that removes the privilege.
A process cannot use its existing ancestral sessions to gain control of their account.
Seems like an unnecessary assumption.
“su is mostly designed for unprivileged users, the recommended solution for privileged users (e.g. scripts executed by root) is to use non-set-user-ID command runuser(1) that does not require authentication and provide separate PAM configuration. If the PAM session is not required at all then the recommend solution is to use command setpriv(1).”
This is kind of a subtle point that the article makes—the problem is that “su” is not good for dropping user privileges in general, but it does work if you are a user trying to spawn an interactive shell as another user.
Keep in mind that you are giving control of your terminal to that less-privileged account, and that this is a potential point of privilege escalation.