Chapter 11. Permissions and User and Group ID Numbers

In this chapter

  • 11.1 Checking Permissions page 404

  • 11.2 Retrieving User and Group IDs page 407

  • 11.3 Checking as the Real User: access() page 410

  • 11.4 Checking as the Effective User: euidaccess() (GLIBC) page 412

  • 11.5 Setting Extra Permission Bits for Directories page 412

  • 11.6 Setting Real and Effective IDs page 415

  • 11.7 Working with All Three IDs: getresuid() and setresuid() (Linux) page 421

  • 11.8 Crossing a Security Minefield: Setuid root page 422

  • 11.9 Suggested Reading page 423

  • 11.10 Summary page 424

  • Exercises page 426

Linux, following Unix, is a multiuser system. Unlike most operating systems for personal computers,[1] in which there is only one user and whoever is physically in front of the computer has complete control, Linux and Unix separate files and processes by the owners and groups to which they belong. In this chapter, we examine permission checking and look at the APIs for retrieving and setting the owner and group identifiers.

Checking Permissions

As we saw in Section 5.4.2, “Retrieving File Information,” page 141, the filesystem stores a file’s user identifier and group identifier as numeric values; these are the types uid_t and gid_t, respectively. For brevity, we use the abbreviations UID and GID for “user identifier” and “group identifier.”

Every process has several user and group identifiers associated with it. As a simplification, one particular UID and GID are used for permission checking; when the UID of a process matches the UID of a file, the file’s user permission bits dictate what the process can do with the file. If they don’t match, the system checks the GID of the process against the GID of the file; if they match, the group permissions apply; otherwise the “other” permissions apply.

Besides files, the UID controls how one process can affect another by sending it a signal. Signals are described in Chapter 10, “Signals,” page 347.

Finally, the superuser, root, is a special case. root is identified by a UID of 0. When a process has UID 0, the kernel lets it do whatever it wants to: read, write, or remove files, send signals to arbitrary processes, and so on. (POSIX is more obtuse about this, referring to processes with “appropriate privilege.” This language in turn has filtered down into the GNU/Linux manpages and the GLIBC online Info manual. Some operating systems do separate privilege by user, and Linux is moving in this direction as well. Nevertheless, in current practice, “appropriate privilege” just means processes with UID 0.)

Real and Effective IDs

UID and GID numbers are like personal identification. Sometimes you need to carry more than one bit of identification around with you. For instance, you may have a driver’s license or government identity card.[2] In addition, your university or company may have issued you an identification card. Such is the case with processes too; they carry multiple UID and GID numbers around with them, as follows:

Real user ID

  • The UID of the user that forked the process.

Effective user ID

  • The UID used for most permission checking. Most of the time, the effective and real UIDs are the same. The effective UID can be different from the real one at startup if the setuid bit of the executable program’s file is set and the file is owned by someone other than the user running the program. (More details soon.)

Saved set-user ID

  • The original effective UID at program startup (after the exec). This plays a role in permission checking when a process needs to swap its real and effective UIDs back and forth. This concept came from System V.

Real group ID

  • The GID of the user that created the process, analogous to the real UID.

Effective group ID

  • The GID used for permission checking, analogous to the effective UID.

Saved set-group ID

  • The original effective GID at program startup, analogous to the saved set-user ID.

Supplemental group set

  • 4.2 BSD introduced the idea of a group set. Besides the real and effective GIDs, each process has some set of additional groups to which it simultaneously belongs. Thus, when permission checking is done for a file’s group permissions, not only does the kernel check the effective GID, but it also checks all of the GIDs in the group set.

Any process can retrieve all of these values. A regular (non-superuser) process can switch its real and effective user and group IDs back and forth. A root process (one with an effective UID of 0) can also set the values however it needs to (although this can be a one-way operation).

Setuid and Setgid Bits

The setuid and setgid bits[3] in the file permissions cause a process to acquire an effective UID or GID that is different from the real one. These bits are applied manually to a file with the chmod command:

$ chmod u+s myprogram                Add setuid bit
$ chmod g+s myprogram                Add setgid bit
-rwsr-sr-x    1 arnold   devel        4573 Oct 9 18:17 myprogram
$ ls -l myprogram

The s character where an x character usually appears indicates the presence of the setuid/setgid bits.

As mentioned in Section 8.2.1, “Using Mount Options,” page 239, the nosuid option to mount for a filesystem prevents the kernel from honoring both the setuid and setgid bits. This is a security feature; for example, a user with a home GNU/Linux system might handcraft a floppy with a copy of the shell executable made setuid to root. But if the GNU/Linux system in the office or the lab will only mount floppy filesystems with the nosuid option, then running this shell won’t provide root access.[4]

