Functions from the setuid-family including setuid and setgid are used to change the identity of the caller
process. They are used to change privileges for the subsequent actions to be executed. If a call to these functions returns an error that is not
checked and handled appropriately, the subsequent parts of the program will execute with unexpected privileges. This, in turn, leads to unexpected
program behavior and poses a serious security risk.
What is the potential impact?
Functions for managing a program’s privileges are fairly complex and one needs to take great care to use them correctly.
Failing to correctly handle potential errors indicated by those functions' respective return values can lead to unexpected behavior. If the program
fails to acquire more privileges to execute a privileged operation, for instance, the OS will disallow the operation and the program is likely
terminated by the OS. However, if the program silently fails to drop its privileges, it will continue to run the subsequent operations with more
privileges than expected, which poses a serious security risk.
Example program that changes between privileged and unprivileged mode
A brief example on how setuid can be used and how to correctly check its return value is given in what follows.
Consider the following example:
Assume that the program shown below has been compiled to its binary named my-program. One can change its owner to root, for
instance.
$ sudo chown root:root my-program
After setting the program’s setuid bit using
$ sudo chmod u+s my-program
the program’s permissions may look like
-rwsrwxr-x 1 root root 16416 Aug 21 15:45 my-program
The 's' indicates that the executable has the setuid bit set. Notice that the program’s owner (here root) may be different from its caller.
Functions from the setuid-family can be used to manipulate privileges and acquire (or drop) the program owner’s privileges.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
static uid_t real_uid;
static uid_t effective_uid;
void do_setuid(void) {
printf("Try to acquire privileges\n");
int status;
status = seteuid(effective_uid);
if (status < 0) { // Compliant: return code is checked and potential errors are handled.
fprintf(stderr, "Couldn't set uid.\n");
exit(status);
}
printf("Set uid to %lu\n", (unsigned long int)effective_uid);
}
void undo_setuid(void) {
printf("Try to drop privileges\n");
int status;
status = seteuid(real_uid);
if (status < 0) { // Compliant: return code is checked and potential errors are handled.
fprintf(stderr, "Couldn't set uid.\n");
exit(status);
}
printf("Set uid to %lu\n", (unsigned long int)real_uid);
}
int main(void) {
real_uid = getuid(); // Real ID of the program's caller.
effective_uid = geteuid(); // Effective ID of the program's owner (here root).
// Immediately drop privileges and set effective user ID back to real user ID
// such that operations are performed using the real user ID for determining
// permissions.
undo_setuid();
// Switch back to file user ID (the owner's privileges) only if the effective
// ID (the privileges) is required to determine permission for privileged
// operations.
do_setuid();
//
// PERFORM PRIVILEGED OPERATION (using root privileges in this example).
//
// Drop privileges as soon as they are no longer needed.
undo_setuid();
// There are a multitude of operations that can be extremely dangerous if
// executed as root. Assume that this program would not correctly handle
// potential errors. In that case, the following system call poses a security
// risk, if dropping privileges fails. The following command recursively removes
// all files and directories inside the /tmp directory. If executed with root
// permissions, it can potentially delete important files and directories, causing
// system instability or data loss.
system("rm -rf /tmp/*"); // Caution: poses a security risk if accidentally executed as root.
}