10.3 File System Permissions

A good way to see users and groups in action is to look at file system permissions. By examining how the system associates permissions with each file and then seeing how the kernel checks to see who is allowed to access which files, the concepts of user ID and group ID should become clearer.

Each file has exactly one owning user and exactly one owning group. When you create a new file, the file is owned by the user and group of the creating process. [2]

[2] Actually, there are some rare exceptions, involving sticky bits, discussed later in Section 10.3.2, "Sticky Bits."

The basic things that you can do with files, as far as Linux is concerned, are read from them, write to them, and execute them. (Note that creating a file and removing a file are not considered things you can do with the file; they're considered things you can do with the directory containing the file. We'll get to this a little later.) If you can't read a file, Linux won't let you examine the file's contents. If you can't write a file, you can't change its contents. If there's a program file for which you do not have execute permission, you cannot run the program.

Linux enables you to designate which of these three actions—reading, writing, and executing—can be performed by the owning user, owning group, and everybody else. For example, you could say that the owning user can do anything she wants with the file, that anyone in the owning group can read and execute the file (but not write to it), and that nobody else can access the file at all.

You can view these permission bits interactively with the ls command by using the -l or -o options and programmatically with the stat system call. You can set the permission bits interactively with the chmod program [3] or programmatically with the system call of the same name. To look at the permissions on a file named hello, use ls -l hello. Here's how the output might look:

[3] You'll sometimes see the permission bits for a file referred to as the file's mode. The name of the chmod command is short for "change mode."

 
% ls -l hello 
-rwxr-x---    1 samuel   csl         11734 Jan 22 16:29 hello 

The samuel and csl fields indicate that the owning user is samuel and that the owning group is csl.

The string of characters at the beginning of the line indicates the permissions associated with the file. The first dash indicates that this is a normal file. It would be d for a directory, or it can be other letters for special kinds of files such as devices (see Chapter 6, "Devices" ) or named pipes (see Chapter 5, "Interprocess Communication," Section 5.4, "Pipes" ). The next three characters show permissions for the owning user; they indicate that samuel can read, write, and execute the file. The next three characters show permissions for members of the csl group; these members are allowed only to read and execute the file. The last three characters show permissions for everyone else; these users are not allowed to do anything with hello.

Let's see how this works. First, let's try to access the file as the user nobody, who is not in the csl group:

 
% id 
uid=99(nobody) gid=99(nobody) groups=99(nobody) 
% cat hello 
cat: hello:  Permission denied 
% echo hi > hello 
sh: ./hello:  Permission denied 
% ./hello 
sh: ./hello:  Permission denied 

We can't read the file, which is why cat fails; we can't write to the file, which is why echo fails; and we can't run the file, which is why ./hello fails.

Things are better if we are accessing the file as mitchell, who is a member of the csl group:

 
% id 
uid=501(mitchell) gid=501(mitchell) groups=501(mitchell),503(csl) 
% cat hello 
#!/bin/bash 
echo "Hello, world." 
% ./hello 
Hello, world. 
% echo hi > hello 
bash: ./hello: Permission denied 

We can list the contents of the file, and we can run it (it's a simple shell script), but we still can't write to it.

If we run as the owner(samuel), we can even overwrite the file:

 
% id 
uid=502(samuel) gid=502(samuel) groups=502(samuel),503(csl) 
% echo hi > hello 
% cat hello 
hi 

You can change the permissions associated with a file only if you are the file's owner (or the superuser). For example, if you now want to allow everyone to execute the file, you can do this:

 
% chmod o+x hello 
% ls -l hello 
-rwxr-x--x    1 samuel   csl             3 Jan 22 16:38 hello 

Note that there's now an x at the end of the first string of characters. The o+x bit means that you want to add the execute permission for other people (not the file's owner or members of its owning group). You could use g-w instead, to remove the write permission from the group. See the man page in section 1 for chmod for details about this syntax:

 
% man 1 chmod 

Programmatically, you can use the stat system call to find the permissions associated with a file. This function takes two parameters: the name of the file you want to find out about, and the address of a data structure that is filled in with information about the file. See Appendix B, "Low-Level I/O," Section B.2, "stat," for a discussion of other information that you can obtain with stat. Listing 10.2 shows an example of using stat to obtain file permissions.

Listing 10.2 (stat-perm.c) Determine File Owner's Write Permission
#include <stdio.h> 
#include <sys/stat.h> 
 