The canonical (and probably overused) motivating example of a setuid program is a game program. Suppose you’ve written a really cool game, and you wish to allow users on the system to play it. The game keeps a score file, listing the highest scores.

If you’re not the system administrator, you can’t create a separate group of just those users who are allowed to play the game and thus write to the score file. But if you make the file world-writable so that anyone can play the game, then anyone can also cheat and put any name at the top.

However, by making the game program setuid to yourself, users running the game have your UID as their effective UID. The game program can then open and update the file as needed, but arbitrary users can’t come along and edit it. (You also open yourself up to most of the dangers of setuid programming; for example, if the game program has a hole that can be exploited to produce a shell running as you, all your files are available for deletion or change. This is a justifiably scary thought.)

The same logic applies to setgid programs, although in practice setgid programs are much less used than setuid ones. (This is too bad; many things that are done with setuid root programs could easily be done with setgid programs or programs that are setuid to a regular user, instead.[5])

Retrieving User and Group IDs

Getting the UID and GID information from the system is straightforward. The functions are as follows:

#include <unistd.h>                                                    POSIX

uid_t getuid(void);                        Real and effective UID
uid_t geteuid(void);

gid_t getgid(void);                        Real and effective GID
gid_t getegid(void);

int getgroups(int size, gid_t list[]);     Supplemental group list

The functions are:

uid_t getuid(void)

  • Returns the real UID.

uid_t geteuid(void)

  • Returns the effective UID.

gid_t getgid(void)

  • Returns the real GID.

gid_t getegid(void)

  • Returns the effective GID.

int getgroups (int size, gid_t list[])

  • Fills in up to size elements of list from the process’s supplemental group set. The return value is the number of elements filled in or -1 if there’s an error. It is implementation defined whether the effective GID is also included in the set.

  • On POSIX-compliant systems, you can pass in a size value of zero; in this case, getgroups() returns the number of groups in the process’s group set. You can then use that value to dynamically allocate an array that’s big enough.

  • On non-POSIX systems, the constant NGROUPS_MAX defines the maximum necessary size for the list array. This constant can be found in <limits.h> on modern systems or in <sys/param.h> on older ones. We present an example shortly.

You may have noticed that there are no calls to get the saved set-user ID or saved set-group ID values. These are just the original values of the effective UID and effective GID. Thus, you can use code like this at program startup to obtain the six values:

uid_t ruid, euid, saved_uid;
gid_t rgid, egid, saved_gid;

int main(int argc, char **argv)
{
    ruid = getuid();
    euid = saved_uid = geteuid();

    rgid = getgid();
    egid = saved_gid = getegid();

    ...rest of program ...
}

Here is an example of retrieving the group set. As an extension, gawk provides awk-level access to the real and effective UID and GID values and the supplemental group set. To do this, it has to retrieve the group set. The following function is from main.c in the gawk 3.1.3 distribution:

1080  /* init_groupset --- initialize groupset */
1081
1082  static void
1083  init_groupset()
1084  {
1085  #if defined (HAVE_GETGROUPS) && defined (NGROUPS_MAX) && NGROUPS_MAX > 0
1086  #ifdef GETGROUPS_NOT_STANDARD
1087      /* For systems that aren't standards conformant, use old way. */
1088      ngroups = NGROUPS_MAX;
1089  #else
1090      /*
1091       * If called with 0 for both args, return value is
1092       * total number of groups.
1093       */
1094      ngroups = getgroups(0, NULL);
1095  #endif
1096      if (ngroups == -1)
1097          fatal (_("could not find groups: %s"), strerror(errno));
1098      else if (ngroups == 0)
1099          return;
1100
1101      /* fill in groups */
1102      emalloc(groupset, GETGROUPS_T *, ngroups * sizeof(GETGROUPS_T),
                  "init_groupset");
1103
1104      ngroups = getgroups(ngroups, groupset);
1105      if (ngroups == -1)
1106          fatal(_("could not find groups: %s"), strerror(errno));
1107   #endif
1108   }

The ngroups and groupset variables are global; their declaration isn’t shown. The GETGROUPS_T macro (line 1102) is the type to use for the second argument; it’s gid_t on a POSIX system, int otherwise.

Lines 1085 and 1107 bracket the entire function body; on ancient systems that don’t have group sets at all, the function has an empty body.

Lines 1086–1088 handle non-POSIX systems; GETGROUPS_NOT_STANDARD is defined by the configuration mechanism before the program is compiled. In this case, the code uses NGROUPS_MAX, as described earlier. (As late as 2004, such systems still exist and are in use; thankfully though, they are diminishing in number.)

Lines 1089–1094 are for POSIX systems, using a size parameter of zero to retrieve the number of groups.

