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:
Owner (also known as user): The permissions granted to the owner of the file.
The term user is used by commands such as chmod(1), which uses the abbreviation u to refer to this permission category.
Group: The permissions granted to users who are members of the file’s group.
Other: The permissions granted to everyone else.
Three permissions may be granted to each user category:
Read: The contents of the file may be read.
Write: The contents of the file may be changed.
Execute: The file may be executed (i.e., it is a program or a script). In order to execute a script file (e.g., a bash script), both read and execute permissions are required.
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.
Table 15-4. Constants for file permission bits
Constant | Octal value | Permission bit |
---|---|---|
|
| Set-user-ID |
|
| Set-group-ID |
|
| Sticky |
|
| User-read |
|
| User-write |
|
| User-execute |
|
| Group-read |
|
| Group-write |
|
| Group-execute |
|
| Other-read |
|
| Other-write |
|
| Other-execute |
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).
Example 15-3. Header file for file_perms.c
files/file_perms.h
#ifndef FILE_PERMS_H #define FILE_PERMS_H #include <sys/types.h> #define FP_SPECIAL 1 /* Include set-user-ID, set-group-ID, and sticky bit information in returned string */ char *filePermStr(mode_t perm, int flags); #endiffiles/file_perms.h
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.
Example 15-4. Convert file permissions mask to string
files/file_perms.c
#include <sys/stat.h> #include <stdio.h> #include "file_perms.h" /* Interface for this implementation */ #define STR_SIZE sizeof("rwxrwxrwx") char * /* Return ls(1)-style string for file permissions mask */ filePermStr(mode_t perm, int flags) { static char str[STR_SIZE]; snprintf(str, STR_SIZE, "%c%c%c%c%c%c%c%c%c", (perm & S_IRUSR) ? 'r' : '-', (perm & S_IWUSR) ? 'w' : '-', (perm & S_IXUSR) ? (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 's' : 'x') : (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 'S' : '-'), (perm & S_IRGRP) ? 'r' : '-', (perm & S_IWGRP) ? 'w' : '-', (perm & S_IXGRP) ? (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 's' : 'x') : (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 'S' : '-'), (perm & S_IROTH) ? 'r' : '-', (perm & S_IWOTH) ? 'w' : '-', (perm & S_IXOTH) ? (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 't' : 'x') : (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 'T' : '-')); return str; }files/file_perms.c
Directories have the same permission scheme as files. However, the three permissions are interpreted differently:
Read: The contents (i.e., the list of filenames) of the directory may be listed (e.g., by ls).
If experimenting to verify the operation of the directory read permission bit, be aware that some Linux distributions alias the ls command to include flags (e.g., -F) that require access to i-node information for files in the directory, and this requires execute permission on the directory. To ensure that we are using an unadulterated ls, we can specify the full pathname of the command (/bin/ls)
.
Write: Files may be created in and removed from the directory. Note that it is not necessary to have any permission on a file itself in order to be able to delete it.
Execute: Files within the directory may be accessed. Execute permission on a directory is sometimes called search permission.
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.)
Once a file has been opened with open(), no permission checking is performed by subsequent system calls that work with the returned file descriptor (such as read(), write(), fstat(), fcntl(), and mmap()).
The rules applied by the kernel when checking permissions are as follows:
If the process is privileged, all access is granted.
If the effective user ID of the process is the same as the user ID (owner) of the file, then access is granted according to the owner permissions on the file. For example, read access is granted if the owner-read permission bit is turned on in the file permissions mask; otherwise, read access is denied.
If the effective group ID of the process or any of the process supplementary group IDs matches the group ID (group owner) of the file, then access is granted according to the group permissions on the file.
Otherwise, access is granted according to the other permissions on the file.
In the kernel code, the above tests are actually constructed so that the test to see whether a process is privileged is performed only if the process is not granted the permissions it needs via one of the other tests. This is done to avoid unnecessarily setting the ASU
process accounting flag, which indicates that the process made use of superuser privileges (Process Accounting).
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.
Above, we said that if a process is privileged, all access is granted when checking permissions. We need to add one proviso to this statement. For a file that is not a directory, Linux grants execute permission to a privileged process only if that permission is granted to at least one of the permission categories for the file. On some other UNIX implementations, a privileged process can execute a file even when no permission category grants execute permission. When accessing a directory, a privileged process is always granted execute (search) permission.
We can rephrase our description of a privileged process in terms of two Linux process capabilities: CAP_DAC_READ_SEARCH
and CAP_DAC_OVERRIDE
(The Linux Capabilities). A process with the CAP_DAC_READ_SEARCH
capability always has read permission for any type of file, and always has read and execute permissions for a directory (i.e., can always access files in a directory and read the list of files in a directory). A process with the CAP_DAC_OVERRIDE
capability always has read and write permissions for any type of file, and also has execute permission if the file is a directory or if execute permission is granted to at least one of the permission categories for the file.
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);
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.
Table 15-5. mode constants for access()
Constant | Description |
---|---|
| Does the file exist? |
| Can the file be read? |
| Can the file be written? |
| Can the file be executed? |
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.
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);
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--
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.
Example 15-5. Using umask()
files/t_umask.c
#include <sys/stat.h> #include <fcntl.h> #include "file_perms.h" #include "tlpi_hdr.h" #define MYFILE "myfile" #define MYDIR "mydir" #define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) #define DIR_PERMS (S_IRWXU | S_IRWXG | S_IRWXO) #define UMASK_SETTING (S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH) int main(int argc, char *argv[]) { int fd; struct stat sb; mode_t u; umask(UMASK_SETTING); fd = open(MYFILE, O_RDWR | O_CREAT | O_EXCL, FILE_PERMS); if (fd == -1) errExit("open-%s", MYFILE); if (mkdir(MYDIR, DIR_PERMS) == -1) errExit("mkdir-%s", MYDIR); u = umask(0); /* Retrieves (and clears) umask value */ if (stat(MYFILE, &sb) == -1) errExit("stat-%s", MYFILE); printf("Requested file perms: %s\n", filePermStr(FILE_PERMS, 0)); printf("Process umask: %s\n", filePermStr(u, 0)); printf("Actual file perms: %s\n\n", filePermStr(sb.st_mode, 0)); if (stat(MYDIR, &sb) == -1) errExit("stat-%s", MYDIR); printf("Requested dir. perms: %s\n", filePermStr(DIR_PERMS, 0)); printf("Process umask: %s\n", filePermStr(u, 0)); printf("Actual dir. perms: %s\n", filePermStr(sb.st_mode, 0)); if (unlink(MYFILE) == -1) errMsg("unlink-%s", MYFILE); if (rmdir(MYDIR) == -1) errMsg("rmdir-%s", MYDIR); exit(EXIT_SUCCESS); }files/t_umask.c
The chmod() and fchmod() system calls change the permissions of a file.
#include <sys/stat.h> intchmod
(const char *pathname, mode_t mode); intfchmod
(int fd, mode_t mode);
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