int main (int argc, char* argv[]) 
{
  const char* const filename = argv[1]; 
  struct stat buf; 
  /* Get file information.  */ 
  stat (filename, &buf); 
  /* If the permissions are set such that the file's owner can write 
     to it, print a message.  */ 
  if (buf.st_mode & S_IWUSR) 
    printf ("Owning user can write `%s'.\n", filename); 
  return 0; 
} 

If you run this program on our hello program, it says:

 
% ./stat-perm hello 
Owning user can write 'hello'. 

The S_IWUSR constant corresponds to write permission for the owning user. There are other constants for all the other bits. For example, S_IRGRP is read permission for the owning group, and S_IXOTH is execute permission for users who are neither the owning user nor a member of the owning group. If you store permissions in a variable, use the typedef mode_t for that variable. Like most system calls, stat will return -1 and set errno if it can't obtain information about the file.

You can use the chmod function to change the permission bits on an existing file. You call chmod with the name of the file you want to change and the permission bits you want set, presented as the bitwise or of the various permission constants mentioned previously. For example, this next line would make hello readable and executable by its owning user but would disable all other permissions associated with hello:

 
chmod ("hello", S_IRUSR | S_IXUSR); 

The same permission bits apply to directories, but they have different meanings. If a user is allowed to read from a directory, the user is allowed to see the list of files that are present in that directory. If a user is allowed to write to a directory, the user is allowed to add or remove files from the directory. Note that a user may remove files from a directory if she is allowed to write to the directory, even if she does not have permission to modify the file she is removing. If a user is allowed to execute a directory, the user is allowed to enter that directory and access the files therein. Without execute access to a directory, a user is not allowed to access the files in that directory independent of the permissions on the files themselves.

To summarize, let's review how the kernel decides whether to allow a process to access a particular file. It checks to see whether the accessing user is the owning user, a member of the owning group, or someone else. The category into which the accessing user falls is used to determine which set of read/write/execute bits are checked. Then that apply to this user. the kernel checks the operation that is being performed against the permission bits [4]

[4] The kernel may also deny access to a file if a component directory in its file path is inaccessible. For instance, if a process may not access the directory /tmp/private/, it may not read /tmp/private/data, even if the permissions on the latter are set to allow the access.

There is one important exception: Processes running as root (those with user ID 0) are always allowed to access any file, regardless of the permissions associated with it.

10.3.1 Security Hole: Programs Without Execute Permissions

Here's a first example of where security gets very tricky. You might think that if you disallow execution of a program, then nobody can run it. After all, that's what it means to disallow execution. But a malicious user can make a copy of the program, change the permissions to make it executable, and then run the copy! If you rely on users not being able to run programs that aren't executable but then don't prevent them from copying the programs, you have a security hole—a means by which users can perform some action that you didn't intend.

10.3.2 Sticky Bits

In addition to read, write, and execute permissions, there is a magic bit called the sticky bit. [5] This bit applies only to directories.

[5] This name is anachronistic; it goes back to a time when setting the sticky bit caused a program to be retained in main memory even when it was done executing. The pages allocated to the program were "stuck" in memory.

A directory that has the sticky bit set allows you to delete a file only if you are the owner of the file. As mentioned previously, you can ordinarily delete a file if you have write access to the directory that contains it, even if you are not the file's owner. When the sticky bit is set, you still must have write access to the directory, but you must also be the owner of the file that you want to delete.

A few directories on the typical GNU/Linux system have the sticky bit set. For example, the /tmp directory, in which any user can place temporary files, has the sticky bit set. This directory is specifically designed to be used by all users, so the directory must be writable by everyone. But it would be bad if one user could delete another user's files, so the sticky bit is set on the directory. Then only the owning user (or root, of course) can remove a file.

You can see the sticky bit is set because of the t at the end of the permission bits when you run ls on /tmp:

 
% ls -ld /tmp 
drwxrwxrwt   12 root     root         2048 Jan 24 17:51 /tmp 

The corresponding constant to use with stat and chmod is S_ISVTX.

If your program creates directories that behave like /tmp, in that lots of people put things there but shouldn't be able to remove each other's files, then you should set the sticky bit on the directory. You can set the sticky bit on a directory with the chmod command by invoking the following:

 
% chmod o+t directory 

To set the sticky bit programmatically, call chmod with the S_ISVTX mode flag. For example, to set the sticky bit of the directory specified by dir_path to those of the /tmp and give full read, write, and execute permissions to all users, use this call:

 
chmod (dir_path, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);