10.4 Real and Effective IDs

Until now, we've talked about the user ID and group ID associated with a process as if there were only one such user ID and one such group ID. But, actually, it's not quite that simple.

Every process really has two user IDs: the effective user ID and the real user ID. (Of course, there's also an effective group ID and real group ID. Just about everything that's true about user IDs is also true about group IDs.) Most of the time, the kernel checks only the effective user ID. For example, if a process tries to open a file, the kernel checks the effective user ID when deciding whether to let the process access the file.

The geteuid and getegid functions described previously return the effective user ID and the effective group ID. Corresponding getuid and getgid functions return the real user ID and real group ID.

If the kernel cares about only the effective user ID, it doesn't seem like there's much point in having a distinction between a real user ID and an effective user ID. However, there is one very important case in which the real user ID matters. If you want to change the effective user ID of an already running process, the kernel looks at the real user ID as well as the effective user ID.

Before looking at how you can change the effective user ID of a process, let's examine why you would want to do such a thing by looking back at our accounting package. Suppose that there's a server process that might need to look at any file on the system, regardless of the user who created it. Such a process must run as root because only root can be guaranteed to be capable of looking at any file. But now suppose that a request comes in from a particular user (say, mitchell) to access some file. The server process could carefully examine the permissions associated with the files in question and try to decide whether mitchell should be allowed to access those files. But that would mean duplicating all the processing that the kernel would normally do to check file access permissions. Reimplementing that logic would be complex, errorprone, and tedious.

A better approach is simply to temporarily change the effective user ID of the process from root to mitchell and then try to perform the operations required. If mitchell is not allowed to access the data, the kernel will prevent the process from doing so and will return appropriate indications of error. After all the operations taken on behalf of mitchell are complete, the process can restore its original effective user ID to root.

Programs that authenticate users when they log in take advantage of the capability to change user IDs as well. These login programs run as root. When the user enters a username and password, the login program verifies the username and password in the system password database. Then the login program changes both the effective user ID and the real ID to be that of the user. Finally, the login program calls exec to start the user's shell, leaving the user running a shell whose effective user ID and real user ID are that of the user.

The function used to change the user IDs for a process is setreuid. (There is, of course, a corresponding setregid function as well.) This function takes two arguments. The first argument is the desired real user ID; the second is the desired effective user ID. For example, here's how you would exchange the effective and real user IDs:

 
setreuid  (geteuid( ),  getuid  ( )); 

Obviously, the kernel won't let just any process change its user IDs. If a process were allowed to change its effective user ID at will, then any user could easily impersonate any other user, simply by changing the effective user ID of one of his processes. The kernel will let a process running with an effective user ID of 0 change its user IDs as it sees fit. (Again, notice how much power a process running as root has! A process whose effective user ID is 0 can do absolutely anything it pleases.) Any other process, however, can do only one of the following things:

·         Set its effective user ID to be the same as its real user ID

·         Set its real user ID to be the same as its effective user ID

·         Swap the two user IDs

The first alternative would be used by our accounting process when it has finished accessing files as mitchell and wants to return to being root. The second alternative could be used by a login program after it has set the effective user ID to that of the user who just logged in. Setting the real user ID ensures that the user will never be able go back to being root. Swapping the two user IDs is almost a historical artifact; modern programs rarely use this functionality.

You can pass -1 to either argument to setreuid if you want to leave that user ID alone. There's also a convenience function called seteuid. This function sets the effective user ID, but it doesn't modify the real user ID. The following two statements both do exactly the same thing:

 
seteuid (id); 
setreuid ( -1, id); 

10.4.1 Setuid Programs

Using the previous techniques, you know how to make a root process impersonate another process temporarily and then return to being root. You also know how to make a root process drop all its special privileges by setting both its real user ID and its effective user ID.

Here's a puzzle: Can you, running as a nonroot user, ever become root ? That doesn't seem possible, using the previous techniques, but here's proof that it can be done:

 
% whoami 
mitchell 
% su 
Password: ... 
% whoami 
root 

The whoami command is just like id, except that it shows only the effective user ID, not all the other information. The su command enables you to become the superuser if you know the root password.

How does su work? Because we know that the shell was originally running with both its real user ID and its effective user ID set to mitchell, setreuid won't allow us to change either user ID.

The trick is that the su program is a setuid program. That means that when it is run, the effective user ID of the process will be that of the file's owner rather than the effective user ID of the process that performed the exec call. (The real user ID will still be that of the executing user.) To create a setuid program, you use chmod +s at the command line, or use the S_ISUID flag if calling chmod programmatically. [6]

[6] Of course, there is a similar notion of a setgid program. When run, its effective group ID is the same as that of the group owner of the file. Most setuid programs are also setgid programs.

For example, consider the program in Listing 10.3.

Listing 10.3 (setuid-test.c) Setuid Demonstration Program
#include <stdio.h> 
#include <unistd.h> 
 
int main () 
{
  printf ("uid=%d euid=%d\n", (int) getuid (), (int) geteuid ()); 
  return 0; 
} 

Now suppose that this program is setuid and owned by root. In that case, the ls output will look like this:

 
-rwsrws--x    1 root     root        11931 Jan 24 18:25 setuid-test 

The s bits indicate that the file is not only executable (as an x bit would indicate) but also setuid and setgid. When we use this program, we get output like this:

 
% whoami 
mitchell 
% ./setuid-test 
uid=501 euid=0 

Note that the effective user ID is set to 0 when the program is run.

You can use the chmod command with the u+s or g+s arguments to set the setuid and setgid bits on an executable file, respectively—for example:

 
% ls -l program 
-rwxr-xr-x    1 samuel   csl             0 Jan 30 23:38 program 
% chmod g+s program 
% ls -l program 
-rwxr-sr-x    1 samuel   csl             0 Jan 30 23:38 program 
% chmod u+s program 
% ls -l program 
-rwsr-sr-x    1 samuel   csl             0 Jan 30 23:38 program 

You can also use the chmod call with the S_ISUID or S_ISGID mode flags.

su is capable of changing the effective user ID through this mechanism. It runs initially with an effective user ID of 0. Then it prompts you for a password. If the password matches the root password, it sets its real user ID to be root as well and then starts a new shell. Otherwise, it exits, unceremoniously leaving you as a non-privileged user.

Take a look at the permissions on the su program:

 
% ls -l /bin/su 
-rwsr-xr-x    1 root     root        14188 Mar  7  2000 /bin/su 

Notice that it's owned by root and that the setuid bit is set.

Note that su doesn't actually change the user ID of the shell from which it was run. Instead, it starts a new shell process with the new user ID. The original shell is blocked until the new shell completes and su exits.