Lines 1096–1099 do error checking. If the return value was 0, there aren’t any supplemental groups, so init_groupset() merely returns early.

Finally, line 1102 uses malloc() (through an error-checking wrapper macro, see Section 3.2.1.8, “Example: Reading Arbitrarily Long Lines,” page 67) to allocate an array that’s large enough. Line 1104 then fills in the array.

Checking as the Real User: access()

Most of the time, the effective and real UID and GID values are the same. Thus, it doesn’t matter that file-permission checking is performed against the effective ID and not the real one.

However, when writing a setuid or setgid application, you sometimes want to check whether a file operation that’s OK for the effective UID and GID is also OK for the real UID and GID. This is the job of the access() function:

#include <unistd.h>                                 POSIX

int access(const char *path, int amode);

The path argument is the pathname of the file to check the real UID and GID against. amode is the bitwise OR of one or more of the following values:

R_OK The real UID/GID can read the file.

W_OK The real UID/GID can write the file.

X_OK The real UID/GID can execute the file, or if a directory, search through the directory.

F_OK Check whether the file exists.

Each component in the pathname is checked, and on some implementations, when checking for root, access() might act as if X_OK is true, even if no execute bits are set in the file’s permissions. (Strange but true: In this case, forewarned is forearmed.) Linux doesn’t have this problem.

If path is a symbolic link, access() checks the file that the symbolic link points to.

The return value is 0 if the operation is permitted to the real UID and GID or -1 otherwise. Thus, if access() returns -1, a setuid program can deny access to a file that the effective UID/GID would otherwise be able to work with:

