International * Contact  * Sitemap  * Links  * Register Software
Search  
 SUSE - simply change

Home Users

 Novell
  | Home  |  | Overview  |  | Products  |  | Support  |  | Downloads  |  | Distributors & Resellers  |
  SUSE LINUX Support   Online Help   License information   Security   Feedback
  Printable page

Security-specific Programming Errors (Part 2)

Thomas Biege

Race Conditions (not limited to C!)

Race conditions can occur wherever non-atomic calls for security-specific program sections are used. This section focuses on a classic and frequent type of race condition. Basically, race conditions can occur in the most diverse manners and are not limited to the file system.

Examples:

When a privileged application opens a file belonging to the user activating the program, the system needs to check whether the user is entitled to do this in the first place, and needs to do so before the application opens the file.

Wrong:

[...]
if(access("/home/evil_ed/RythmStick", W_OK) == 0)
{
    /* User has write permission */
    if((fd = open("/home/evil_ed/RythmStick", O_WRONLY)) < 0)
    {
         fprintf(stderr, "I'm not allowed to open
                                                   the file!\n");
         exit(-1);
    }
}
[...]

First, it is necessary to understand the user-specific IDs in Unix:

User ID

Every user is given a unique number. UID 0 belongs to root. Anybody working with UID 0 will not encounter any security barriers from the system. Normal users are generally assigned a UID starting at 100, for example, or 500. All the UIDs below this belong to system processes.

Group ID

Unix systems have several groups. The administrators, for example, can be assigned to the group root or wheel. Users are generally assigned to the group called user or to groups that identify their activities, e.g., fb4, wwwadmin, students, accounting, etc. A user can belong to 1...NGROUPS_MAX (32) groups.

SetUID and SetGID

In order for a program to accomplish a task for which it requires higher-level privileges, the program can either be executed by a user with these rights, or the system needs to be informed that the program is to be assigned the necessary rights when executing. The former is a very laborious and insecure approach as either the password needs to be known by all, or the person with the password has to log in to the system to accomplish a task for another user. In the case of a printer or fax spooler, the effort involved would not be justifiable. The Unix file system implementation has two files for this very purpose. The SetUID flag gives the program the (effective) UID of the user to whom the program belongs and not the UID of the user executing the program (the real UID). The same applies to the SetGID flag.

real UID/GID and effective UID/GID

The real UID is the UID of the user. If user Thomas with UID 543 executes a SetUID program from user root, for example, the program has the effective UID 0 and the real UID 543. As Unix systems use the effective UID for access checking on system objects, the UIDs can be substituted and the effective UID can even be discarded. If the real UID is not 0, a UID that has been discarded cannot be retrieved. A substituted UID, on the other hand, can.

saved UID/GID (POSIX)

The saved IDs are only implemented when _POSIX_SAVED_IDS is set, which should be the case with today's Unix alternatives. The saved UID is set by the exec*(3) functions and allows the UID to be changed. For example, if the real and effective UID is 543 and the saved UID 0, then it is possible to change, even though the effective UID is not 0. The saved UID is only discarded when setuid(2) is called and the effective UID is 0. Moreover, SetUID applications whose SetUID is not 0 must use setreuid(getuid(), getuid()) to get rid of their privileges. A simple setuid(2) doesn't help. Of course, the same applies to the GID, too.

The access(2) call uses the real UID and real GID to check the rights. This means that the effective UID/GID of SetUID/-GID programs does not apply. In the case of the access check with open(2), on the other hand, the effective UID/GID is used. This fact wouldn't be worth mentioning - in fact, it would even be desirable - if access(2) and open(2) were an atomic function i.e., a system call. Between access(2) and open(2), however, there is now a time window during which the program is vulnerable. The attacker can now delete the file /home/evil_ed/RythmStick and replace it with a link pointing to /etc/security/shadow, for example. The open(2) call thus does not then open /home/evil_ed/RythmStick, but /etc/security/shadow via the link instead, and processes the data which the user is not actually allowed to access.

To summarize:

There is a file called /home/evil_ed/RythmStick. The user EvilEd has write permission for this file, which is checked with access("...", W_OK). EvilEd now deletes the original file and places a link to a file for which he does not have write permission. Open(2) follows the link and opens the protected file.

Right:

This problem can be encountered in different ways.

faccess()

Unfortunately, faccess() does not exist on all systems, e.g., not on Linux, OpenBSD, Solaris and AIX.

[...]
fd = open("file", O_WRONLY);
if(faccess(fd, W_OK) != 0)
{
     fprintf(stderr, "Nice try!\n");
     exit(-1);
}
[...]

A further disadvantage of faccess() is that open(2) needs to be called first. When open(2) is used on device files (Unix), an action can already be performed with the corresponding device depending on the implementation of the device driver. Magnetic tapes are one example - they have to be rewound when the device file is opened. In the case of iterative backups, this can lead to loss of the old backup data.

O_NOFOLLOW

The open(2) option O_NOFOLLOW forbids open(2) for following symbolic links. This can be circumvented with hard links, so it is of little use.

setegid(2) and seteuid(2)

With seteuid(2) and setegid(2), the program's higher-level privileges can be discarded and the rights of the user accepted for the period during which open(2) is being executed. The old rights can be restored after open(2).

The sete(u|g)id(2) and setre(u|g)id(2) functions conform to BSD4.3 and are included in the extended POSIX standard when _POSIX_SAVED_IDS is defined. The operating system must thus support saved IDs in addition to the real and effective IDs. It is important to make sure that the group ID is placed before the User ID as the GID cannot otherwise be changed when the UID has been reduced. This fact is often forgotten, which means that the program still has a security flaw. Moreover, the return value of setuid(2) and setgid(2) should always be checked in order to be able to react to unpleasant events.

[...]
uid_t euid, ruid;
gid_t egid, rgid;

euid = geteuid();
egid = getegid();
ruid = getuid();
rgid = getgid();

if(setegid(rgid) < 0)
     [Exit]
if(seteuid(ruid) < 0)
     [Exit]

open("...", ...);

if(setegid(egid) < 0)
     [Exit]
if(seteuid(euid) < 0)
     [Exit]
[...]
fork(2)

The only portable, most secure (and most elaborate) way to create a child process with fork(2) is to discard the privileges permanently, open the file and end the child process after the file descriptor has been returned to the parent process.

[...]
pid_t child_pid;
[...]

if((child_pid = fork()) < 0)
     [Exit]
else if(child_pid > 0) // Parent
     [Get Filedescriptor and Wait for Child]
else                   // Child
{
     inf fd;

     if(setgid(getgid()) < 0)
          [Exit]
     if(setuid(getuid()) < 0)
          [Exit]
     /*
     ** When the EUID of the process is 0, the
     ** _all_ IDs are changed with set(u|g)id(2)
     ** (real, effective, saved)!
     ** Otherwise, _only_ the effective ID is set!
     */

     if( (fd = open("userfile", O_WRONLY)) < 0)
          [Exit]

     /*
     ** Now, the file descriptor can be passed to the parent
     ** process. Unfortunately, there is not yet
     ** a standardized function for this. SystemV and BSD Unix
     ** offer different possibilities.
     ** SVR4:
     ** - ioctl(I_SENDFD)
     ** - Unix Domain Sockets
     ** 4.3BSD:
     ** - sendmsg(2) and recvmsg(2)
     ** - Unix Domain Sockets
     */
     [...]
}
[...]

Further Information

* Reseller
* Reviews
* Support Database
* Hardware Database
* Education Program

Quick Links

* Security
* Support Portal
* Mailing Lists
* Feedback
* SUSE LINUX eNewsletter

Subscribe now!

Get the Live DVD and Run Linux in Seconds!

SUSE LINUX 9.1 Personal Live CD

Want a hassle-free way to try Linux? Download SUSE LINUX Professional 9.2 Live DVD. It runs completely from your DVD drive. No need to install anything.

 This server is powered by NPS.
Linux is a registered trademark of Linus Torvalds.
Last changed: 05.12.2001 17:18 MET by webmaster@suse.de