If you are writing programs that are SUID or SGID, you must take added precautions in your programming. An overwhelming number of UNIX security problems have been caused by SUID/SGID programs. These rules should be considered in addition to the previous list.
Here are some rules for writing (and not writing) SUID/SGID programs:
"Don't do it. Most of the time, it's not necessary."
 Thanks to Patrick H. Wood and Stephen G. Kochan, UNIX System Security, Hayden Books, 1985, for this insightful remark.
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 under which the program was developed. (See item 5 under Section 23.2, "Tips on Avoiding Security-related Bugs" earlier in this chapter for more information about this suggestion.)
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.
If you must provide a shell escape, be sure to setgid(getgid( )) and setuid(getuid( )) before executing the user's command.
In general, use the setuid() and setgid() functions to bracket the sections of your code which require superuser privileges. For example:
setuid(0); /* Become superuser to open the master file */ fd = open("/etc/masterfile",O_RDONLY); setuid(-1); /* Give up superuser for now */ if(fd<0) error_open(); /* Handle errors */
Not all versions of UNIX allow you to switch UIDS like this; however, most modern versions do.
If you must use pipes or subshells, be especially careful with the environment variables PATH and IFS.
If at all possible, 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.
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 possible.
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 the section Chapter 11, Protecting Against Programmed Threats.)
Consider using perl -T or taintperl for your SUID programs and scripts.
Perl's tainting features make it more suited to SUID programming than C. For example, taintperl will insist that you set the PATH environment variable to a known "safe value" before calling system(). The program will also require 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 you are careless in writing code. Also note that using taintperl introduces dependence on another large body of code working correctly: we'd suggest you skip using taintperl if you believe you can code at least as well as Larry Wall.
 Hint: if you think you can, you are probably wrong.
If you are writing a SUID root program, you can enhance its security 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.
For example, if you have a program which 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 system call to restrict the program to that directory:
There are several issues that you must be aware of when using the chroot() system call that are not immediately obvious:
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 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.
Note that under some versions of UNIX, a user with a root shell and the ability to copy compiled code into the chroot'd environment may be able to "break out." Thus, don't put all your faith in this mechanism.