10.2 Process Attributes
10.2.1 The pid and Parentage
Two of the most basic attributes are its process ID, or pid, and the pid of its parent process. A pid is a positive integer that uniquely identifies a running process and is stored in a variable of type pid_t. When a new process is created, the original process is known as the parent of the new process and is notified when the new child process ends.
When a process dies, its exit code is stored until the parent process requests it. The exit status is kept in the kernel's process table, which forces the kernel to keep the process's entry active until it can safely discard its exit status. Processes that have exited and are kept around only to preserve their exit status are known as zombies. Once the zombie's exit status has been collected, the zombie is removed from the system's process table.
If a process's parent exits (making the child process an orphan process), that process becomes a child of the init process. The init process is the first process started when a machine is booted and is assigned a pid of 1. One of the primary jobs of the init process is to collect the exit statuses of processes whose parents have died, allowing the kernel to remove the child processes from the process table. Processes can find their pid and their parent's pid through the getpid() and getppid() functions.
pid_t getpid(void)
Returns the pid of the current process.
pid_t getppid(void)
Returns the parent process's pid.
10.2.2 Credentials
Linux uses the traditional Unix security mechanisms of users and groups. User IDs (uids) and group IDs (gids) are integers [1] that are mapped to symbolic user names and group names through /etc/passwd and /etc/group, respectively (see Chapter 28 for more information on the user and group databases). However, the kernel knows nothing about the names—it is concerned only with the integer representation. The 0 uid is reserved for the system administrator, normally called root. All normal security checks are disabled for processes running as root (that is, with a uid of 0), giving the administrator complete control over the system.
In most cases, a process may be considered to have a single uid and gid associated with it. Those are the IDs that are used for most system security purposes (such as assigning ownerships to newly created files). The system calls that modify a process ownership are discussed later in this chapter.
As Unix developed, restricting processes to a single group turned out to create new difficulties. Users involved in multiple projects had to explicitly switch their gid when they wanted to access files whose access was restricted to members of different groups.
Supplemental groups were introduced in BSD 4.3 to eliminate this problem. Although every process still has its primary gid (which it uses as the gid for newly created files, for example), it also belongs to a set of supplemental groups. Security checks that used to ensure that a process belonged to a particular group (and only that group) now allow access as long as the group is one of the supplemental groups to which the process belongs. The sysconf() macro _SC_NGROUPS_MAX specifies how many supplemental groups a process may belong to. (See Chapter 6, page 54, for details on sysconf().) Under Linux 2.4 and earlier, _SC_NGROUPS_MAX is 32; under Linux 2.6 and later, _SC_NGROUPS_MAX is 65536. Do not use a static array to store supplemental groups; instead, dynamically allocate memory, taking into account the return value of sysconf(_SC_NGROUPS_MAX). Older code may use the NGROUPS_MAX macro to determine how many supplemental groups are supported by a system; using this macro does not function correctly when the code is compiled in one environment and used in another.
Setting the list of groups for a process is done through the setgroups() system call and may be done only by processes running with root permissions.
int setgroups(size_t num, const gid_t * list);
The list parameter points to an array of num gids. The process's supplemental group list is set to the gids listed in the list array.
The getgroups() function lets a process get a list of the supplemental groups to which it belongs.
int getgroups(size_t num, gid_t * list);
The list must point to an array of gid_t, which is filled in with the process's supplemental group list, and num specifies how many gid_ts list can hold. The getgroups() system call returns -1 on error (normally, if list is not large enough to hold the supplemental group list) or the number of supplemental groups. As a special case, if num is 0, getgroups() returns the number of supplemental groups for the process.
Here is an example of how to use getgroups():
gid_t *groupList; int numGroups; numGroups = getgroups(0, groupList); if (numGroups) { groupList = alloca(numGroups * sizeof(gid_t)); getgroups(numGroups, groupList); }
A more complete example of getgroups() appears in Chapter 28.
Thus, a process has a uid, a primary gid, and a set of supplemental groups associated with it. Luckily, this is as much as most programmers ever have to worry about. There are two classes of programs that need very flexible management of uids and gids, however: setuid/setgid programs and system daemons.
System daemons are programs that are always running on a system and perform some action at the request of an external stimulus. For example, most World Wide Web (http) daemons are always running, waiting for a client to connect to it so that they can process the client's requests. Other daemons, such as the cron daemon (which runs requests periodically), sleep until a time when they need to perform actions. Most daemons need to run with root permissions but perform actions at the request of a user who may be trying to compromise system security through the daemon.
The ftp daemon is a good example of a daemon that needs flexible uid handling. It initially runs as root and then switches its uid to the uid of the user who logs into it (most systems start a new process to handle each ftp request, so this approach works quite well). This leaves the job of validating file accesses to the kernel where it belongs. Under some circumstances, however, the ftp daemon must open a network connection in a way that only root is allowed to do (see Chapter 17 for details on this). The ftp daemon cannot simply switch back to root, because user processes cannot give themselves superuser access (with good reason!), but keeping the root uid rather than switching to the user's uid would require the ftp daemon to check all file-system accesses itself. The solution to this dilemma has been applied symmetrically to both uids and primary gids, so we just talk about uids here.
A process actually has three uids: its real uid, saved uid, and effective uid. [2] The effective uid is used for all security checks and is the only uid of the process that normally has any effect.
The saved and real uids are checked only when a process attempts to change its effective uid. Any process may change its effective uid to the same value as either its saved or real uid. Only processes with an effective uid of 0 (processes running as root) may change their effective uid to an arbitrary value.
Normally, a process's effective, real, and saved uid's are all the same. However, this mechanism solves the ftp daemon's dilemma. When it starts, all its IDs are set to 0, giving it root permissions. When a user logs in, the daemon sets its effective uid to the uid of the user, leaving both the saved and real uids as 0. When the ftp daemon needs to perform an action restricted to root, it sets its effective uid to 0, performs the action, and then resets the effective uid to the uid of the user who is logged in.
Although the ftp daemon does not need the saved uid at all, the other class of programs that takes advantage of this mechanism, setuid and setgid binaries, does use it.
The passwd program is a simple example of why setuid and setgid functionality was introduced. The passwd program allows users to change their passwords. User passwords are usually stored in /etc/passwd. Only the root user may write to this file, preventing other users from changing user information. Users should be able to change their own passwords, however, so some way is needed to give the passwd program permission to modify /etc/passwd.
To allow this flexibility, the owner of a program may set special bits in the program's permission bits (see pages 158-162 for more information), which tells the kernel that whenever the program is executed, it should run with the same effective uid (or gid) as the user who owns the program file, regardless of what user runs the program. These programs are called setuid (or setgid) executables.
Making the passwd program owned by the root user and setting the setuid bit in the program's permission bits lets all users change their passwords. When a user runs passwd, the passwd program is run with an effective user ID of 0, allowing it to modify /etc/passwd and change the user's password. Of course, the passwd program has to be carefully written to prevent unintended side effects. Setuid programs are a popular target for system intruders, as poorly written setuid programs provide a simple way for users to gain unauthorized permissions.
There are many cases in which setuid programs need their special permissions only for short periods of time and would like to switch back to the uid of the actual user for the remainder of the time (like the ftp daemon). As setuid programs have only their effective uid set to the uid of the program's owner, they know the uid of the user who actually ran them (the saved uid), making switching back simple. In addition, they may set their real uid to the setuid value (without affecting the saved uid) and regain those special permissions as needed. In this situation, the effective, saved, and real uid's work together to make system security as simple as possible.
Unfortunately, using this mechanism can be confusing because POSIX and BSD take slightly different approaches, and Linux supports both. The BSD solution is more full-featured than the POSIX method. It is accessed with the setreuid() function.
int setreuid(uid_t ruid, uid_t euid);
The real uid of the process is set to ruid and the effective uid of the process is set to euid. If either of the parameters is -1, that ID is not affected by this call.
If the effective uid of the process is 0, this call always succeeds. Otherwise, the IDs may be set to either the saved uid or the real uid of the process. Note that this call never changes the saved uid of the current process; to do that, use POSIX's setuid() function, which can modify the saved uid.
int setuid(uid_t euid);
As in setreuid(), the effective uid of the process is set to euid as long as euid is the same as either the saved or real uid of the process, or as long as the process's effective uid at the time of the call is 0.
When setuid() is used by a process whose effective uid is 0, all the process's uids are changed to euid. Unfortunately, this makes it impossible for setuid() to be used in a setuid root program that needs to temporarily assume the permissions of another uid, because after calling setuid(), the process cannot recover its root privileges.
Although the ability to switch uids does make it easier to write code that cannot be tricked into compromising system security, it is not a panacea. There are many popular methods for tricking programs into executing arbitrary code [Lehey, 1995]. As long as either the saved or real uid of a process is 0, such attacks can easily set the effective uid of a process to 0. This makes switching uids insufficient to prevent serious vulnerabilities in system programs. However, if a process can give up any access to root privileges by setting its effective, real, and saved IDs to non-0 values, doing so limits the effectiveness of any attack against it.
10.2.3 The filesystem uid
In highly specialized circumstances, a program may want to keep its root permissions for everything but file-system access, for which it would rather use a user's uid. The user-space NFS server originally used by Linux illustrates the problem that can occur when a process assumes a user's uid. Although the NFS server in the past used setreuid() to switch uids while accessing the file system, doing so allowed the user whose uid the NFS server was assuming to kill the NFS server. After all, for a moment that user owned the NFS server process. To prevent this type of problem, Linux uses the filesystem uid (fsuid) for file-system access checks.
Whenever a process's effective uid is changed, the process's fsuid is set to the process's new effective user ID, making the fsuid transparent to most applications. Those applications that need the extra features provided by the separate fsuid must use the setfsuid() call to explicitly set the fsuid.
int setfsuid(uid_t uid);
The fsuid may be set to any of the current effective, saved, or real uids of the process. In addition, setfsuid() succeeds if the current fsuid is being retained or if the process's effective uid is 0.
10.2.4 User and Group ID Summary
Here is a summary of all the system calls that modify the access permissions of a running process. Most of the functions listed here that pertain to user IDs have been discussed in detail earlier in this chapter, but those related to group IDs have not. As those functions mirror similar functions that modify user IDs, their behavior should be clear.
All of these functions return -1 on error and 0 on success, unless otherwise noted. Most of these prototypes are in <unistd.h>. Those that are located elsewhere are noted below.
int setreuid(uid_t ruid, uid_t euid)
Sets the real uid of the current process to ruid and the effective uid of the process to euid. If either of the parameters is -1, that uid remains unchanged.
int setregid(gid_t rgid, gid_t egid)
Sets the real gid of the current process to rgid and the effective gid of the process to egid. If either of the parameters is -1, that gid remains unchanged.
int setuid(uid_t uid)
If used by a normal user, sets the effective uid of the current process to uid. If used by a process with an effective uid of 0, it sets the real, effective, and saved uids to uid.
int setgid(gid_t gid)
If used by a normal user, sets the effective gid of the current process to gid. If used by a process with an effective gid of 0, sets the real, effective, and saved gids to gid.
int seteuid(uid_t uid)
Equivalent to setreuid(-1, uid).
int setegid(gid_t gid)
Equivalent to setregid(-1, gid).
int setfsuid(uid_t fsuid)
Sets the fsuid of the current process to fsuid. It is prototyped in <sys/fsuid.h>. It returns the previous fsuid.
int setfsgid(gid_t fsgid)
Sets the fsgid of the current process to fsgid. It is prototyped in <sys/fsuid.h>. It returns the previous fsgid.
int setgroups(size_t num, const gid_t * list)
Sets the supplemental groups for the current process to the groups specified in the array list, which must contain num items. The sysconf() macro _SC_NGROUPS_MAX tells how many groups may be in the list (likely 32 or 65536, depending on the Linux versions you are currently running). The setgroups() function is prototyped in <grp.h>.
uid_t getuid()
Returns the real uid of the process.
uid_t geteuid()
Returns the effective uid of the process.
gid_t getgid()
Returns the real gid of the process.
gid_t getegid()
Returns the effective gid of the process.
size_t getgroups(size_t size, gid_t list[])
Returns the current set of supplemental groups for the current process in the array list.size tells how many gid_ts the list can hold. If the list is not big enough to hold all of the groups, -1 is returned and errno is set to EINVAL. Otherwise, the number of groups in the list is returned. If size is 0, the number of groups in the list is returned, but list is not affected. The getgroups() function is prototyped in <grp.h>.