Your process runs with extra privileges granted by the setuid or setgid bits on the executable. Because it requires those privileges at various times throughout its lifetime, it can’t permanently drop the extra privileges. You would like to limit the risk of those extra privileges being compromised in the event of an attack.
When your program first initializes, create a Unix domain socket pair
using socketpair( )
, which will create two
endpoints of a connected unnamed socket. Fork the process using
fork( )
, drop the extra privileges in the child
process, and keep them in the parent process. Establish communication
between the parent and child processes.
Whenever the child process needs to perform an operation that
requires the extra privileges held by the parent process, defer the
operation to the parent.
The result is that the child performs the bulk of the program’s work. The parent retains the extra privileges and does nothing except communicate with the child and perform privileged operations on its behalf.
If the privileged process opens files on behalf of the unprivileged
process, you will need to use a Unix domain socket, as opposed to an
anonymous pipe or some other other interprocess communication
mechanism. The reason is that only Unix domain sockets provide a
means by which file descriptors can be exchanged between the
processes after the initial fork( )
.
In Recipe 1.3, we discussed setuid, setgid, and the importance of permanently dropping the extra privileges resulting from their use as quickly as possible to minimize the window of vulnerability to a privilege escalation attack. In many cases, the extra privileges are necessary for performing some initialization or other small amount of work, such as binding a socket to a privileged port. In other cases, however, the work requiring extra privileges cannot always be restricted to the beginning of the program, thus requiring that the extra privileges be dropped only temporarily so that they can later be restored when they’re needed. Unfortunately, this means that an attacker who compromises the program can also restore those privileges.
One way to solve this problem is to use privilege separation . When privilege separation is employed, one process is solely responsible for performing all privileged operations, and it does absolutely nothing else. A second process is responsible for performing the remainder of the program’s work, which does not require any extra privileges. As illustrated in Figure 1-1, a bidirectional communications channel exists between the two processes to allow the unprivileged process to send requests to the privileged process and to receive the results.
Normally, the two processes are closely related. Usually
they’re the same program split during initialization
into two separate processes using fork( )
. The
original process retains its privileges and enters a loop waiting to
service requests from the child process. The child process starts by
permanently dropping the extra privileges inherited from the parent
process and continues normally, sending requests to the parent when
it needs privileged operations to be performed.
By separating the process into privileged and unprivileged pieces, the risk of a privilege escalation attack is significantly reduced. The risk is further reduced by the parent process refusing to perform any operations that it knows the child does not need. For example, if the program never needs to delete any files, the privileged process should refuse to service any requests to delete files. Because the unprivileged child process undertakes most of the program’s functionality, it stands the greatest risk of compromise by an attacker, but because it has no extra privileges of its own, an attacker does not stand to gain much from the compromise.
NAI Labs has released a library that implements privilege separation on Unix with an easy-to-use API. This library, called privman , can be obtained from http://opensource.nailabs.com/privman/. As of this writing, the library is still in an alpha state and the API is subject to change, but it is quite usable, and it provides a good generic framework from which to work.
A program using privman should include the
privman.h header file and link to the
privman library. As part of the
program’s initialization, call the
privman API function priv_init(
)
,
which requires a single argument specifying the name of the program.
The program’s name is used for log entries to
syslog (see Recipe 13.11 for a discussion of
logging), as well as for the configuration file to use. The
priv_init( )
function should be called by the
program with root privileges enabled, and it will take care of
splitting the program into two processes and adjusting privileges for
each half appropriately.
The privman library uses configuration files to
determine what operations the privileged half of a program may
perform on behalf of the unprivileged half of the same program. In
addition, the configuration file determines what user the
unprivileged half of the program runs as, and what directory is used
in the call to chroot(
)
in the
unprivileged process (see Recipe 2.12). By default,
privman runs the unprivileged process as the
user “nobody” and does a
chroot( )
to the root directory, but we strongly
recommend that your program use a user specifically set up for it
instead of “nobody”, and that you
chroot( )
to a safe directory (see Recipe 2.4).
When the priv_init( )
function returns control to
your program, your code will be running in the unprivileged child
process. The parent process retains its privileges, and control is
never returned to you. Instead, the parent process remains in a loop
that responds to requests from the unprivileged process to perform
privileged operations.
The privman
library
provides a number of functions intended to replace standard C runtime
functions for performing privileged operations. When these functions
are called, a request is sent to the privileged process to perform
the operation, the privileged process performs the operation, and the
results are returned to the calling process. The
privman versions of the standard functions are
named with the prefix of priv_
, but otherwise they
have the same signature as the functions they replace.
For example, a call to fopen( )
:
FILE *f = fopen("/etc/shadow", "r");
becomes a call to priv_fopen( )
:
FILE *f = priv_fopen("/etc/shadow", "r");
The following code demonstrates calling priv_init(
)
to initialize the
privman
library, which will split the program into privileged and
unprivileged halves:
#include <privman.h> #include <string.h> int main(int argc, char *argv[ ]) { char *progname; /* Get the program name to pass to the priv_init( ) function, and call * priv_init( ). */ if (!(progname = strrchr(argv[0], '/'))) progname = argv[0]; else progname++; priv_init(progname); /* Any code executed from here on out is running without any additional * privileges afforded by the program running setuid root. This process * is the child process created by the call in priv_init( ) to fork( ). */ return 0; }
privman from NAI Labs: http://opensource.nailabs.com/privman/
Recipe 1.3, Recipe 1.7, Recipe 2.4, Recipe 2.12, Recipe 13.11
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.