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.
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
.)
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
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
Real group ID
Effective group ID
Saved set-group 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).
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])
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
uid_t getuid(void)
uid_t geteuid(void)
gid_t getgid(void)
gid_t getegid(void)
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.
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.
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).
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.
On modern systems, the setgid and “sticky” bits each have special meaning when applied to 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.
“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.
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.
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
.
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)
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.
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.
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.
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:
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
.
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.
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.
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.
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.
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.
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 |
---|---|---|---|---|
| E | No | From R, E, S | Any value |
| E | No | From R, E, S | Any value |
| Root: R, E, S | Root: yes | From R, E | Any value |
Other: E | Other: no | |||
| Root: R, E, S | Root: yes | From, R, E | Any value |
Other: E | Other: no | |||
| E, may set R | No | From R, E | Any value |
| E, may set R | No | From R, E | Any value |
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)
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)
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.
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.
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.
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.
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.
“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.
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.
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:
| Print all the group values as numeric values only, no names. |
| Print the name only, no numeric values. Applies to user and group values. |
| Print just the effective GID. |
| Print just the effective UID. |
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.
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.
3.133.152.159