sufor dropping user privileges.
00 4 * * 1-6 /bin/su adm -c "/usr/lib/acct/runacct 2> /usr/adm/acct/nite/fd2log"
— UNIX System Administration by David Fielder and Bruce H. Hunter, published in 1986
Like M. Fielder's and M. Hunter's 1986 book, one can find many instances in books, on the World Wide Web, in tutorials, and even on manual pages, of abusing
su for dropping superuser privileges and running programs with ordinary user privileges — in
init.d scripts, and even from
They are all wrong.
su for this purpose.
It has never in fact been the function of
su, and for the past two decades people have been triggering errors with this abusage.
Over the past decade or so, this error has gradually become more and more blatant, going from a few ignorable warning messages in obscure log files to systems that fail to function, but it has in fact been there all of this time.
suadds privileges; it does not drop them.
su command authenticates an additional user (by default the superuser, but it can be anyone) in the same login session, and runs a command (by default a shell program) under the aegis of that user.
Thought of in this way, it should be fairly obvious that it is a mechanism for adding privileges, namely adding the privileges of the newly authenticated user to the current login session.
If, for example, one is user A at an interactive shell and one runs
su B then one has two shells available, one running under the aegis of user A and one running under the aegis of user B, and one has the privileges of both users at one's fingertips.
(With job control, switching between the two is a matter of the
The problem is that, thanks to hugely outdated documentation and outright computer folklore that still circulates, people think of
su in terms of raw internal mechanics, rather than in terms of its functional behaviour.
su is thought of as looking up accounts in the system password database, calling the
setuid() system calls to switch its process GIDs and UIDs, and then
execvp() to overlay itself with the target program.
M. Fielder and M. Hunter were not wrong in 1986.
But, aside from a few hold-out BSDs and BSD-derived systems (such as Android),
su hasn't worked this way for roughly two decades, now.
On the Unices such as Solaris and AIX, on Linux, and on the other BSDs such as FreeBSD and PC-BSD,
su has a completely different mechanism, and what used to hold true of it as a byproduct of its original mechanism no longer holds true now, and hasn't done so for a long while.
The 1980s truisms are long gone.
The reason for this is PAM (Pluggable Authentication Modules).
The su command is a PAM-enabled application with a service name of su. […] The authentication mechanisms used when PAM is enabled depend on the configuration for the
/etc/pam.confentries for the
sessionmodule types. In order for the
sucommand to exhibit a similar behavior through PAM authentication as seen in standard AIX®authentication, the
pam_allowrootmodule must be used as
sufficientand called before
pam_aixin both the
sucommand, IBM AIX Manuals since at least the turn of the 21st century
In the middle 1990s, with the advent of PAM, commands such as
login changed drastically, starting with the proprietary Unices and gradually filtering down to Linux and the BSDs.
In particular, PAM now controls the actual function of
All of the authentication behaviour, including the superuser being able to bypass the authentication step, is actually encoded in the series of PAM modules that is defined for the "su" application in PAM.
/etc/pam.d/su.conf file on a Debian system or the sample
/etc/pam.conf content given in the IBM AIX manual for
su, for examples.)
PAM itself operates in terms of what it calls "user sessions".
Programs such as
su call the PAM library function
pam_open_session() to "open" a login session and something must call the PAM library function
pam_close_session() to "close" it.
Furthermore, that something must call
pam_close_session() with the same security context — the same effective UID and effective GIDs — in which
pam_open_session() was originally called.
Session close needs the privileges to be able to reverse whatever was done by session open (writing accounting database records, dealing with per-user per-login session directories and services, and so forth).
The authenticated user xyrself won't necessarily have those privileges.
(It is basic UNIX security that unprivileged users don't have the rights to overwrite their own entries in the accounting database.)
In 2000 and 2001, the GNU world was catching up with the commercial Unices.
login programs underwent a series of tweaks as a result of PAM being used on Linux, because PAM broke the old implementation.
See Ben Gertzfield's 2000 patches for Debian
Around the same time, the same changes were made to the FreeBSD
Witness David J. MacKenzie's 2001 patches to FreeBSD
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.
(See contemporary accounts such as this one from Ben Collins.)
An initial implementation where the PAM "user session" was opened in the parent process and closed in the child was found to be faulty, with bugs such as Debian Bug #195048, Debian Bug #580434, and Debian Bug #599731 a.k.a. Gentoo Bug #246813 because the unprivileged child did not have the access rights to undo all of the session setup that opening the session did in the privileged parent.
For the next decade, bits and pieces kept popping up related to PAM and
In 2004, for example, there was a problem with the SELinux pluggable authentication module.
The Freedesktop.org people came late to the party in 2013, ten years after
su had been switched to PAM, when
pkexec had to be fixed to make the right PAM library calls for closing "user sessions" within the privileged parent process.
pkexec is much the same tool as
su, in that it authenticates an additional user into the current login session, except that it uses PolicyKit for authorization and PolicyKit style authentication agents instead of the system account database and a plain password prompt on the terminal.)
PAM thus, years ago, made
su unsuitable for use as a dæmon helper tool:
fork()s a subprocess rather than overlaying itself with the target program that it is to run.
Dæmon privilege-dropping helpers need to have the dæmon running in the same process, so that a dæmon supervisor can send control signals to it.
su now sets up and tears down user sessions, and all sorts of things associated with them by whatever pluggable authentication modules the system administrator has enabled, from kerberos keys and SELinux security contexts through per-user "runtime" directories to temporary network mounts.
Dæmons aren't associated with controlling TTYs or login sessions, and the mechanisms of user login sessions should not be affixed to them.
These are in addition to the non-PAM things that make it unsuitable, such as
su being sensitive to the target account's choice of interactive login shell, making it impossible to straightforwardly drop privileges with
su to a "nologin" unprivileged account — a commonly employed paradigm for dæmons that run under the aegises of dedicated user accounts that shouldn't ever be used for real user login.
Fortunately, the right way to drop privileges in a dæmon had already been around for some several years before the GNU tools changed, being only a couple of years younger than PAM itself, in fact.
Create a small wrapper binary with C (e.g.
/bin/setid) to perform basically the following (about 10-20 lines):
takes arguments and one option
first argument is always the userid to change the identity to
the rest of the arguments would be stored as a command.
the option, if present, could toggle whether the command is run through
initgroupsto the specified user
— Pekka Savola, Debian Bug #55219, 2001-10-27
Writing in 2001 in a Debian Bug discussing a problem where an abuse of
su to drop privileges had stopped working, M. Savola was not in fact describing a hypothetical future.
He was in fact, possibly unknowingly, describing a tool that had existed for several years at that point.
Indeed, by 2001 people were already busy cloning it into their own toolsets.
That tool was
setuidgid from Daniel J. Bernstein's daemontools, first released in 1997 (originally under the name
setuser in daemontools 0.51).
By the start of the 21st century it was already being duplicated into daemontools clones, and today we now have a range of such tools:
setuidgid, to which Uwe Ohse added
setuidgid from Adam Sampson's freedt
All of these tools were specifically designed for dropping privileges from a privileged process, in a simple, self-contained, easily security auditable, and one-way manner.
A couple of them explicitly say this on their manual pages.
All of these tools do the very thing that
su used to do, as an accident of implementation, long ago: take an account name and a command as program arguments; look up the account in the system account database; change the process' UID, GID, and supplementary groups; and chain load the command in the same process.
|The wrong way||The right way|
|Fielder and Hunter's book||
/bin/su adm -c "/usr/lib/acct/runacct 2> /usr/adm/acct/nite/fd2log"
setuidgid adm sh -c "exec /usr/lib/acct/runacct 2> /usr/adm/acct/nite/fd2log"
setuidgid adm redirfd -w 2 /usr/adm/acct/nite/fd2log /usr/lib/acct/runacct
setuidgid adm fdredir -w 2 /usr/adm/acct/nite/fd2log /usr/lib/acct/runacct
|Daniel Reed's wrapper for FreeCiv||
su -s /bin/bash nobody -c "XAUTHORITY=$TMP /usr/local/freeciv/bin/civclient $@" &
XAUTHORITY=$TMP setuidgid nobody /usr/local/bin/civclient $@ &