Privilege Model


In the UNIX access control model, each process has three associated user IDs:

  • Real user ID The ID of the user who started the process (that is, the user ID of the user who initially ran the program).

  • Saved set-user-ID If a program is configured as setuid, it runs as the user that owns the file when it's called, regardless of who called it. The ID of this user, the set-user-ID, is saved here.

  • Effective user ID The actual ID used when permission checks are done in the kernel. The effective user ID tells you the current privileges of the process. If a program wants to change its privileges, it changes its effective user ID to the ID of the user with the desired privileges. If a program has an effective user ID of 0, it has full superuser privileges to the system.

In general, a process is allowed to change its effective user ID to its real user ID or saved set-user-ID. In this way, processes can toggle their effective permissions between the user who started the program and the more privileged set-user-ID. Note that a program with the superuser's effective user ID doesn't have to obey many rules, so the semantics of how those programs manage their IDs are more subtle.

Each UNIX process also has multiple group IDs:

  • Real group ID The primary group ID of the user who called the process.

  • Saved set-group-ID If a program is configured as setgid, it runs as a member of a particular group. That group, the set-group-ID, is saved here.

  • Effective group ID One of the group IDs used when permission checks are done in the kernel. It's used with the supplemental group IDs when the kernel performs access control checks.

  • Supplemental group IDs Each process also maintains a list of groups the process has membership in. This list is used with the effective group ID when the kernel does permission checks of group permissions.

The group IDs mirror the user IDs as far as functionality, except supplemental groups are also considered in access control decisions. Note that having an effective group ID of 0usually the wheel groupdoes not grant any special privileges in the system. It gives you access commensurate with the privileges members of the wheel group have, but it doesn't give you any special consideration at the kernel level. (Caveat: There have been vague references to older UNIX systems where the kernel does give special consideration to group 0, but the authors never encountered such a system.)

When a process runs another program, the real user ID stays the same. The effective user ID also stays the same, unless the new program is setuid. The saved set-user-ID is replaced with the effective user ID of the new process when it starts. So if you temporarily drop privileges by setting your effective user ID equal to your real user ID and then run a new program with exec(), the elevated privileges stored in your saved set-user-ID aren't passed on to the new program.

Privileged Programs

There are basically three categories of programs in UNIX, described in the following sections, that manage privileges by manipulating their effective user and group IDs. We will explore each of them in this section.

Nonroot Setuid and Setgid Programs

The setuid and setgid programs allow normal users to perform actions that require privileges they don't have. For example, the wall program is used to broadcast a message to all users on a system. This program works by writing a message to each user's terminal device. Normally, a regular (non-root) user can't write directly to another user's terminal device, as this would allow users to spy on each other and interfere with one another's terminal sessions. So the wall program is usually installed as setgid tty, which means wall runs as a member of the group tty. All the terminal devices on a system belong to this tty group, and permissions are set up so that the terminal devices are group writeable. Therefore, the wall program can provide users with the ability to write to other user's terminal devices in a controlled, safe fashion.

Another example is the minicom programa text-based interface for interacting with a serial device, such as a modem. The administrator typically doesn't want to allow users to talk directly with serial device drivers, as this could lead to various attacks and reliability issues. One way some UNIX systems work around this requirement is by making the serial devices owned by the user uucp and configuring the minicom program to run setuid uucp. This way, when a normal user runs minicom, the program runs as the uucp user and has the privileges necessary to make use of serial devices.

So a process's effective permissions are determined by its effective user ID, its effective group ID, and its supplemental group IDs. Setuid programs start off running with their elevated privileges, so their effective user ID is equal to their saved set-user-ID. Setgid programs behave in the same fashion. At any point, these programs are allowed to switch their effective IDs to their real IDs to drop their privileges. If they want to regain their privileges, they can toggle their effective IDs back to their saved set-user-IDs.

These programs can permanently drop their privileges by changing their saved setIDs and effective IDs to be equal to their real IDs, so they can't toggle to the user ID with higher privileges.

Setuid Root Programs

