|
Related Reading
Practical UNIX and Internet Security |
Editor's note: An overwhelming number of Unix security problems have been caused by SUID/SGID programs. In this week's excerpt from Chapter 16 of Practical Unix & Internet Security, 3rd Edition, we offer tips on writing SUID/SGID programs. Consider the rules described in this excerpt as well as those we covered in Part 1 and Part 2. And as an added bonus, this excerpt also includes advice on using the chroot( ) system call to enhance the security of your programs.
If you are writing programs that are SUID or SGID, you must take added precautions in your programming.
"Don't do it. Most of the time, it's not necessary."[17]
Avoid writing SUID shell scripts.
If you are using SUID to access a special set of files, don't. Instead, create a special group for your files and make the program SGID to that group. If you must use SUID, create a special user for the purpose.
If your program needs to perform some functions as superuser, but generally does not require SUID permissions, consider putting the SUID part in a different program, and constructing a carefully controlled and monitored interface between the two.
If you need SUID or SGID permissions, use them for their intended purpose as early in the program as possible, and then revoke them by returning the effective, and real, UIDs and GIDs to those of the process that invoked the program.
If you have a program that absolutely must run as SUID, try to avoid equipping the program with a general-purpose interface that allows users to specify much in the way of commands or options.
Erase the execution environment, if at all possible, and start fresh. Many security problems have been caused because there was a significant difference between the environment in which the program was run by an attacker and the environment in which the program was developed.
If your program must spawn processes, use only the execve( ),
execv( ), or execl( )
calls, and use them with great care. Avoid the execlp(
) and execvp( ) calls because they
use the PATH environment variable to find an executable, and you
might not run what you think you are running. Avoid system( ) and
popen( ) at all costs.
If you must provide a shell escape, be sure to setgid(getgid( )) and
setuid(getuid( )) before executing the
user's command--and use them in the correct
order! You must reset the group ID before you
reset the user ID, or the call will fail.
In general, use the setuid( ) and
setgid( ) functions and their friends to bracket
the sections of your code that require superuser privileges. For
example:
/* setuid program is effectively superuser so it
can open the master file */
fd = open("/etc/masterfile",O_RDONLY);
assert(seteuid(getuid( )) == 0);
/* Give up superuser now, but we can get it back.*/
assert(geteuid() == getuid( ));
/* Insure that the euid is what we expect. */
if(fd<0) error_open( );
/* Handle errors. */
Editor's note: Be aware that the assert() macro is nullified when you compile with the NDEBUG flag enabled. This is a common release optimization. Please read man assert for more details.
Not all versions of Unix allow you to switch UIDs in this way;
moreover, the semantics of the various versions of setuid(
), seteuid( ), and
setreuid( ) have been shown to vary between Unix
flavors, and even be misimplemented. It's also
crucial both to check their return values and to separately test to
ensure that the UIDs are as you expect them. Read Chen, Wagner, and
Dean's paper "Setuid Demystified" (http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf)
before you even think about writing code that tries to save and
restore privileges.
If you must use pipes or subshells, be especially careful with the environment variables and IFS. One approach is to erase these variables and set them to safe values. For example:
putenv("PATH=/bin:/usr/bin:/usr/ucb");
putenv("IFS= \t\n");Then, examine the environment to be certain that there is only
one instance of the variable: the one you set.
An attacker can run your code from another program that creates
multiple instances of an environment variable. Without an explicit
check, you may find the first instance, but not the others; such a
situation could result in problems later on. In particular, step
through the elements of the environment yourself rather than
depending on the library getenv( ) function.
Another approach, simpler but more drastic, is to create an empty
environment and fill it with only those variables that you know are
OK. This environment can then be passed to execve():
char *env[MAX_ENV];
int mysetenv(const char *name, const char *value) {
static char count = 0;
char buff[255];
if (count == MAX_ENV) return 0;
if (!name || !value) return 0;
if (snprintf(buff, sizeof(buff), "%s=%s", name,
value) < 0) return 0;
if (env[count] = strdup(buff)) {
count++;
return 1;
}
return 0;
}
...And then in the program...
if (mysetenv("PATH", "/bin:/usr/bin") &&
mysetenv("SHELL", "/bin/sh") &&
mysetenv("TERM", "vt100") &&
mysetenv("USER", getenv("USER")) &&
mysetenv("LOGNAME", getenv("LOGNAME")) &&
mysetenv("HOME", getenv("HOME"))) {
execve(myprogram,NULL,env);
perror(myprogram);
} else {
perror("Unable to establish safe environment");
}Use the full pathname for all files that you open. Do not make any
assumptions about the current directory. (You can enforce this
requirement by doing a chdir("/tmp/root/") as
one of the first steps in your program, but be sure to check the
return code!)
Consider statically linking your program. If a user can substitute a different module in a dynamic library, even carefully coded programs are vulnerable. (We have some serious misgivings about the trend in commercial systems towards completely shared, dynamic libraries. (See our comments in Section 23.6.2 in Chapter 23 of Practical Unix & Internet Security, 3rd Edition.)
Consider using perl-T or taintperl
for your SUID programs and scripts. Perl's tainting
features often make Perl more suited than C to SUID programming. For
example, taintperl insists that you set the PATH
environment variable to a known "safe
value" before calling system(). The program also requires that you
"untaint" any variable that is
input from the user before using it (or any variable dependent on
that variable) as an argument for opening a file.
However, note that you can still get yourself in a great deal of
trouble with taintperl if you circumvent its
checks or if you are careless in writing code. Also note that using
taintperl introduces dependence on another large
body of code working correctly: we suggest you skip using
taintperl if you believe that you can code at
least as well as Larry Wall.[18]
You can enhance the security of your programs by using the
chroot( ) system call. The chroot( ) call changes the
root directory of a process to a specified
subdirectory within your filesystem. This change essentially gives
the calling process a private world from which it cannot
escape.[19] Several widely used network daemons, such as the BIND
nameserver, are written so they can run in a chroot() environment.
For example, if you have a program that only needs to listen to the network and write into a log file that is stored in the directory /usr/local/logs, then you could execute the following code to restrict the program to that directory:
assert(chdir("/usr/local/logs") == 0);
assert(chroot("/usr/local/logs") == 0);
assert(chdir("/") == 0);
There are several issues that you must be aware of when using the
chroot( ) system call that are not immediately
obvious:
It is imperative that you successfully chdir( )
into the chroot area before doing anything
important (and best if you chdir( ) there before
you call chroot( )). chroot(
) does not change the working directory, and a privileged
program can break out of a chroot area if its
working directory is outside the area.
With some systems, it is also critical that you set the current
working directory to be "/" after
the chdir is executed. Otherwise, it is possible
to break out of the chroot( ) system in some
cases.
If your operating system supports shared libraries and you are able to statically link your program, you should be sure that your program is statically linked. On some systems, static linking is not possible. On these systems, you should make certain that the necessary shared libraries are available within the restricted directory (as copies).
You should not give other users write access to the chroot(
)ed directory.
If you intend to log with syslog( ), you should
call the openlog( ) function before executing
the chroot( ) system call, or make sure that a
/dev/log device file exists within the
chroot( ) directory.
chroot( )ed processes should run with a UID that
is not used by any programs outside of the chroot(
) area. This prevents the processes from using debugger
hooks to manipulate outside processes and potentially subvert the
jail.
Do not allow root-owned processes to run inside the
chroot area. As soon as your program
successfully chroots, it should immediately
setgid( ) and setuid( ) to
give up its superuser privileges. Likewise, where possible, restrict
the occurance of SUID programs and devices within the
chroot environment.
Many versions of Unix provide a program called
chroot that can be used to execute an arbitrary
command in a chrooted environment, like this:
# chroot /path/to/directory /chrooted/path/to/command arguments
This will cause a chroot to the specified
directory (which must be set up as described earlier), and then run
the given command, which must be in the chrooted
area (along with any necessary shared libraries, etc.) already.
Note that under some versions of Unix, a user with a
root shell and the ability to copy compiled code
into the chrooted environment may be able to
"break out." The same applies to an
SUID program (or other program running as root)
that has not dropped its privileges. Thus, don't put
all your faith in this mechanism.
[17] Thanks to Patrick H. Wood and Stephen G. Kochan, Unix System Security (Hayden Books, 1985) for this insightful remark.
[18] Hint: if you think you can, you are probably wrong.
[19] BSD systems have an even more powerful version of chroot( ) called jail( ) that can provide significantly better isolation to jailed processes.
Return to ONLamp.com.
Copyright © 2009 O'Reilly Media, Inc.