5.6 Setuid root Startup Scripts

If a group of developers need to be able to start and stop the server, there may be a temptation to give them the root password, which is probably not a wise thing to do. The fewer people that know the root password, the less likely you will encounter problems. Fortunately, an easy solution to this problem is available on Unix platforms. It is called a setuid executable (setuid root in this case).

Before continuing, we must stress that this technique should not be used unless it is absolutely necessary. If an improperly written setuid script is used, it may compromise the system by giving root privileges to system breakers (crackers).

To be on the safe side, do not deploy the techniques explained in this section. However, if this approach is necessary in a particular situation, this section will address the possible problems and provide solutions to reduce the risks to a minimum.

5.6.1 Introduction to setuid Executables

A setuid executable has the setuid permissions bit set, with the following command:

panic% chmod u+s filename

This sets the process's effective user ID to that of the file upon execution. Most users have used setuid executables even if they have not realized it. For example, when a user changes his password he executes the passwd command, which, among other things, modifies the /etc/passwd file. In order to change this file, the passwd program needs root permissions. The passwd command has the setuid bit set, so when someone executes this utility, its effective ID becomes the root user ID.

Using setuid executables should be avoided as a general practice. The less setuid executables there are in a system, the less likely it is that someone will find a way to break in. One approach that crackers use is to find and exploit unanticipated bugs in setuid executables.

When the executable is setuid to root, it is vital to ensure that it does not extend read and write permissions to its group or to the world. Let's take the passwd utility as an example. Its permissions are:

panic% ls -l /usr/bin/passwd
-r-s--x--x 1 root root 12244 Feb 8 00:20 /usr/bin/passwd

The program is group- and world-executable but cannot be read or written by group or world. This is achieved with the following command:

panic% chmod 4511 filename

The first digit (4) stands for the setuid bit, the second digit (5) is a bitwise-OR of read (4) and executable (1) permissions for the user, and the third and fourth digits set the executable (1) permissions for group and world.

5.6.2 Apache Startup Script's setuid Security

In the situation where several developers need to be able to start and stop an Apache server that is run by the root account, setuid access must be available only to this specific group of users. For the sake of this example, let's assume that these developers belong to a group named apache. It is important that users who are not root or are not part of the apache group are unable to execute this script. Therefore, the following commands must be applied to the apachectl program:

panic% chgrp apache apachectl
panic% chmod  4510  apachectl

The execution order is important. If the commands are executed in reverse order, the setuid bit is lost.

The file's permissions now look like this:

panic% ls -l apachectl
-r-s--x--- 1 root apache 32 May 13 21:52 apachectl

Everything is set. Well, almost...

When Apache is started, Apache and Perl modules are loaded, so code may be executed. Since all this happens with the root effective ID, any code is executed as if run by the root user. This means that there is a risk, even though none of the developers has the root password?all users in the apache group now have an indirect root access. For example, if Apache loads some module or executes some code that is writable by any of these users, they can plant code that will allow them to gain shell access to the root account.

Of course, if the developers are not trusted, this setuid solution is not the right approach. Although it is possible to try to check that all the files Apache loads are not writable by anyone but root, there are so many of them (especially with mod_perl, where many Perl modules are loaded at server startup) that this is a risky approach.

If the developers are trusted, this approach suits the situation. Although there are security concerns regarding Apache startup, once the parent process is loaded, the child processes are spawned as non-root processes.

This section has presented a way to allow non-root users to start and stop the server. The rest is exactly the same as if they were executing the script as root in the first place.

5.6.3 Sample setuid Apache Startup Script

Example 5-1 shows a sample setuid Apache startup script.

Note the line marked WORKAROUND, which fixes an obscure error when starting a mod_perl-enabled Apache, by setting the real UID to the effective UID. Without this workaround, a mismatch between the real and the effective UIDs causes Perl to croak on the -e switch.

This script depends on using a version of Perl that recognizes and emulates the setuid bits. This script will do different things depending on whether it is named start_httpd, stop_httpd, or restart_httpd; use symbolic links to create the names in the filesystem.

Example 5-1. suid_apache_ctl
#!/usr/bin/perl -T
use strict;

# These constants will need to be adjusted.
my $PID_FILE = '/home/httpd/httpd_perl/logs/httpd.pid';
my $HTTPD = '/home/httpd/httpd_perl/bin/httpd_perl ';
$HTTPD   .= '-d /home/httpd/httpd_perl';

# These prevent taint checking failures
$ENV{PATH} = '/bin:/usr/bin';

# This sets the real to the effective ID, and prevents
# an obscure error when starting apache/mod_perl
$< = $>; # WORKAROUND
$( = $) = 0; # set the group to root too

# Do different things depending on our name
my $name = $0;
$name =~ m|([^/]+)$|;

if ($name eq 'start_httpd') {
    system $HTTPD and die "Unable to start HTTPD";
    print "HTTP started.\n";
    exit 0;

# extract the process id and confirm that it is numeric
my $pid = `cat $PID_FILE`;
$pid =~ /^(\d+)$/ or die "PID $pid not numeric or not found";
$pid = $1;

if ($name eq 'stop_httpd') {
    kill 'TERM', $pid or die "Unable to signal HTTPD";
    print "HTTP stopped.\n";
    exit 0;

if ($name eq 'restart_httpd') {
    kill 'HUP', $pid or die "Unable to signal HTTPD";
    print "HTTP restarted.\n";
    exit 0;

# script is named differently
die "Script must be named start_httpd, stop_httpd, or restart_httpd.\n";

    Part I: mod_perl Administration
    Part II: mod_perl Performance
    Part VI: Appendixes