if (access("/some/special/file", R_OK|W_OK) < 0) {
    fprintf(stderr, "Sorry: /some/special/file: %s
", strerror(errno));
    exit(1);
}

At least with the 2.4 series of Linux kernels, when the X_OK test is applied to a filesystem mounted with the noexec option (see Section 8.2.1, “Using Mount Options,” page 239), the test succeeds if the file’s permissions indicate execute permission. This is true even though an attempt to execute the file will fail. Caveat emptor.

Note

While using access() before opening a file is proper practice, a race condition exists: The file being opened could be swapped out in between the check with access() and the call to open(). Careful programming is required, such as checking owner and permission with stat() and fstat() before and after the calls to access() and open().

For example, the pathchk program checks pathnames for validity. The GNU version uses access() to check that the directory components of given pathnames are valid. From the Coreutils pathchk.c:

244  /* Return 1 if PATH is a usable leading directory, 0 if not,
245     2 if it doesn't exist. */
246
247  static int
248  dir_ok (const char *path)
249  {
250    struct stat stats;
251
252    if (stat (path, &stats))                     Nonzero return = failure
253      return 2;
254
255    if (!S_ISDIR (stats.st_mode))
256      {
257        error (0, 0, _("`%s' is not a directory"), path);
258        return 0;
259      }
260
261    /* Use access to test for search permission because
262       testing permission bits of st_mode can lose with new
263       access control mechanisms. Of course, access loses if you're
264       running setuid. */
265    if (access (path, X_OK) != 0)
266      {
267        if (errno == EACCES)
268          error (0, 0, _("directory `%s' is not searchable"), path);
269        else
270          error (0, errno, "%s", path);
271        return 0;
272      }
273
274    return 1;
275  }

The code is straightforward. Lines 252–253 check whether the file exists. If stat() fails, then the file doesn’t exist. Lines 255–259 verify that the file is indeed a directory.

The comment on lines 261–264 explains the use of access(). Checking the st_mode bits isn’t enough: The file could be on a filesystem that was mounted read-only, on a remote filesystem, or on a non-Linux or non-Unix filesystem, or the file could have file attributes that prevent access. Thus, only the kernel can really tell if the access would work. Lines 265–272 do the check, with the error message being determined by the value of errno (lines 267–270).

Checking as the Effective User: euidaccess() (GLIBC)

GLIBC provides an additional function that works like access() but that checks according to the effective UID, GID and group set:

#include <unistd.h>                                     GLIBC

int euidaccess(const char *path, int amode);

The arguments and return value have the same meaning as for access(). When the effective and real UIDs are equal and the effective and real GIDs are equal, euidaccess() calls access() to do the test. This has the advantage that the kernel can test for read-only filesystems or other conditions that are not reflected in the file’s ownership and permissions.

Otherwise, euidaccess() checks the file’s owner and group values against those of the effective UID and GID and group set, using the appropriate permission bits. This test is based on the file’s stat() information.

If you’re writing a portable program but prefer to use this interface, it’s easy enough to extract the source file from the GLIBC archive and adapt it for general use.

Setting Extra Permission Bits for Directories

On modern systems, the setgid and “sticky” bits each have special meaning when applied to directories.

Default Group for New Files and Directories

In the original Unix system, when open() or creat() created a new file, the file received the effective UID and GID of the process creating it.

V7, BSD through 4.1 BSD, and System V through Release 3 all treated directories like files. However, with the addition of the supplemental group set in 4.2 BSD, the way new directories were created changed: new directories inherited the group of the parent directory. Furthermore, new files also inherited the group ID of the parent directory and not the effective GID of the creating process.

The idea behind having multiple groups and directories that work this way is to facilitate group cooperation. Each organizational project using a system would have a separate group assigned to it. The top-level directory for each project would be in that project’s group, and files for the project would all have group read and write (and if necessary, execute) permission. In addition, new files automatically get the group of the parent directory. By being simultaneously in multiple groups (the group set), a user could move among projects at will with a simple cd command, and all files and directories would maintain their correct group.

What happens on modern systems? Well, this is another of the few cases where it’s possible to have our cake and eat it too. SunOS 4.0 invented a mechanism that was included in System V Release 4; it is used today by at least Solaris and GNU/Linux. These systems give meaning to the setgid bit on the parent directory of the new file or directory, as follows:

Setgid bit on parent directory clear

  • New files and directories receive the creating process’s effective GID.

Setgid bit on parent directory set

  • New files and directories receive the parent directory’s GID. New directories also inherit the setgid bit being on.

(Until SunOS 4.0, the setgid bit on a directory had no defined meaning.) The following session shows the setgid bit in action:

$ cd /tmp                                                       Move to/tmp
$ ls -ld.                                                       Check its permissions
drwxrwxrwt    8 root   root     4096 Oct 16 17:40.
$ id                                                            Check out current groups
uid=2076(arnold) gid=42(devel) groups=19(floppy), 42(devel), 2076(arnold)
$ mkdir d1 ; ls -ld d1                                          Make a new directory
drwxr-xr-x    2 arnold  devel    4096 Oct 16 17:40 d1           Effective group ID inherited
$ chgrp arnold d1                                               Change the group
$ chmod g+s d1                                                  Add setgid bit
$ ls -ld d1                                                     Verify change
drwxr-sr-x    2 arnold  arnold   4096 Oct 16 17:40 d1
$ cd d1                                                         Change into it
$ echo this should have group arnold on it > f1                 Create a new file
$ ls -l f1                                                      Check permissions
-rw-r--r--    1 arnold  arnold     36 Oct 16 17:41 f1           Inherited from parent
$ mkdir d2                                                      Make a directory
$ ls -ld d2                                                     Check permissions
drwxr-sr-x    2 arnold  arnold   4096 Oct 16 17:51 d2           Group and setgid inherited

The ext2 and ext3 filesystems for GNU/Linux work as just shown. In addition they support special mount options, grpid and bsdgroups, which make the “use parent directory group” semantics the default. (The two names mean the same thing.) In other words, when these mount options are used, then parent directories do not have to have their setgid bits set.

The opposite mount options are nogrpid and sysvgroups. This is the default behavior; however, the setgid bit is still honored if it’s present. (Here, too, the two names mean the same thing.)

POSIX specifies that new files and directories inherit either the effective GID of the creating process or the group of the parent directory. However, implementations have to provide a way to make new directories inherit the group of the parent directory. Furthermore, the standard recommends that applications not rely on one behavior or the other, but in cases where it matters, applications should use chown() to force the ownership of the new file or directory’s group to the desired GID.

Directories and the Sticky Bit

 

“Sherman, set the wayback machine for 1976.”

 
 --Mr.Peabody

The sticky bit originated in the PDP-11 versions of Unix and was applied to regular executable files.[6] This bit was applied to programs that were expected to be heavily used, such as the shell and the editor. When a program had this bit set, the kernel would keep a copy of the program’s executable code on the swap device, from which it could be quickly loaded into memory for reuse. (Loading from the filesystem took longer: The image on the swap device was stored in contiguous disk blocks, whereas the image in the filesystem might be spread all over the disk.) The executable images “stuck” to the swap device, hence the name.

Thus, even if the program was not currently in use, it was expected that it would be in use again shortly when another user went to run it, so it would be loaded quickly.

Modern systems have considerably faster disk and memory hardware than the PDP-11s of yore. They also use a technique called demand paging to load into memory only those parts of an executable program that are being executed. Thus, today, the sticky bit on a regular executable file serves no purpose, and indeed it has no effect.

However, in Section 1.1.2, “Directories and Filenames,” page 6, we mentioned that the sticky bit on an otherwise writable directory prevents file removal from that directory, or file renaming within it, by anyone except the file’s owner, or root. Here is an example:

$ ls -ld /tmp                                                     Show /tmp's permissions
drwxrwxrwt   19 root     root              4096 Oct 20 14:04 /tmp
$ cd /tmp                                                         Change there
$ echo this is my file > arnolds-file                             Create a file
$ ls -l arnolds-file                                              Show its permissions
-rw-r--r--    1 arnold   devel              16 Oct 20 14:14 arnolds-file
$ su - miriam                                                     Change to another user
Password:
$ cd /tmp                                                         Change to /tmp
$ rm arnolds-file                                                 Attempt to remove file
rm: remove write-protected regular file 'arnolds-file'? Y         rm is cautious
rm: cannot remove 'arnolds-file': Operation not permitted         Kernel disallows removal

The primary purpose of this feature is exactly for directories such as /tmp, where multiple users wish to place their files. On the one hand, the directory needs to be world-writable so that anyone can create files in it. On the other hand, once it’s world-writable, any user can remove any other user’s files! The directory sticky bit solves this problem nicely. Use ’chmod+t’ to add the sticky bit to a file or directory:

$ mkdir mytmp                                                     Create directory
$ chmod a+wxt mytmp                                               Add all-write, sticky bits
$ ls -ld mytmp                                                    Verify result
drwxrwxrwt    2 arnold   devel           4096 Oct 20 14:23 mytmp

Finally, note that the directory’s owner can also remove files, even if they don’t belong to him.

Setting Real and Effective IDs

Things get interesting once a process has to change its UID and GID values. Setting the group set is straightforward. Changing real and effective UID and GID values around is more involved.

Changing the Group Set

The setgroups() function installs a new group set:

#include <sys/types.h>                                Common
#include <unistd.h>
#include <grp.h>

int setgroups(size_t size, const gid_t *list);

The size parameter indicates how many items there are in the list array. The return value is 0 if all went well, -1 with errno set otherwise.

Unlike the functions for manipulating the real and effective UID and GID values, this function may only be called by a process running as root. This is one example of what POSIX terms a privileged operation; as such it’s not formally standardized by POSIX.

setgroups() is used by any program that does a login to a system, such as /bin/login for console logins or /bin/sshd for remote logins with ssh.

Changing the Real and Effective IDs

Running with two different user IDs presents a challenge to the application programmer. There are things that a program may need to do when working with the effective UID, and other things that it may need to do when working using the real UID.

For example, before Unix systems had job control, many programs provided shell escapes, that is, a way to run a command or interactive shell from within the current program. The ed editor is a good example of this: Typing a command line beginning with ! ran the rest of the line as a shell command. Typing ’!sh’ gave you an interactive shell. (This still works—try it!) Suppose the hypothetical game program described earlier also provides a shell escape: the shell should be run as the real user, not the effective one. Otherwise, it again becomes trivial for the game player to directly edit the score file or do lots more worse things!

Thus, there is a clear need to be able to change the effective UID to be the real UID. Furthermore, it’s helpful to be able to switch the effective UID back to what it was originally. (This is the reason for having a saved set-user ID in the first place; it becomes possible to regain the original privileges that the process had when it started out.)

As with many Unix APIs, different systems solved the problem in different ways, sometimes by using the same API but with different semantics and sometimes by introducing different APIs. Delving into the historic details is only good for producing headaches, so we don’t bother. Instead, we look at what POSIX provides and how each API works. Furthermore, our discussion focuses on the real and effective UID values; the GID values work analogously, so we don’t bother to repeat the details for those system calls. The functions are as follows:

#include <sys/types.h>                                                      POSIX
#include <unistd.h>

int seteuid(uid_t euid);                       Set effective ID
int setegid(gid_t egid); 

int setuid(uid_t uid);                         Set effective ID, if root, set all
int setgid(gid_t gid);

int setreuid(uid_t ruid, uid_t euid);          BSD compatibility, set both
int setregid(gid_t rgid, gid_t egid);

There are three sets of functions. The first two were created by POSIX:

int seteuid(uid_t euid)

  • This function sets only the effective UID. A regular (non-root) user can only set the ID to one of the real, effective, or saved set-user ID values. Applications that will switch the effective UID around should use this function exclusively.

  • A process with an effective UID of zero can set the effective UID to any value. Since it is also possible to set the effective UID to the saved set-user ID, the process can regain its root privileges with another call to seteuid().

int setegid(gid_t egid)

  • This function does for the effective group ID what seteuid() does for the effective user ID.

The next set of functions offers the original Unix API for changing the real and effective UID and GID. Under the POSIX model, these functions are what a setuid-root program should use to make a permanent change of real and effective UID:

int setuid(uid_t uid)

  • For a regular user, this function also sets only the effective UID. As with seteuid(), the effective UID may be set to any of the current real, effective, or saved set-user ID values. The change is not permanent; the effective UID can be changed to another value (from the same source set) with a subsequent call.

  • However, for root, this function sets all three of the real, effective, and saved set-user IDs to the given value. Furthermore, the change is permanent; the IDs cannot be changed back. (This makes sense: Once the saved set-user ID is changed, there isn’t a different ID to change back to.)

int setgid(gid_t gid)

  • This function does for the effective group ID what setuid() does for the effective user ID. The same distinction between regular users and root applies.

Note

The ability to change the group ID hinges on the effective user ID. An effective GID of 0 has no special privileges.

Finally, POSIX provides two functions from 4.2 BSD for historical compatibility. It is best not to use these in new code. However, since you are likely to see older code which does use these functions, we describe them here.

int setreuid(uid_t ruid, uid_t euid)

  • Sets the real and effective UIDs to the given values. A value of -1 for ruid or euid leaves the respective ID unchanged. (This is similar to chown(); see Section 5.5.1, “Changing File Ownership: chown(), fchown(), and lchown(),” page 155.)

  • root is allowed to set both the real and the effective IDs to any value. According to POSIX, non-root users may only change the effective ID; it is “unspecified” what happens if a regular user attempts to change the real UID. However, the GNU/Linux setreuid(2) manpage spells out the Linux behavior: The real UID may be set to either the real or effective UID, and the effective UID may be set to any of the real, effective, or saved set-user IDs. (For other systems, see the setreuid(2) manpage.)

int setregid(gid_t rgid, gid_t egid)

  • Does for the real and effective group IDs what setreuid() does for the real and effective user ID. The same distinction between regular users and root applies.

The saved set-user ID didn’t exist in the BSD model, so the idea behind setreuid() and setregid() was to make it simple to swap the real and effective IDs:

setreuid(geteuid(), getuid()); /* swap real and effective */

However, given POSIX’s adoption of the saved set-user ID model and the seteuid() and setegid() functions, the BSD functions should not be used in new code. Even the 4.4 BSD documentation marks these functions as obsolete, recommending seteuid()/setuid() and setegid()/setgid() instead.

Using the Setuid and Setgid Bits

There are important cases in which a program running as root must irrevocably change all three of the real, effective, and saved set-user IDs to that of a regular user. The most obvious is the login program, which you use every time you log in to a GNU/Linux or Unix system (either directly, or remotely). There is a hierarchy of programs, as outlined in Figure 11.1.

From init to getty to login to shell

Figure 11.1. From init to getty to login to shell

The code for login is too complicated to be shown here, since it deals with a number of tasks that aren’t relevant to the current discussion. But we can outline the steps that happen at login time, as follows:

  1. init is the primordial process. It has PID 1. All other processes are descended from it. The kernel handcrafts process 1 at boot time and runs init in it. It runs with both the real and effective UID set to zero, that is, as root.

  2. init reads /etc/inittab, which, among other things, tells init on which hardware devices it should start a getty process. For each such device (such as the console, serial terminals, or virtual consoles on a GNU/Linux system), init forks a new process. This new process then uses exec() to run getty (“get tty,” that is, a terminal). On many GNU/Linux systems, this command is named mingetty. The program opens the device, resets its state, and prints the ’login:’ prompt.

  3. Upon reading a login name, getty execs login. The login program looks up the username in the password file, prompts for a password, and verifies the password. If they match, the login process continues.

  4. login changes to the user’s home directory, sets up the initial environment, and then sets up the initial set of open files. It closes all file descriptors, opens the terminal, and uses dup() to copy the terminal’s file descriptor to 0, 1, and 2. This is where the already opened standard input, output, and error file descriptors come from.

  5. login then uses setgroups() to set the supplemental group set, setgid() to set the real, effective, and saved set-group IDs to those of the user, and finally setuid() to set all three of the real, effective, and saved set-user IDs to those of the logging-in user. Note that the call to setuid() must come last so that the other two calls succeed.

  6. Finally, login execs the user’s login shell. Bourne-style shells then read /etc/profile and $HOME/ .profile, if those files exist. Finally, the shell prints a prompt.

Note how one process changes its nature from system process to user process. Each child of init starts out as a copy of init. By using exec(), the same process does different jobs. By calling setuid() to change from root to a regular user, the process finally goes directly to work for the user. When you exit the shell (by CTRL-D or exit), the process simply dies. init then restarts the cycle, spawning a fresh getty, which prints a fresh ’login:’ prompt.

Note

Open files remain open and usable, even after a process has changed any or all of its UIDs or GIDs. Thus, setuid programs should open any necessary files up front, change their IDs to those of the real user, and continue with the rest of their job without any extra privilege.

Table 11.1 summarizes the six standard functions for manipulating UID and GID values.

Table 11.1. API summary for setting real and effective IDs

Function

Sets

Permanent

Regular user

Root

seteuid()

E

No

From R, E, S

Any value

setegid()

E

No

From R, E, S

Any value

setuid()

Root: R, E, S

Root: yes

From R, E

Any value

 

Other: E

Other: no

  

setgid()

Root: R, E, S

Root: yes

From, R, E

Any value

 

Other: E

Other: no

  

setreuid()

E, may set R

No

From R, E

Any value

setregid()

E, may set R

No

From R, E

Any value

Working with All Three IDs: getresuid() and setresuid() (Linux)

Linux provides additional system calls by which you can work directly with the real, effective, and saved user and group IDs:

#include <sys/types.h>                                     Linux
#include <unistd.h>

int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);