Most setuid programs in UNIX environments are setuid root, meaning they run as the superuser when they are started. The rules for setuid root programs are a little different; when a process has an effective user ID of 0, it doesn't have to obey conventions for how it manipulates its associated user and group IDs. Also, the semantics of the ID management API functions change slightly, as explained shortly in "User ID Functions" and "Group ID Functions."

A good example of a setuid root program is the ping program. Ping needs the capability to use a raw socket, which requires root privileges. A raw socket can be used to spoof arbitrary network packets and retrieve certain types of raw network packets, so allowing nonprivileged users to create one would allow them to sniff traffic and forge data packets (generally considered rude in polite society). Therefore, this capability is limited to root users, and the ping program is configured as setuid root so that it can create a raw socket.

A setuid root program starts off with an effective user ID of 0, a saved set-user-ID of 0, and a real user ID corresponding to the user who started the program. Setuid root programs typically behave like other setuid and setgid programs, in that they manage privileges by toggling their effective user ID between their real user ID and saved set-user-ID. They permanently drop their privileges by setting all three IDs to the real user ID. However, they aren't required to obey these conventions when they're running as the superuser, so they could conceivably change their IDs in arbitrary ways.

Daemons and Their Children

In UNIX, daemons are long-running processes that provide system services (not unlike Windows service processes). They are usually started automatically by the system at boot time, or they are started by an administrator or a job-scheduling program. Daemons often run as the superuser so that they can perform privileged operations. A daemon running as root starts with an effective user ID of 0, a real user ID of 0, and a saved set-user-ID of 0. Its group membership corresponds to the root account's group membership, which equates to an effective group ID of 0, a real group ID of 0, a saved set-group-ID of 0, and membership in several administration-related supplementary groups.

Daemon programs often run other programs to handle required tasks, and these child programs are usually also started with root privileges. These daemons and their child processes might temporarily assume a normal user's identity to perform certain actions in a safe manner or to minimize the amount of time they're running with root privileges. To pull this off, the program typically changes its effective user ID to the user ID it's interested in assuming. However, first the program needs to change its effective group ID to an appropriate group ID and alter its supplemental group list to contain appropriate groups. As long as the program leaves its saved set-user-ID or real user ID set to 0, it can regain its superuser privileges later.

A program running as root might also want to fully drop its root privileges and assume the role of a normal user permanently. To fully drop root privileges, the program must set all three of its user IDs and group IDs to the correct IDs for the user that it wants to become.

A good example of a program like this is the login program, which authenticates users on a local terminal or remotely via the telnet service. This login program displays the login prompt and waits for the user to try to log in to the machine. At this point in time, the login program is running as root, because it needs access to system authentication databases. If the user authenticates successfully, login assumes the identity of that user before it opens a command shell, such as /bin/sh It does this by initializing its group IDs based on the user's group membership and then setting all three of its user IDs to the user's ID.

User ID Functions

The setuid(), seteuid(), setreuid(), and setresuid() functions are used to manipulate the three user IDs associated with a process. These functions have slightly different semantics on different UNIX OSs, and these differences can lead to security problems in applications that are intended to be portable across UNIX variants. This section introduces the user ID functions exposed by the standard C library and notes system-specific idiosyncrasies when relevant.

Note

You can find an excellent paper on the nuances of the setuid() family of functions at www.csl.sri.com/users/ddean/papers/usenix02.pdf.


The seteuid() Function

The effective user ID associated with a process is changed with the seteuid() function:

int seteuid(uid_t euid);


This function, summarized in Table 9-1, has a single parameter, euid, which indicates the desired UID that the effective user ID should be set to. If a process is running with superuser privileges (effective user ID of 0), it can set the effective user ID to any arbitrary ID. Otherwise, for non-root processes, it can toggle the effective user ID between the saved set-user-ID and the real user ID. Programs use seteuid() to temporarily change their privileges.

Table 9-1. Seteuid() Behavior

Privileged

OS

Notes

Yes

General

Changes the effective user ID to any arbitrary value.

Yes

Linux libc

glibc 2.1 and earlier

If the new ID isn't the real user ID or the saved set-user-ID, the saved set-user-ID is updated along with the effective user ID. seteuid() is equivalent to setreuid(-1, euid).

No

General

Toggles the effective user ID between the real user ID, the effective user ID, and the saved set-user-ID.

No

NetBSD FreeBSD

Toggles the effective user ID between the real user ID and the saved set-user-ID.


Take a closer look at this nonprivileged case: Say a user named admin has a user ID of 1000. The admin user runs a file owned by the bin user (typically user ID 1) and the saved set-user-ID bit is set on the file. When the program runs, the process has the following IDs:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1 - bin


The program can do anything the bin user is allowed to do. If the program wants to temporarily relinquish these privileges, it can use seteuid(1000). It then has the following privileges:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1000 - admin


If the program wants to regain its privileges, it uses seteuid(1). It then has these associated IDs:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1 - bin


For the sake of completeness, say you have a program running as root with the following IDs:

real user ID - 0 - root saved set-user-ID - 0 - root effective user ID - 0 - root


This program can call a seteuid() with any value it likes, including values for user IDs that don't exist in the system, and the kernel allows it. Using a seteuid(4242) would result in the following IDs:

real user ID - 0 - root saved set-user-ID - 0 - root effective user ID - 4242 - arbitrary


Warning

There's one caveat with seteuid() that should never be an issue in production code, but it's worth mentioning. On Linux systems with libc or glibc versions before 2.1, if you are the superuser and change the effective user ID to an ID that isn't the real user ID or the saved set-user-ID, the saved set-user-ID is changed along with the effective user ID. So if you're root and all three of your IDs are 0, and you use a seteuid(4242) on a Linux glibc 2.0 system, the process would have the following IDs:

real user ID - 0 - root saved set-user-ID - 4242 - arbitrary effective user ID - 4242 - arbitrary



The setuid() Function

The behavior exhibited by the setuid() function has evolved and mutated over time, with subtle variances surfacing in different implementations across divergent UNIX systems. It has the following prototype:

int setuid(uid_t uid);


The uid parameter is used to specify a new effective user ID to be associated with the calling process. This function will also change both the real user ID and saved set-user-ID, contingent upon the privileges the calling process is running with and the UNIX variant that the process is running on (see Table 9-2). For processes running with superuser privileges, setuid() sets all three of a process's user IDs to the specified argument. For example, if a process's effective user ID is 0, a setuid(12345) sets the real user ID, saved set-user-ID, and effective user ID to 12345. setuid() is mainly used for permanently assuming the role of a user, usually for the purposes of dropping privileges.

Table 9-2. Setuid() Behavior

Privileged

OS

Notes

Yes

General

Real user ID, effective user ID, and saved set-user-ID are all set to the new value.

No

Linux

Solaris

You can specify the real user ID or the saved set-user-ID. The effective user ID is updated; works much like seteuid().

No

OpenBSD

You can specify the real user ID, the saved set-user-ID, or the effective user ID. If the specified value is equal to the the current effective user ID, the real user ID and saved set-user-ID are also updated. Otherwise, it works like seteuid(), just updating the effective user ID.

No

NetBSD

You can specify only the real user ID. The real user ID, effective user ID, and saved set-user-ID are all set to the specified value.

No

FreeBSD

You can specify the real user ID or the effective user ID. The real user ID, effective user ID, and saved set-user-ID are set to the specified value.


If the process isn't running as the superuser, setuid() has a behavior that varies across different flavors of UNIX. UNIX variants fall into two basic camps. The first camp believes that setuid() should work just like seteuid() when dealing with nonsuperuser processes. Linux, Solaris, and OpenBSD fall roughly into this camp. The second camp says that setuid() should work in a fashion consistent with how it works for superuser programs, so it should drop all privileges if the user requests a setuid() to the real user ID. FreeBSD and NetBSD belong in this camp.

Say the admin user runs a set-user-ID bin file:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1 - bin


