Your program runs setuid or setgid (see Section 1.3.3 for definitions), thus providing your program with extra privileges when it is executed. After the work requiring the extra privileges is done, those privileges need to be dropped so that an attacker cannot leverage your program during an attack that results in privilege elevation.
If your program must run setuid or setgid, make sure to use the privileges properly so that an attacker cannot exploit other possible vulnerabilities in your program and gain these additional privileges. You should perform whatever work requires the additional privileges as early in the program as possible, and you should drop the extra privileges immediately after that work is done.
While many programmers may be aware of the need to drop privileges, many more are not. Worse, those who do know to drop privileges rarely know how to do so properly and securely. Dropping privileges is tricky business because the semantics of the system calls to manipulate IDs for setuid/setgid vary from one Unix variant to another—sometimes only slightly, but often just enough to make the code that works on one system fail on another.
On modern Unix systems, the extra privileges resulting from using the setuid or setgid bits on an executable can be dropped either temporarily or permanently. It is best if your program can do what it needs to with elevated privileges, then drop those privileges permanently, but that’s not always possible. If you must be able to restore the extra privileges, you will need to be especially careful in your program to do everything possible to prevent an attacker from being able to take control of those privileges. We strongly advise against dropping privileges only temporarily. You should do everything possible to design your program such that it can drop privileges permanently as quickly as possible. We do recognize that it’s not always possible to do—the Unix passwd command is a perfect example: the last thing it does is use its extra privileges to write the new password to the password file, and it cannot do it any sooner.
Before we can discuss how to drop privileges either temporarily or permanently, it’s useful to have at least a basic understanding of how setuid, setgid, and the privilege model in general work on Unix systems. Because of space constraints and the complexity of it all, we’re not able to delve very deeply into the inner workings here. If you are interested in a more detailed discussion, we recommend the paper “Setuid Demystified” by Hao Chen, David Wagner, and Drew Dean, which was presented at the 11th USENIX Security Symposium in 2002 and is available at http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf.
On all Unix systems, each process has an effective user ID, a real user ID, an effective group ID, and a real group ID. In addition, each process on most modern Unix systems also has a saved user ID and a saved group ID.[3] All of the Unix variants that we cover in this book have saved user IDs, so our discussion assumes that the sets of user and group IDs each have an effective ID, a real ID, and a saved ID.
Normally when a process is executed, the effective, real, and saved user and group IDs are all set to the real user and group ID of the process’s parent, respectively. However, when the setuid bit is set on an executable, the effective and saved user IDs are set to the user ID that owns the file. Likewise, when the setgid bit is set on an executable, the effective and saved group IDs are set to the group ID that owns the file.
For the most part, all privilege checks performed by the operating system are done using the effective user or effective group ID. The primary deviations from this rule are some of the system calls used to manipulate a process’s user and group IDs. In general, the effective user or group ID for a process may be changed as long as the new ID is the same as either the real or the saved ID.
Taking all this into account, permanently dropping privileges involves ensuring that the effective, real, and saved IDs are all the same value. Temporarily dropping privileges requires that the effective and real IDs are the same value, and that the saved ID is unchanged so that the effective ID can later be restored to the higher privilege. These rules apply to both group and user IDs.
One more issue needs to be addressed with regard to dropping
privileges. In addition to the effective, real, and saved group IDs
of a process, a process also has ancillary groups. Ancillary groups
are inherited by a process from its parent process, and they can only
be altered by a process with superuser privileges. Therefore, if a
process with superuser privileges is dropping these privileges, it
must also be sure to drop any ancillary groups it may have. This is
achieved by calling setgroups(
)
with a single group, which is the real group ID for the process.
Because the setgroups( )
system call is guarded by
requiring the effective user ID of the process to be that of the
superuser, it must be done prior to dropping root privileges.
Ancillary groups should be dropped regardless of whether privileges
are being dropped permanently or temporarily. In the case of a
temporary privilege drop, the process can restore the ancillary
groups if necessary when elevated privileges are restored.
The first of two functions, spc_drop_privileges(
)
drops any extra group or user privileges
either permanently or temporarily, depending on the value of its only
argument. If a nonzero value is passed, privileges will be dropped
permanently; otherwise, the privilege drop is temporary. The second
function, spc_restore_privileges(
)
, restores privileges to what they were at the
last call to spc_drop_privileges( )
. If either
function encounters any problems in attempting to perform its
respective task, abort(
)
is
called, terminating the process immediately. If any manipulation of
privileges cannot complete successfully, it’s safest
to assume that the process is in an unknown state, and you should not
allow it to continue.
Recalling our earlier discussion regarding subtle differences in the
semantics for changing a process’s group and user
IDs, you’ll notice that
spc_drop_privileges( )
is littered with
preprocessor conditionals that test for the platform on which the
code is being compiled. For the BSD-derived platforms (Darwin,
FreeBSD, NetBSD, and OpenBSD), dropping privileges involves a simple
call to setegid( )
or seteuid(
)
, followed by a call to either setgid(
)
or setuid( )
if privileges are being
permanently dropped. The setgid( )
and
setuid( )
system calls adjust the
process’s saved group and user IDs, respectively, as
well as the real group or user ID.
On Linux and Solaris, the setgid( )
and
setuid( )
system calls do not alter the
process’s saved group and user IDs in all cases. (In
particular, if the effective ID is not the superuser, the saved ID is
not altered; otherwise, it is.). That means that these calls
can’t reliably be used to permanently drop
privileges. Instead, setregid(
)
and
setreuid( )
are used, which actually simplifies the
process except that these two system calls have different semantics
on the BSD-derived platforms.
Warning
As discussed above, always drop group privileges before dropping user privileges; otherwise, group privileges may not be able to be fully dropped.
#include <sys/param.h> #include <sys/types.h> #include <stdlib.h> #include <unistd.h> static int orig_ngroups = -1; static gid_t orig_gid = -1; static uid_t orig_uid = -1; static gid_t orig_groups[NGROUPS_MAX]; void spc_drop_privileges(int permanent) { gid_t newgid = getgid( ), oldgid = getegid( ); uid_t newuid = getuid( ), olduid = geteuid( ); if (!permanent) { /* Save information about the privileges that are being dropped so that they * can be restored later. */ orig_gid = oldgid; orig_uid = olduid; orig_ngroups = getgroups(NGROUPS_MAX, orig_groups); } /* If root privileges are to be dropped, be sure to pare down the ancillary * groups for the process before doing anything else because the setgroups( ) * system call requires root privileges. Drop ancillary groups regardless of * whether privileges are being dropped temporarily or permanently. */ if (!olduid) setgroups(1, &newgid); if (newgid != oldgid) { #if !defined(linux) setegid(newgid); if (permanent && setgid(newgid) = = -1) abort( ); #else if (setregid((permanent ? newgid : -1), newgid) = = -1) abort( ); #endif } if (newuid != olduid) { #if !defined(linux) seteuid(newuid); if (permanent && setuid(newuid) = = -1) abort( ); #else if (setreuid((permanent ? newuid : -1), newuid) = = -1) abort( ); #endif } /* verify that the changes were successful */ if (permanent) { if (newgid != oldgid && (setegid(oldgid) != -1 || getegid( ) != newgid)) abort( ); if (newuid != olduid && (seteuid(olduid) != -1 || geteuid( ) != newuid)) abort( ); } else { if (newgid != oldgid && getegid( ) != newgid) abort( ); if (newuid != olduid && geteuid( ) != newuid) abort( ); } } void spc_restore_privileges(void) { if (geteuid( ) != orig_uid) if (seteuid(orig_uid) = = -1 || geteuid( ) != orig_uid) abort( ); if (getegid( ) != orig_gid) if (setegid(orig_gid) = = -1 || getegid( ) != orig_gid) abort( ); if (!orig_uid) setgroups(orig_ngroups, orig_groups); }
“Setuid Demystified” by Hao Chen, David Wagner, and Drew Dean: http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf
[3] Linux further complicates the already complex privilege model by adding a filesystem user ID and a filesystem group ID, as well as POSIX capabilities. At this time, most systems do not actually make use of POSIX capabilities, and the filesystem IDs are primarily maintained automatically by the kernel. If the filesystem IDs are not explicitly modified by a process, they can be safely ignored, and they will behave properly. We won’t discuss them any further here.
Get Secure Programming Cookbook for C and C++ now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.