Add Book to My BookshelfPurchase This Book Online

Chapter 8 - Users and Groups

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Writing Set-User-Id and Set-Group-Id Programs
Set-user-id and set-group-id programs are extraordinarily useful tools (in fact, the set-user-id bit is the only part of the original UNIX operating system that was patented). These programs enhance security, by letting unprivileged users perform certain privileged tasks without “giving away the store” and letting everyone have the root password.
Before undertaking the writing of a set-user-id or set-group-id program, however, it is important to realize that there are several ways in which an unscrupulous user can attempt to trick these programs into granting him privileges that he should not have. This includes fooling the program into reading or writing files that the attacker does not have access to (e.g., the password file), getting the program to start an interactive shell with the wrong real or effective user ID, tricking the program into changing the permission bits on a file other than the one it thinks it's changing, making the program execute a command different from the one it thinks it's executing, and so forth.
The simplest rule to follow in writing set-user-id and set-group-id programs is, “if there's another way, don't.” These programs should not be used indiscriminately. If there is a secure method with which you can accomplish what you want without using a set-user-id or set-group-id program, use that method instead. Don't create a set-user-id or set-group-id program just to save yourself the trouble of doing things right the first time.
And while we're speaking of doing things right, if you do decide to write a set-user-id program, always begin the program as follows:
    int euid;
    int
    main(int argc, char **argv)
    {
        /* variable declarations */
        euid = geteuid();
        seteuid(getuid());
    ·
    ·
    ·
This code causes the program to save its special privileges, but revert back to the calling user's “normal” privileges at once. In this way, if the program should encounter an error, it can only cause the damage that the user's privileges allow it to; it cannot cause extra damage because of its extra privileges. Then, when the program needs to do a privileged operation, the code for that can be bracketed as follows:
    /* non-privileged code */
    seteuid(euid);
    /* privileged code */
    seteuid(getuid());
    /* non-privileged code */
In this way, the program only uses its special privileges when it absolutely has to, and the amount of code that has to be carefully examined for defects is much smaller. The same idea applies for set-group-id programs.
If you've read all the information above and still think you need to write a program, follow the list of rules below. This list has been adapted and expanded from a paper by Matt Bishop, entitled How to Write a Setuid Program, which appeared in the January/February 1987 issue of ;login:, the newsletter of the USENIX Association. Some of these rules describe topics discussed later in the book; if you don't understand them now, don't worry. But be sure to come back and read this list if you ever need to write a set-user-id or set-group-id program.
 1.The overall rule, upon which all the rest of these rules are based, is even paranoids have enemies. You cannot be too paranoid when writing these programs; one slip-up and the security of your system will be defeated. Don't trust anyone or anything, not even the operating system. Don't ever think “this can't happen.” Sooner or later it will, and your program had better be prepared for it.
 2.Never, ever, write set-user-id or set-group-id interpreted scripts. Some versions of UNIX allow command scripts, such as shell scripts, to be made set-user-id or set-group-id. Unfortunately, the power and complexity of the interpreters make them easy to trick into performing functions that were not intended. This rule applies to Bourne shell scripts, C shell scripts, Korn shell scripts, Perl scripts, awk scripts, Tcl scripts, and indeed any other script that is processed by a command interpreter.
 3.Be as restrictive as possible in choosing the user ID and group ID. Don't give a program more privilege than it needs. For example, if a game program is made set-user-id root so that it can write its score file, and an attacker can figure out how to get the game to start a subshell (as many can), the set-user-id bit will give the attacker a superuser shell. On the other hand, if the game programs were all made set-user-id to the “games” account, then the attacker would be able to do much less with his set-user-id subshell (he could change the game's high score, but not much else).
 4.Reset the effective user ID and group ID before calling exec. This seems obvious, but is often overlooked. When it is, a user may find herself running a program with unexpected privileges. This is often a problem with programs that use the setreuid or setregid functions. It is important to remember that even if you don't call exec directly, some library routines such as popen and system call it for you. Whenever calling any function whose purpose is to execute another command as though that command were typed at the keyboard, the effective user ID and group ID should be reset as follows, unless there is a compelling reason not to:
    setuid(getuid());
    setgid(getgid());
 5.Close all unnecessary files before calling exec. If your set-user-id or set-group-id program uses its privileges to open a file that would otherwise be inaccessible to the user, and then executes another process (such as a shell) without closing that file, the new process will also be able to read and/or write that file, because files stay open by default across calls to exec. The easiest way to prevent this is to set the file's close-on-exec flag, as described in Chapter 6, Special-Purpose File Operations, immediately after opening the file.
 6.Check ownership and access permissions on file descriptors, not filenames. A favorite technique of attackers is to execute a set-user-id or set-group-id program that accesses one of their own files (programs that copy users' files into trusted areas such as spool directories are a prime example). The program uses stat or access to check the ownership or permissions on the file, and then opens the file and processes it. This creates a window between the time the program has checked things and the time it opens the file. The attacker can stop the program, replace the real file with a symbolic link to some other file, and then continue the program. The program, already satisfied that it has made its checks, continues on as if nothing is wrong. To avoid this, always open the file first. Then use fstat on the file descriptor to check ownership and permissions. This technique insures that even if the attacker is trying to fool you with a symbolic link, you will be checking the information about the file you will actually be using, and not the file he substituted.
 7.Catch or ignore all signals. As mentioned in the previous rule, an attacker can use some signals (stop and continue, in that case) to confuse your program. She can let your program check that everything is “right” before doing something, stop the program, change things around so they are no longer “right,” and then let the program continue. Set-user-id and set-group-id programs should catch or ignore all signals possible. At the very minimum, the following signals should be caught or ignored: SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT (SIGIOT), SIGEMT, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGPOLL, SIGTSTP, SIGTTIN, SIGTTOU, SIGVTALRM, SIGPROF, SIGXCPU, SIGXFSZ.
 8.Never trust your inherited environment. Do not rely on the values of a user's environment variables, such as PATH, USER, LOGNAME, etc. When executing programs, always specify an absolute pathname to the program to be executed. If you rely on the user's search path, he can use this to trick you into executing something you don't expect. When checking identity, use only the real user ID and the password file. If you rely on the environment variables or the results of getlogin or cuserid, the user can lie to you. Always set your umask explicitly. If you don't, the user can trick you into creating world-writable files. (Don't create the file and then rely on using chmod to fix its mode; the user can stop your program and change the file's contents before you get to complete both steps.)
 9.Never pass on your inherited environment. This relates to the item above, but is more insidious. Especially with shared libraries, it is possible for an attacker to put things in the environment that do not affect your program, but do affect programs executed by your program. Always provide programs you execute from a set-user-id or set-group-id program with a “clean” environment. If you must copy values from the inherited environment into the new one, check their contents for validity before passing them on.
 10.Never trust your input. Never rely on the fact that your program's input is in the format you expect, or that it was created by whoever or whatever was supposed to have created it. If your program is given garbage as input, it should recognize this and discard it, rather than try to make sense of the garbage. If your program reads input from somewhere, make sure that it is not possible to overflow your program's buffers. Never assume an array is big enough to hold the input; if you read data into an array, refuse to read more data than the array will hold. Never, ever, use the gets function.
 11.Never trust system calls or library routines. Check the return values of everything, even those things that “can't happen.” For example, it is often assumed that the close function cannot fail. But on an NFS filesystem, the only indication a process receives that a filesystem it tried to write to is full is delivered as a return code from close.
 12.Make only safe assumptions about error recovery. If your program detects an error over which it has no control (such as no more file descriptors), the proper thing to do is exit. Do not, under any circumstances, attempt to handle unexpected or unknown situations; you may be operating under incorrect assumptions. For example, a long time ago, the passwd program assumed that if the password file could not be opened, something was seriously wrong with the system, and the user should be given a superuser shell to fix the problem. Not a good assumption.
Following these rules will help you keep your set-user-id or set-group-id program safe from attack. But no list of rules is perfect. Always approach the writing of these programs with the utmost care, and always verify that they do only what you want them to do. And remember, if you don't really, really need one, don't write one.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback