File Permissions

In this section, we describe the permission scheme applied to files and directories. Although we talk about permissions here mainly as they apply to regular files and directories, the rules that we describe apply to all types of files, including devices, FIFOs, and UNIX domain sockets. Furthermore, the System V and POSIX interprocess communication objects (shared memory, semaphores, and message queues) also have permission masks, and the rules that apply for these objects are similar to those for files.

As noted in Retrieving File Information: stat(), the bottom 12 bits of the st_mode field of the stat structure define the permissions for a file. The first 3 of these bits are special bits known as the set-user-ID, set-group-ID, and sticky bits (labeled U, G, and T, respectively, in Figure 15-1). We say more about these bits in Set-User-ID, Set-Group-ID, and Sticky Bits. The remaining 9 bits form the mask defining the permissions that are granted to various categories of users accessing the file. The file permissions mask divides the world into three categories:

Three permissions may be granted to each user category:

The permissions and ownership of a file can be viewed using the command ls -l, as in the following example:

$ ls -l myscript.sh
-rwxr-x---    1 mtk      users        1667 Jan 15 09:22 myscript.sh

In the above example, the file permissions are displayed as rwxr-x--- (the initial hyphen preceding this string indicates the type of this file: a regular file). To interpret this string, we break these 9 characters into sets of 3 characters, which respectively indicate whether read, write, and execute permission are enabled. The first set indicates the permissions for owner, which has read, write, and execute permissions enabled. The next set indicates the permissions for group, which has read and execute enabled, but not write. The final set are the permissions for other, which doesn’t have any permissions enabled.

The <sys/stat.h> header file defines constants that can be ANDed (&) with st_mode of the stat structure, in order to check whether particular permission bits are set. (These constants are also defined via the inclusion of <fcntl.h>, which prototypes the open() system call.) These constants are shown in Table 15-4.

In addition to the constants shown in Table 15-4, three constants are defined to equate to masks for all three permissions for each of the categories owner, group, and other: S_IRWXU (0700), S_IRWXG (070), and S_IRWXO (07).

The header file in Example 15-3 declares a function, filePermStr(), which, given a file permissions mask, returns a statically allocated string representation of that mask in the same style as is used by ls(1).

If the FP_SPECIAL flag is set in the filePermStr() flags argument, then the returned string includes the settings of the set-user-ID, set-group-ID, and sticky bits, again in the style of ls(1).

The implementation of the filePermStr() function is shown in Example 15-4. We employ this function in the program in Example 15-1.

Directories have the same permission scheme as files. However, the three permissions are interpreted differently:

When accessing a file, execute permission is required on all of the directories listed in the pathname. For example, reading the file /home/mtk/x would require execute permission on /, /home, and /home/mtk (as well as read permission on the file x itself). If the current working directory is /home/mtk/sub1 and we access the relative pathname ../sub2/x, then we need execute permission on /home/mtk and /home/mtk/sub2 (but not on / or /home).

Read permission on a directory only lets us view the list of filenames in the directory. We must have execute permission on the directory in order to access the contents or the i-node information of files in the directory.

Conversely, if we have execute permission on a directory, but not read permission, then we can access a file in the directory if we know its name, but we can’t list the contents of (i.e., the other filenames in) the directory. This is a simple and frequently used technique to control access to the contents of a public directory.

To add or remove files in a directory, we need both execute and write permissions on the directory.

The kernel checks file permissions whenever we specify a pathname in a system call that accesses a file or directory. When the pathname given to the system call includes a directory prefix, then, in addition to checking for the required permissions on the file itself, the kernel also checks for execute permission on each of the directories in this prefix. Permission checks are made using the process’s effective user ID, effective group ID, and supplementary group IDs. (To be strictly accurate, for file permission checks on Linux, the file-system user and group IDs are used instead of the corresponding effective IDs, as described in Section 9.5.)

The rules applied by the kernel when checking permissions are as follows:

The checks against owner, group, and other permissions are done in order, and checking stops as soon as the applicable rule is found. This can have an unexpected consequence: if, for example, the permissions for group exceed those of owner, then the owner will actually have fewer permissions on the file than members of the file’s group, as illustrated by the following example:

$ echo 'Hello world' > a.txt
$ ls -l a.txt
-rw-r--r--   1 mtk    users    12 Jun 18 12:26 a.txt
$ chmod u-rw a.txt               Remove read and write permission from owner
$ ls -l a.txt
----r--r--   1 mtk    users    12 Jun 18 12:26 a.txt
$ cat a.txt
cat: a.txt: Permission denied    Owner can no longer read file
$ su avr                         Become someone else...
Password:
$ groups                         who is in the group owning the file...
users staff teach cs
$ cat a.txt                      and thus can read the file
Hello world

Similar remarks apply if other grants more permissions than owner or group.

Since file permissions and ownership information are maintained within a file i-node, all filenames (links) that refer to the same i-node share this information.

Linux 2.6 provides access control lists (ACLs), which make it possible to define file permissions on a per-user and per-group basis. If a file has an ACL, then a modified version of the above algorithm is used. We describe ACLs in Chapter 17.

As noted in Permission-Checking Algorithm, the effective user and group IDs, as well as supplementary group IDs, are used to determine the permissions a process has when accessing a file. It is also possible for a program (e.g., a set-user-ID or set-group-ID program) to check file accessibility based on the real user and group IDs of the process.

The access() system call checks the accessibility of the file specified in pathname based on a process’s real user and group IDs (and supplementary group IDs).

#include <unistd.h>

int access(const char *pathname, int mode);

Note

Returns 0 if all permissions are granted, otherwise -1

If pathname is a symbolic link, access() dereferences it.

The mode argument is a bit mask consisting of one or more of the constants shown in Table 15-5, ORed (|) together. If all of the permissions specified in mode are granted on pathname, then access() returns 0; if at least one of the requested permissions is not available (or an error occurred), then access() returns -1.

The time gap between a call to access() and a subsequent operation on a file means that there is no guarantee that the information returned by access() will still be true at the time of the later operation (no matter how brief the interval). This situation could lead to security holes in some application designs.

Suppose, for example, that we have a set-user-ID-root program that uses access() to check that a file is accessible to the real user ID of the program, and, if so, performs an operation on the file (e.g., open() or exec()).

The problem is that if the pathname given to access() is a symbolic link, and a malicious user manages to change the link so that it refers to a different file before the second step, then the set-user-ID-root may end up operating on a file for which the real user ID does not have permission. (This is an example of the type of time-of-check, time-of-use race condition described in Section 38.6.) For this reason, recommended practice is to avoid the use of access() altogether (see, for example, [Borisov, 2005]). In the example just given, we can achieve this by temporarily changing the effective (or file system) user ID of the set-user-ID process, attempting the desired operation (e.g., open() or exec()), and then checking the return value and errno to determine whether the operation failed because of a permissions problem.

As well as the 9 bits used for owner, group, and other permissions, the file permissions mask contains 3 additional bits, known as the set-user-ID (bit 04000), set-group-ID (bit 02000), and sticky (bit 01000) bits. We have already discussed the use of the set-user-ID and set-group-ID permission bits for creating privileged programs in Section 9.3. The set-group-ID bit also serves two other purposes that we describe elsewhere: controlling the group ownership of new files created in a directory mounted with the nogrpid option (Ownership of New Files), and enabling mandatory locking on a file (Mandatory Locking). In the remainder of this section, we limit our discussion to the use of the sticky bit.

On older UNIX implementations, the sticky bit was provided as a way of making commonly used programs run faster. If the sticky bit was set on a program file, then the first time the program was executed, a copy of the program text was saved in the swap area—thus it sticks in swap, and loads faster on subsequent executions. Modern UNIX implementations have more sophisticated memory-management systems, which have rendered this use of the sticky permission bit obsolete.

Note

The name of the constant for the sticky permission bit shown in Table 15-4, S_ISVTX, derives from an alternative name for the sticky bit: the saved-text bit.

In modern UNIX implementations (including Linux), the sticky permission bit serves another, quite different purpose. For directories, the sticky bit acts as the restricted deletion flag. Setting this bit on a directory means that an unprivileged process can unlink (unlink(), rmdir()) and rename (rename()) files in the directory only if it has write permission on the directory and owns either the file or the directory. (A process with the CAP_FOWNER capability can bypass the latter ownership check.) This makes it possible to create a directory that is shared by many users, who can each create and delete their own files in the directory but can’t delete files owned by other users. The sticky permission bit is commonly set on the /tmp directory for this reason.

A file’s sticky permission bit is set via the chmod command (chmod +t file) or via the chmod() system call. If the sticky bit for a file is set, ls -l shows a lowercase or uppercase letter T in the other-execute permission field, depending on whether the other-execute permission bit is on or off, as in the following:

$ touch tfile
$ ls -l tfile
-rw-r--r--   1 mtk    users     0 Jun 23 14:44 tfile
$ chmod +t tfile
$ ls -l tfile
-rw-r--r-T   1 mtk    users     0 Jun 23 14:44 tfile
$ chmod o+x tfile
$ ls -l tfile
-rw-r--r-t   1 mtk    users     0 Jun 23 14:44 tfile

We now consider in more detail the permissions that are placed on a newly created file or directory. For new files, the kernel uses the permissions specified in the mode argument to open() or creat(). For new directories, permissions are set according to the mode argument to mkdir(). However, these settings are modified by the file mode creation mask, also known simply as the umask. The umask is a process attribute that specifies which permission bits should always be turned off when new files or directories are created by the process.

Often, a process just uses the umask it inherits from its parent shell, with the (usually desirable) consequence that the user can control the umask of programs executed from the shell using the shell built-in command umask, which changes the umask of the shell process.

