Your program is running with extra permissions because its executable has the setuid or setgid bit set. You need to determine whether the user running the program will be able to access a file without the extra privileges granted by setuid or setgid.
Temporarily drop privileges to the user and group for which access is to be checked. With the process's privileges lowered, perform the access check, then restore privileges to what they were before the check. See Recipe 1.3 for additional discussion of elevated privileges and how to drop and restore them.
It is always best to allow the operating system to do the bulk of the work of performing access checks. The only way to do so is to manipulate the privileges under which the process is running. Recipe 1.3 provides implementations for functions that temporarily drop privileges and then restore them again.
When performing access checks on files, you need to be careful to
avoid the types of race conditions known as
Time of Check, Time of Use
(TOCTOU), which are illustrated in Figure 2-1 and Figure 2-2. These
race
conditions occur when access is checked before opening a file. The
most common way for this to occur is to use the access(
)
system
call to verify access to a file, and then to use open(
)
or
fopen( )
to open the file if the return from
access( )
indicates that access will be granted.
The problem is that between the time the access check via
access( )
completes and the time open(
)
begins (both system calls are atomic within the operating
system kernel), there is a window of vulnerability where an attacker
can replace the file that is being operated upon.
Let's say that a program uses access(
)
to check to see whether an attacker has write permissions
to a particular file, as shown in Figure 2-1. If
that file is a symbolic link, access( )
will
follow it, and report that the attacker does indeed have write
permissions for the underlying file. If the attacker can change the
symbolic link after the check occurs, but before the program starts
using the file, pointing it to a file he couldn't
otherwise access, the privileged program will end up opening a file
that it shouldn't, as shown in Figure 2-2. The problem is that the program can
manipulate either file, and it gets tricked into opening one on
behalf of the user that it shouldn't have.
While such an attack might sound impossible to perform, attackers have many tricks to slow down a program to make exploiting race conditions easier. Plus, even if an attacker can only exploit the race condition every 1,000 times, generally the attack can be automated.
The best approach is to actually have the program take on the
identity of the unprivileged user before opening the file. That way,
the correct access permission checks will happen automatically when
the file is opened. You need not even call access(
)
. After the file is opened, the program can revert to its
privileged state. For example, here's some
pseudo-code that opens a file properly, using the
spc_drop_privileges( )
and
spc_restore_privileges( )
functions from Recipe 1.3:
int fd; /* Temporarily drop drivileges */ spc_drop_privileges(0); /* Open the file with the limited privileges */ fd = open("/some/file/that/needs/opening", O_RDWR); /* Restore privileges */ spc_restore_privileges( ); /* Check the return value from open to see if the file was opened successfully. */ if (fd = = -1) { perror("open(\"/some/file/that/needs/opening\")"); abort( ); }
There are many other situations where security-critical race conditions occur, particularly in file access. Basically, every time a condition is explicitly checked, one needs to make sure that the result cannot have changed by the time that condition is acted upon.