In Linux and Solaris, setuid() behaves exactly like seteuid() when the effective user ID isn't the superuser's. You can specify the real user ID or saved set-user-ID as the argument, and setuid() updates the process's effective user ID. So in the preceding case, the two potentially valid calls are setuid(1000) and setuid(1), both of which would change only the effective user ID. So if you use setuid(1000), the IDs would change as follows:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1000 - admin


If you then use setuid(1), you have this result:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1 - bin


OpenBSD allows you to use setuid() on the real user ID, the saved set-user-ID, or the effective user ID. Its behavior is a little different; if you use the current effective user ID as the argument, setuid() in OpenBSD sets all three IDs to that user ID. However, if you use setuid() to toggle between the saved set-user-ID and effective user ID, as you would in Linux or Solaris, the function behaves like seteuid(). The basic idea is that if you repeat the setuid() call, you can make the permission change permanent. For example, say you have this set of IDs :

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1 - bin


If you use setuid(1), you effectively assume the bin user's identity, and all three IDs are changed to 1. If you use setuid(1000), however, you toggle your effective user ID, and the result is as follows:

real user ID - 1000 - admin saved set-user-ID - 1 - bin effective user ID - 1000 - admin


From here, you could use another setuid(1000) and cause the program to fully assume the admin user's identity, or you could toggle back to bin by using setuid(1).

FreeBSD allows you to use setuid() on the real user ID or effective user ID, and the result causes all three user IDs to be set. So in the preceding example, you could use setuid(1000) to set all three IDs to 1000, or you could use setuid(1) to set all three IDs to 1. FreeBSD always lets you fully drop privileges back to the real user ID. However, it also lets you use setuid() to confirm your current effective user ID and have it become your new user ID across all three IDs.

NetBSD allows you to use setuid() only with the real user ID, and the result causes all three user IDs to be set. In essence, the NetBSD version of setuid() allows only a nonsuperuser process to fully drop privileges back to the real user ID. So in the preceding example, if you use a setuid(1000), you would end up with all three IDs being 1000.

All these details are great, but what's the bottom line for auditing code that uses setuid()? Basically, if the program has an effective user ID of 0, and the developer is using it to fully drop user privileges, everything is probably fine. If the program doesn't have an effective user ID of 0, setuid() is probably the wrong function for trying to manipulate privileges. If developers try to rely on it to fully drop privileges, they are burned by the saved set-user-IDs persisting in Linux, OpenBSD, and Solaris. If they try to rely on it just to change the effective user ID, they inadvertently throw away credentials in FreeBSD and NetBSD.

The setresuid() Function

The setresuid() function is used to explicitly set the real, effective, and saver set-user-IDs. This function has the following prototype:

int setresuid(uid_t ruid, uid_t euid, uid_t suid);


The ruid, euid, and suid parameters indicate new values for the real user ID, effective user ID, and saved set-user-ID attributes respectively. The caller can place a -1 in any of the arguments, and the kernel fills in the current value of the corresponding UID. Superusers can set the IDs to any value they want. A nonsuperuser process can set any of the IDs to the value of any of the three current IDs. This function has clear semantics and is implemented the same way across the UNIX variants that provide it. It's currently available on Linux, FreeBSD, HPUX, and newer versions of OpenBSD. This is summarized in Table 9-3.

Table 9-3. Setresuid() Behavior

Privileged

OS

Notes

Yes

Linux

FreeBSD

HPUX

OpenBSD 3.3 and later.

Real user ID, effective user ID, and saved set-user-ID are set to the specified values or filled in from current values

No

Linux

FreeBSD

HPUX

OpenBSD3.3 and later

Any of the three values can be set to any of the current real user ID, effective user ID, or saved set-user-ID. Other values can be filled in by the kernel.


The setreuid() Function

The setreuid() function sets both the real user ID and effective user ID of a process. It works as shown:

int setreuid(uid_t ruid, uid_t euid);


The setreuid() takes a ruid parameter to indicate what the real userID should be set to, and an euid function to indicate what the effective user ID should be set to. If you provide an argument of -1 for ruid or euid, the function fills in the current value from the process. The semantics of this function are explored in Table 9-4.

Table 9-4. Setreuid() Behavior

Privileged

OS

Notes

Yes

NetBSD

Real user ID and effective user ID can be set to arbitrary values. Saved set-user-ID is set to the effective user ID if the real user ID value is specified, even if it isn't changed.

Yes

FreeBSD

Solaris

Real user ID and effective user ID can be set to arbitrary values. Saved set-user-ID is set to the effective user ID if the real user ID is specified or the effective user ID doesn't equal the real user ID.

Yes

Linux

Real user ID and effective user ID can be set to arbitrary values. Saved set-user-ID is set to the effective user ID if the real user ID is specified or the effective user ID is specified and its new value doesn't equal the real user ID.

Yes

OpenBSD 3.3 and later

Real user ID and effective user ID can be set to arbitrary values. Saved set-user-ID is set to the effective user ID if the real user ID is specified and the real user ID is actually changed or the effective user ID doesn't equal the saved user ID.

Yes

OpenBSD before 3.3

Effectively unsupported. Behavior is provided through compatibility lib with rather complex, nonconfirming behavior.

No

NetBSD

Real user ID can be set to real user ID or effective user ID. Effective user ID can be set to real user ID, effective user ID, or saved set-user-ID. Saved set-user-ID is set to the effective user ID if the real user ID value is specified, even if it isn't changed.

No

FreeBSD

Real user ID can be set to real user ID or saved user ID. Effective user ID can be set to real user ID, effective user ID, or saved set-user-ID. Saved set-user-ID is set to the effective user ID if the real user ID is specified or the effective user ID doesn't equal the real user ID.

No

Solaris

Real user ID can be set to real user ID or effective user ID. Effective user ID can be set to real user ID, effective user ID, or saved set-user-ID. Saved set-user-ID is set to the effective user ID if the real user ID is specified or the effective user ID doesn't equal the real user ID.

No

Linux

Real user ID can be set to real user ID or effective user ID. Effective user ID can be set to real user ID, effective user ID, or saved set-user-ID. Saved set-user-ID is set to the effective user ID if the real user ID is specified or the effective user ID is specified and its new value doesn't equal the real user ID.

No

OpenBSD 3.3 and later

Real user ID can be set to real user ID, saved set-user-ID or effective user ID. Effective user ID can be set to real user ID, effective user ID, or saved set-user-ID. Saved set-user-ID is set to the effective user ID if the real user ID is specified and the real user ID is actually changed or the effective user ID doesn't equal the saved user ID.

No

OpenBSD before 3.3

Effectively unsupported. Behavior is provided through compatibility lib with rather complex, nonconfirming behavior.


If you're the superuser, you can set the user ID and effective user ID to any value you like. If you aren't the superuser, allowed behaviors vary among OSs, but you can typically change the real user ID to the effective user ID. You can change the effective user ID to the real user ID, the effective user ID, or the saved set-user-ID.

After it modifies the real user ID and the effective user ID, the setreuid() function attempts to determine whether it should update the saved set-user-ID to reflect the value of the new effective user ID. It varies a bit among OSs, but generally, if the real user ID is changed or the effective user ID is changed to something other than the real user ID, the saved set-user-ID is set to be equal to the effective user ID.

This API is quite cumbersome and there are issues with it having variances across multiple platforms, which you can definitely see in Table 9-4. Linux, NetBSD, and Solaris implement similar algorithms, but FreeBSD lets a nonsuperuser process change the real user ID to the saved set-user-ID as opposed to the effective user ID, which is slightly different. Versions of OpenBSD before 3.3 effectively didn't support this function; it was provded through a compatibility mode that was incompatible with other UNIX implementations. Versions after 3.3 implement it, but it has slightly different semantics than the other UNIX implementations.