The initialization files for most shells set the default umask to the octal value 022 (----w--w-). This value specifies that write permission should always be turned off for group and other. Thus, assuming the mode argument in a call to open() is 0666 (i.e., read and write permitted for all users, which is typical), then new files are created with read and write permissions for owner, and only read permission for everyone else (displayed by ls -l as rw-r--r--). Correspondingly, assuming that the mode argument to mkdir() is specified as 0777 (i.e., all permissions granted to all users), new directories are created with all permissions granted for owner, and just read and execute permissions for group and other (i.e., rwxr-xr-x).

The umask() system call changes a process’s umask to the value specified in mask.

#include <sys/stat.h>

mode_t umask(mode_t mask);

Note

Always successfully returns the previous process umask

The mask argument can be specified either as an octal number or by ORing (|) together the constants listed in Table 15-4.

A call to umask() is always successful, and returns the previous umask.

Example 15-5 illustrates the use of umask() in conjunction with open() and mkdir(). When we run this program, we see the following:

$ ./t_umask
Requested file perms: rw-rw----             This is what we asked for
Process umask:        ----wx-wx             This is what we are denied
Actual file perms:    rw-r-----             So this is what we end up with

Requested dir. perms: rwxrwxrwx
Process umask:        ----wx-wx
Actual dir. perms:    rwxr--r--

Note

In Example 15-5, we employ the mkdir() and rmdir() system calls to create and remove a directory, and the unlink() system call to remove a file. We describe these system calls in Chapter 18.

The chmod() and fchmod() system calls change the permissions of a file.

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);

Note

Both return 0 on success, or -1 on error

The chmod() system call changes the permissions of the file named in pathname. If this argument is a symbolic link, chmod() changes the permissions of the file to which it refers, rather than the permissions of the link itself. (A symbolic link is always created with read, write, and execute permissions enabled for all users, and these permission can’t be changed. These permissions are ignored when dereferencing the link.)

The fchmod() system call changes the permissions on the file referred to by the open file descriptor fd.

The mode argument specifies the new permissions of the file, either numerically (octal) or as a mask formed by ORing (|) the permission bits listed in Table 15-4. In order to change the permissions on a file, either the process must be privileged (CAP_FOWNER) or its effective user ID must match the owner (user ID) of the file. (To be strictly accurate, on Linux, for an unprivileged process, it is the process’s file-system user ID, rather than its effective user ID, that must match the user ID of the file, as described in Section 9.5.)

To set the permissions on a file so that only read permission is granted to all users, we could use the following call:

if (chmod("myfile", S_IRUSR | S_IRGRP | S_IROTH) == -1)
    errExit("chmod");
/* Or equivalently: chmod("myfile", 0444); */

In order to modify selected bits of the file permissions, we first retrieve the existing permissions using stat(), tweak the bits we want to change, and then use chmod() to update the permissions:

struct stat sb;
mode_t mode;

if (stat("myfile", &sb) == -1)
    errExit("stat");
mode = (sb.st_mode | S_IWUSR) & ~S_IROTH;
       /* owner-write on, other-read off, remaining bits unchanged */
if (chmod("myfile", mode) == -1)
    errExit("chmod");

The above is equivalent to the following shell command:

$ chmod u+w,o-r myfile

In Ownership of New Files, we noted that if a directory resides on an ext2 system mounted with the -o bsdgroups option, or on one mounted with the -o sysvgroups option and the set-group-ID permission bit is turned on for the directory, then a newly created file in the directory takes its ownership from the parent directory, not the effective group ID of the creating process. It may be the case that the group ID of such a file doesn’t match any of the group IDs of the creating process. For this reason, when an unprivileged process (one that doesn’t have the CAP_FSETID capability) calls chmod() (or fchmod()) on a file whose group ID is not equal to the effective group ID or any of the supplementary group IDs of the process, the kernel always clears the set-group-ID permission bit. This is a security measure designed to prevent a user from creating a set-group-ID program for a group of which they are not a member. The following shell commands show the attempted exploit that this measure prevents:

$ mount | grep test                Hmmm, /test is mounted with -o bsdgroups
/dev/sda9 on /test type ext3 (rw,bsdgroups)
$ ls -ld /test                     Directory has GID root, writable by anyone
drwxrwxrwx   3 root   root    4096 Jun 30 20:11 /test
$ id                               I'm an ordinary user, not part of root group
uid=1000(mtk) gid=100(users) groups=100(users),101(staff),104(teach)
$ cd /test
$ cp ~/myprog .                    Copy some mischievous program here
$ ls -l myprog                     Hey! It's in the root group!
-rwxr-xr-x   1 mtk    root   19684 Jun 30 20:43 myprog
$ chmod g+s myprog                 Can I make it set-group-ID to root?
$ ls -l myprog                     Hmm, no...
-rwxr-xr-x   1 mtk    root   19684 Jun 30 20:43 myprog