int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

The functions are as follows:

int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid)

  • Retrieves the real, effective, and saved set-user ID values. The return value is 0 on success or -1 if an error, with errno indicating the problem.

int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid)

  • Retrieves the real, effective, and saved set-group ID values. The return value is 0 on success or -1 if an error, with errno indicating the problem.

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

  • Sets the real, effective, and saved set-user ID values respectively. When a parameter value is -1, the corresponding UID is left unchanged.

  • When the process is running as root, the parameters can be any arbitrary values. (However, using a nonzero value for euid causes a permanent, irrevocable loss of root privilege.) Otherwise, the parameters must be one of the current real, effective, or saved set-user ID values.

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

  • Sets the real, effective, and saved set-group ID values respectively. When a parameter value is -1, the corresponding GID is left unchanged.

  • This function is analogous to setresuid().

The setresuid() and setresgid() functions are particularly valuable because the semantics are clearly defined. A programmer knows exactly what the effect of the call will be.

Furthermore, the calls are “all or nothing” operations: They either succeed completely, making the desired change, or fail completely, leaving the current situation as it was. This improves reliability since, again, it’s possible to be sure of exactly what happened.

Crossing a Security Minefield: Setuid root

Real minefields are difficult, but not impossible, to cross. However, it’s not something to attempt lightly, without training or experience.