setreuid() isn't pretty, but it's important for one notable situation. If a program is managing two user IDs as its real user ID and saved set-user-ID, but neither is the superuser, it can prove difficult for that program to fully drop one set of privileges. Linux, FreeBSD, HPUX, and more recent OpenBSD builds can make use of the setresuid() function, which has a clean and simple interface. Solaris and certain versions of the BSDs, however, don't have access to this function. For a more cross-platform solution, developers can use the setreuid(getuid(),getuid()) idiom, which should work on all modern UNIX implementations, with the notable exception of older versions of OpenBSD. Before OpenBSD imported the setresuid() function and rewrote the setreuid() function, the only straightforward way for a nonprivileged program to clear the saved set-user-ID was to call the setuid() function when the effective user ID is set to the real user ID. This can be accomplished by calling setuid(getuid()) twice in a row.

Group ID Functions

The setgid(), setegid(), setregid(), setresgid(), setgroups(), and initgroups() functions are used to manipulate the group IDs associated with a process. Like the user ID functions, these functions have slightly different semantics on the different UNIX OSs. The following sections introduce the group ID functions.

Warning

The group ID functions, like the user ID functions, have different behaviors if the process is running as the superuser, which means an effective user ID of 0. An effective group ID of 0, however, doesn't give a process any special kernel-level privileges.


The setegid() Function

The setegid() function is used to change the effective group ID associated with the current process. It's prototype is

int setegid(gid_t egid);


It behaves like its user ID counterpart, the seteuid() function, in that it's used to toggle the effective group ID between the saved set-group-ID and the real group ID. Similar to seteuid(), if the process is running with superuser privileges, it can set the effective group ID to any arbitrary value.

The setgid() Function

The setgid() function changes group IDs associated with a process, and is equally nuanced as its counterpart setuid(). It works like this:

int setgid(gid_t gid);


setgid() takes a single parameter, gid, which it uses to set the effective group ID, and possibly also the saved set-group-ID and real group ID. If it's run from a process running with superuser privileges, it sets the effective group ID, the saved set-group-ID, and the real group ID to the same value. When the process isn't running as the superuser, setgid() has varying behavior that closely tracks the different behaviors discussed for setuid().

The setresgid() Function

The setresgid() function is used to change the real group ID, effective group ID, and saved set-group-ID of a process. It has the following prototype:

int setresgid(gid_t rgid, gid_t egid, gid_t sgid);


setresgid() behaves in much the same way that setresuid() does, except that it manipulates group IDs for a process rather than user IDs. The caller can provide -1 for any of the arguments, and the kernel fills in the current value. Superusers can set any of the group IDs to any value they want. A nonsuperuser process can set any of the IDs to the value of any of the three current IDs. This function has clear semantics and is implemented the same across UNIX variants that provide it.

The setregid() Function

The setregid() function can be used to modify the real group ID and effective group ID associated with a process. It works as shown:

int setregid(gid_t rgid, gid_t egid);


setregid() lets you specify the values you want for your real group ID and effective group ID through the use of the rgid and egid parameters respectively. If you provide an argument of -1 for rgid or egid, it fills in the current value from the process. This function behaves like its counterpart, setreuid().

The setgroups() Function

A process can set its supplementary groups using the setgroups() function, as shown:

int setgroups(int ngroups, const gid_t *gidset);


The setgroups() function takes two parameters; the ngroups parameter indicates how many supplemental groups the process will have and the gidset paramaeter points to an array of group IDs that has ngroup members. This function can be called only by a process with an effective user ID of 0.

The initgroups() Function

As an alternative to setgroups(), processes can set their supplementary groups using initgroups(), which has the following prototype:

int initgroups(const char *name, gid_t basegid);


initgroups() is a convenient alternative to setgroups() because it saves the calling application from having to find out the groups that a particular user is a member of in order to correctly establish the process's supplementary group list. The name parameter indicates a user account whose group memberships are to be enumerated and set as the calling process's supplementary group list. The basegid GID is also added to the supplementary group list, and is typically the primary GID of the user specified by the name parameter. Like setgroups(), it can be performed only by a process with an effective user ID of 0.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net