So, too, writing programs that run setuid to root is a difficult task. There are many, many issues to be aware of, and almost anything can have unexpected security consequences. Such an endeavor should be undertaken carefully.

In particular, it pays to read up on Linux/Unix security issues and to invest time in learning how to write setuid root programs. If you dive straight into such a challenge having read this book and nothing else, rest assured that your system will be broken into, easily and immediately. It’s unlikely that either you or your customers will be happy.

Here are a few guiding principles:

  • Do as little as possible as root. Use your super powers sparingly, only where they’re absolutely needed.

  • Design your program properly. Compartmentalize your program so that all of the root operations can be done up front, with the rest of the program running as a regular user.

  • When changing or dropping privileges, use setresuid() if you have it. Otherwise use setreuid(), since those two functions have the cleanest semantics. Use setuid() only when you want the change to be permanent.

  • Change from root to regular user in the proper order: set the group set and GID values first, and then the UID values.

  • Be especially careful with fork() and exec(); the real and effective UIDs are not changed across them unless you explicitly change them.

  • Consider using setgid permissions and a special group for your application. If that will work, it’ll save you much headache.

  • Consider throwing out the inherited environment. If you must keep some environment variables around, keep as few as possible. Be sure to provide reasonable values for the PATH and IFS environment variables.

  • Avoid execlp() and execvp(), which depend upon the value of the PATH environment variable (although this is less problematic if you’ve reset PATH yourself).

These are just a few of the many tactics for traversing a danger zone notable for pitfalls, booby-traps, and landmines. See the next section for pointers to other sources of information.

Suggested Reading

Unix (and thus GNU/Linux) security is a topic that requires knowledge and experience to handle properly. It has gotten only harder in the Internet Age, not easier.

  1. Practical UNIX & Internet Security, 3rd edition, by Simson Garfinkel, Gene Spafford, and Alan Schwartz, O’Reilly & Associates, Sebastopol, CA, USA, 2003. ISBN: 0-596-00323-4.

    This is the standard book on Unix security.

  2. Building Secure Software: How to Avoid Security Problems the Right Way, by John Viega and Gary McGraw. Addison-Wesley, Reading, Massachusetts, USA, 2001. ISBN: 0-201-72152-X.

    This is a good book on writing secure software and it includes how to deal with setuid issues. It assumes you are familiar with the basic Linux/Unix APIs; by the time you finish reading our book, you should be ready to read it.

  3. “Setuid Demystified,” by Hao Chen, David Wagner, and Drew Dean. Proceedings of the 11th USENIX Security Symposium, August 5–9, 2002. http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf.

    Garfinkel, Spafford, and Schwartz recommend reading this paper “before you even think about writing code that tries to save and restore privileges.” We most heartily agree with them.

Summary

  • The use of user and group ID values (UIDs and GIDs) to identify files and processes is what makes Linux and Unix into multiuser systems. Processes carry both real and effective UID and GID values, as well as a supplemental group set. It is generally the effective UID that determines how one process might affect another, and the effective UID, GID, and group set that are checked against a file’s permissions. Users with an effective UID of zero, known as root or the superuser, are allowed to do what they like; the system doesn’t apply permission checks to such a user.

  • The saved set-user ID and saved set-group ID concepts came from System V and have been adopted by POSIX with full support in GNU/Linux. Having these separate ID values makes it possible to easily and correctly swap real and effective UIDs (and GIDs) as necessary.

  • Setuid and setgid programs create processes in which the effective and real IDs differ. The programs are marked as such with additional bits in the file permissions. The setuid and setgid bits must be added to a file after it is created.

  • getuid() and geteuid() retrieve the real and effective UID values, respectively, and getgid() and getegid() retrieve the real and effective GID values, respectively. getgroups() retrieves the supplemental group set and in a POSIX environment, can query the system as to how many members the group set contains.

  • The access() function does file permission checking as the real user, making it possible for setuid programs to check the real user’s permissions. Note that, often, examining the information as retrieved by stat() may not provide the full picture, given that the file may reside on a nonnative or network filesystem.

  • The GLIBC euidaccess() function is similar to access() but does the checking on the base of the effective UID and GID values.

  • The setgid and sticky bits, when applied to directories, introduce extra semantics. When a directory has its setgid bit on, new files in that directory inherit the directory’s group. New directories do also, and they automatically inherit the setting of the setgid bit. Without the setgid bit, new files and directories receive the effective GID of the creating process. The sticky bit on otherwise writable directories restricts file removal to the file’s owner, the directory’s owner, and to root.

  • The group set is changed with setgroups(). This function isn’t standardized by POSIX, but it exists on all modern Unix systems. Only root may use it.

  • Changing UIDs and GIDs is considerably involved. The semantics of various system calls have changed over the years. New applications that will change only their effective UID/GID should use seteuid() and setegid(). Non-root applications can also set their effective IDs with setuid() and setgid(). The setreuid() and setregid() calls from BSD were intended for swapping the UID and GID values; their use in new programs is discouraged.

  • Applications running as root can permanently change the real, effective, and saved ID values with setuid() and setgid(). One example of this is login, which has to change from a system program running as root to a nonprivileged login shell running as a regular user.

  • The Linux setresuid() and setresgid() functions should be used when they’re available, since they provide the cleanest and most reliable behavior.

  • Writing setuid-root applications is not a task for a novice. If you need to do such a thing, read up on security issues first; the sources cited previously are excellent.

Exercises

  1. Write a simple version of the id command. Its action is to print the user and group IDs, with the group names, to standard output. When the effective and real IDs are different, both are printed. For example:

    $ id
    uid=2076(arnold) gid=42(devel) groups=19(floppy),42(devel),2076(arnold)
    

    Its usage is:

    id [ user ]
    id -G [ -nr ] [ user ]
    id -g [ -nr ] [ user ]
    id -u [ -nr ] [ user ]
    

    With user, that user’s information is displayed; otherwise, id prints the invoking user’s information. The options are as follows:

    -G

    Print all the group values as numeric values only, no names.

    -n

    Print the name only, no numeric values. Applies to user and group values.

    -g

    Print just the effective GID.

    -u

    Print just the effective UID.

  2. Write a simple program, named sume, that is setuid to yourself. It should prompt for a password (see getpass(3)), which for the purposes of this exercise, can be hardwired into the program’s source code. If the person running the program correctly enters the password, sume should exec a shell. Get another user to help you test it.

  3. How do you feel about making sume available to your friends? To your fellow students or coworkers? To every user on your system?



[1] MacOS X and Windows XP are both multiuser systems, but this is a rather recent development.

[2] Although the United States doesn’t have official identity cards, many countries do.

[3] Dennis Ritchie, the inventor of C and a cocreator of Unix, received a patent for the setuid bit: Protection of Data File Contents, US Patent number 4,135,240. See http://www.delphion.com/details?pn=US04135240__ and also http://www.uspto.gov. AT&T assigned the patent to the public, allowing anyone to use its technology.

[4] Security for GNU/Linux and Unix systems is a deep topic in and of itself. This is just an example; see Section 11.9, “Suggested Reading,” page 423.

[5] One program designed for this purpose is GNU userv (ftp://ftp.gnu.org/gnu/userv/).

[6] Images come to mind of happy youthful programs, their faces and hands covered in chocolate.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.152.159