Unix Permissions

Every file within a Unix file system -- and that includes everything that can be in a Unix file system: files, directories, named pipes, sockets, block and character devices, and symbolic links -- has an owner, a group, and a set of permissions. (On some systems, there is also an Access Control List or ACL. We don't discuss ACLs on this page, but they are mentioned on https://doc.opensuse.org/documentation/leap/security/html/book-security/cha-security-acls.html .)

At the most basic level, the permissions on a file work like this:

griffon:~$ ls -l .bashrc
-rw-r--r--  1 greg greg 856 2004-09-06 15:26 .bashrc
^\_/\_/\_/
| |  |  |
| |  |  +-----  The last three characters show the permissions for all other users.
| |  +------  The next three characters show the permissions for the file's group.
| +-------  The first three characters show the permissions for the file's owner.
+-------  The dash at the beginning means this is a regular file.

This file .bashrc has the owner "greg", the group "greg", is 856 bytes long, was last modified on 2004-09-06 at 15:26, and has 1 link (the one we're looking at). It's a regular file (- in the first column), has "rw-" (read, write but not execute) permissions for its owner, has "r--" (read, but not write or execute) permissions for its group, and has "r--" (read only) permissions for every other user on the system.

That's the simplistic viewpoint. Files aren't actually owned by names like "greg". They're owned by UID (user ID) numbers, and ls(1) maps those UIDs to human-readable user names like "greg" by using standard C library calls such as getpwuid(3). In the example shown above, "greg" is defined locally in the /etc/passwd file, and when ls is run with the -l flag, it reads the passwd file, line by line, until it finds a line with the file's owner-UID in the third field. Likewise, files are not actually group-owned by names; they're group-owned by numeric GIDs (group ID numbers), and ls pulls the corresponding name from a system group database, typically /etc/group. User and group names could also come from NIS, LDAP, or some other database, but that's beyond the scope of this document. The point is that everything's really about the numbers.

Here's the same file without the number-to-name conversion:

griffon:~$ ls -ln .bashrc
-rw-r--r--  1 1000 1000 856 2004-09-06 15:26 .bashrc

User "greg" is really the number 1000, and group "greg" is also the number 1000. The -n switch suppresses the normal conversion from numbers to names.

Similarly, there's not actually a place in the file system where the string "rw-r--r--" is stored for this file. That's just ls's human-readable way of representing the number that stores the file's permissions. Let's take a closer look at this part now.

Each character in the string "rw-r--r--" is either a letter or a dash. If we were to add execute permissions (with chmod +x .bashrc), then the file would look like this:

griffon:~$ chmod +x .bashrc; ls -l .bashrc
-rwxr-xr-x  1 greg greg 856 2004-09-06 15:26 .bashrc*

The * on the end is caused by the fact that ls is actually an alias or shell function that adds the -F flag to /bin/ls. You can ignore that for now.

When we add execute permissions, the new string "rwxr-xr-x" is used to display the file's permissions. As you might guess, this means we have "rwx" (read, write and execute) permission for the owner, "r-x" (read and execute, but not write) for members of the file's group, and "r-x" (read and execute) for the other users. Some of the dashes have been replaced by letters. Other dashes remain deactivated, to indicate that their corresponding permission (group or other write permission) has not been enabled for this file.

In fact, the permission string that ls prints out always has the same flags in the same positions, at least for the common cases. (We'll see some exceptions later.) There's really no need for putting a "r" instead of a "w". We could tell which permission is active simply by the position of the letter. Using an "r" in the first column instead of an "x" is simply a convenience to make it easier to read.

Instead of writing a letter, let's write a "1". Instead of writing a dash, let's write a "0". Now, our permission string of "rwxr-xr-x" looks like this:

rwxr-xr-x
111101101

This is a binary (base 2) representation of a number, and it's this number which is actually stored in the file system to hold the permissions for this file. ls gets this number from the file system (using the system call stat(2)) and then converts it into the string you see on the screen.

Unfortunately for us humans, binary numbers are not much fun to work with. They're really long, and it's easy to miscount the number of digits when there are so many of them all together at once, so we might accidentally miss one of them. You could use commas in between the digits, like we do when we write the number "1,000,000" to represent one million (USA usage), but there's actually a better way to handle binary numbers. We convert them to octal (base 8) or hexadecimal (base 16).

When you convert a binary number to hexadecimal, what you actually do is group the bits (BInary digiTS) four at a time, and replace each group of four binary digits with one "hex" digit, which is a number from 0-9 or a letter from A-F. That's great for some purposes, but for this task, it turns out that octal is better suited.

When you convert a binary number to octal, you group the bits three at a time. Each group of three bits is replaced by a single octal digit, from 0 to 7. This is absolutely perfect for our permissions string, because they're already naturally grouped into three groups of three digits. So let's take a look at our permission string again, but this time we'll convert it into octal:

rwxr-xr-x
111101101
\_/\_/\_/
 |  |  |
 |  +--+-- 101 (binary) is 4 + 0 + 1, or 5
 |
 +-------- 111 (binary) is 4 + 2 + 1, or 7

So "rwxr-xr-x" is equivalent to the octal number "755". Moreover, each digit in the octal number corresponds to exactly one group of permissions -- owner, group, or other. If we were to see the command chmod 740 filename, we'd know right away that the owner's permissions are going to become "rwx" (7), without even looking at the other two digits.

A moment's reflection would also let you determine what the other permission bits will be set to. An "r" always corresponds to a 4, a "w" is always a 2, and an "x" is always a 1. You can simply add them up to get the octal number, or you can break the octal number down into its binary representation in your head to get the letters. After you've done this a few times, it will become nearly automatic.

Here's a table to help you out in the meantime:

rwx  7  (4 + 2 + 1)
rw-  6  (4 + 2)
r-x  5  (4 + 1)
r--  4
-wx  3  (2 + 1)
-w-  2
--x  1
---  0

Using this table, we can see that chmod 740 means the resulting permissions are going to be rwx, then r--, then ---, or rwxr-----, meaning full permissions for the owner, read-only for the group, and nothing for the other users.

umask

Now let's take a look at the umask. The umask is simply a number which tells the kernel which permissions bits you do not want enabled whenever you make a new file. Every process has a umask number, which is part of its environment. It's handed down from parent to child, so once you set your umask in your shell, any program you run from that shell inherits it, and acts upon it.

A typical umask might be 022, which as we now know means ----w--w-, or "group write and other write". If we set our umask to 022, we're telling the kernel that any time we create a new file, we do not want it to be group- or world-writable by default. Here's an example, using the external command touch(1). Remember, touch inherits the shell's umask each time it's spawned.

griffon:~$ umask 022; touch testfile
griffon:~$ ls -l testfile
-rw-r--r--  1 greg greg 0 2005-03-11 09:54 testfile

When touch creates a file (since testfile did not yet exist, in our example), it tells the kernel to open(2) the file using the octal permissions 666 (or rw-rw-rw-). But the kernel removes the bits that are specified in the umask, so the result is what we saw above. Here's a closer look at that:

griffon:~$ rm testfile
griffon:~$ strace -eopen touch testfile
open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
open("/lib/libc.so.6", O_RDONLY)        = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
open("testfile", O_WRONLY|O_NONBLOCK|O_CREAT|O_NOCTTY|O_LARGEFILE, 0666) = 3

Ignore the first few opens, because those are just libraries. We're interested in the last one, in which "testfile" is opened with the permissions 0666. (In the C language, octal numbers are written with a leading 0, to differentiate them from ordinary base 10 numbers.) The touch command asked for 0666, but the kernel took away the 0022 that we specified in the umask, and the result was 0644 (rw-r--r--) instead of 0666.

(Don't make the mistake of thinking that the kernel does this by subtracting 022 from 666. The actual operation is carried out by bitwise logic. You'll do better to think of it as "crossing out" all of the bits in the umask, if they happen to be in the original number.)

Now let's suppose we have a paranoid user who doesn't want anybody else to read his files, ever. He has set his umask to 077. Here's what we see in that case:

griffon:~$ rm testfile
griffon:~$ umask 077; strace -eopen touch testfile 2>&1 | tail -1; ls -l testfile
open("testfile", O_WRONLY|O_NONBLOCK|O_CREAT|O_NOCTTY|O_LARGEFILE, 0666) = 3
-rw-------  1 greg greg 0 2005-03-11 10:01 testfile

As before, touch doesn't care what the umask is. It just calls open(2) with the desired permissions of 0666, and the kernel applies the umask. Our umask in this case is 0077, or ---rwxrwx, so those are the permissions we cross out. All that's left are the rw- for the owner; the group and other permissions are all taken away, and we have rw------- (0600).

The same concepts apply to directories. The only real difference is that directories are created with execute permissions by default (0777 instead of 0666). Let's take a look at this:

griffon:~$ umask 022; strace -emkdir mkdir testdir; ls -ld testdir
mkdir("testdir", 0777)                  = 0
drwxr-xr-x  2 greg greg 512 2005-03-11 10:05 testdir/

There are a few new things in this example, so let's take them one at a time. The first is that we used the mkdir(1) command, which then used the mkdir(2) system call to the kernel. So we told strace(1) to show us just that system call. Next, we see that mkdir (the command) told the kernel to mkdir (the system call) this directory with permissions 0777 (which would be rwxrwxrwx). But the kernel took away the umask's bits, so we ended up with rwxr-xr-x (0755). Finally, we used the -d flag to tell ls to show us the directory itself, not the contents, which is what ls normally does when you tell it to list a directory. The first column of ls's output is a d instead of a dash, because this is a directory and not a regular file. The slash at the end is because "greg" has an alias or a function, and you can ignore it for now.

Here's the same example with a different umask:

griffon:~$ rmdir testdir
griffon:~$ umask 027; mkdir testdir; ls -ld testdir
drwxr-x---  2 greg greg 512 2005-03-11 10:09 testdir/

This is the first time we've seen the group permissions differ from the other-user permissions. A umask of 027 means "we don't want our group to have write permission, but they can read and execute; everybody else gets nothing". And this is indeed what we see in the end: rwxr-x---, or 0750.

Directory permissions

But there's something else you need to know about directories, which is not obvious or intuitive. The permissions bits on directories don't mean exactly the same thing that they do on regular files.

Read permission on a directory lets you get a listing of the filenames in that directory, but nothing else. However, surprisingly, Debian doesn't quite give the same results as some other systems. Let's set up an example of this (even though it's really unlikely that we'd ever encounter it in real life):

pegasus:~$ uname -a
OpenBSD pegasus 3.6 PEGASUS#1 i386
pegasus:~$ mkdir testdir; echo s3kr3tdata > testdir/secret; chmod 400 testdir
pegasus:~$ ls -ld testdir   
dr--------  2 greg  greg  512 Mar 11 10:19 testdir/
pegasus:~$ ls testdir
pegasus:~$ echo testdir/*
testdir/secret
pegasus:~$ ls -l testdir/*
ls: testdir/secret: Permission denied
pegasus:~$ cat testdir/secret
cat: testdir/secret: Permission denied

This is how a traditional Unix system deals with read-but-not-execute directories. The shell can "see" the file inside the directory, because it's able to get the file's name by reading the directory in a very literal way. (A directory is just a list of filenames and their inode numbers.) But it can't do anything to the files in that directory -- it can't open them, and it can't stat them. So ls -l fails (can't stat), and cat fails (can't open).

Here's the same setup on a Debian system. I had to switch to /tmp for this example, because the results I got in my home directory were abnormal (because my home directory is NFS mounted, not a local file system).

griffon:/tmp$ uname -a
Linux griffon 2.4.28 #1 Thu Jan 6 08:29:18 EST 2005 i686 GNU/Linux
griffon:/tmp$ mkdir testdir; echo s3kr3tdata > testdir/secret; chmod 400 testdir
griffon:/tmp$ ls testdir
ls: testdir/secret: Permission denied
griffon:/tmp$ echo testdir/*
testdir/secret
griffon:/tmp$ ls -l testdir
ls: testdir/secret: Permission denied
total 0

We see basically the same results, except that ls is a bit more verbose. We get an extra error message on the first instance, and an extra total 0 on the second one. But the same underlying behavior occurs: we can see the name of the file in the read-only directory, but we can't open it or stat it.

Write permission on a directory lets you create, rename or delete files in that directory (with one exception, shown later).

Let me say that one more time, for the people who are reading too quickly. Write permission on a directory lets you delete files from that directory. With one exception that I'll show later. I didn't say anything about ownership, or even the permission on the file itself. They're not relevant.

Here's an example:

griffon:~$ mkdir testdir; sudo touch testdir/foo; ls -la testdir
total 20
drwxr-xr-x    2 greg greg   512 2005-03-11 10:29 ./
drwxr-xr-x  150 greg greg 19456 2005-03-11 10:29 ../
-rw-r--r--    1 root greg     0 2005-03-11 10:29 foo
griffon:~$ rm testdir/foo
rm: remove write-protected regular empty file `testdir/foo'? y
griffon:~$ ls -la testdir
total 20
drwxr-xr-x    2 greg greg   512 2005-03-11 10:29 ./
drwxr-xr-x  150 greg greg 19456 2005-03-11 10:29 ../

Root owned that file, but greg was able to delete it. Why? Because greg has write permission on the directory that it was in. The fact that root owned it just caused rm(1) to ask for confirmation, but it didn't stop us from doing it. If we wanted to skip the confirmation, we could have passed rm the -f (force) flag, and it would have deleted the file without a single squeak.

Last but not least, execute permission on a directory allows you to chdir(2) (change directory) into that directory, and also allows you to open or stat the files therein. Opening a file also requires the appropriate permissions on the file itself. In practice, you will almost never see a directory that has read permission for some set of users, but not the corresponding execute bit. For practical purposes, you should always ensure that a directory that has the "r" also has the "x" -- otherwise, as we saw earlier, the lone "r" doesn't do us very much good.

Is there ever a place where you'd want "x" on a directory but not "r"? Yes. This is useful when you want to allow people to get at files in a directory if they happen to know the file names, but not to be able to find out the names of the files. Also, it can be useful if you're setting up an anonymous upload place, and you don't want people to be able to see what others have uploaded until you have a chance to filter out the undesirable files.

But the most common time you'll see an "x" on a directory without its "r" is when a user has a web site set up in her ~/public_html directory, but doesn't want her entire home directory to be visible to the world. In order for Apache to get to the files in public_html, her home directory has to be "x" (executable) for all users, and so does public_html itself. So she might have it set up something like this:

drwxr-x--x  150 jane jane 19456 2005-03-11 10:29 /home/jane
drwxr-x--x   14 jane jane  5632 2005-02-11 08:48 /home/jane/public_html
-rw-r--r--    1 jane jane  1226 2004-11-04 10:05 /home/jane/public_html/index.html

Now, other users on the system (outside of the "jane" group) can't see what files jane has in her home directory, and they can't see what she has in her public_html directory, but apache can open the index.html file. (To recap, apache needs "x" permissions on all of the directories leading up to the index.html file, and it needs "r" permission on the file itself, in order to open it for reading.)

This doesn't actually protect jane's files from prying eyes if the prying eyeball happens to know the name of the file. For example, john might want to see what aliases jane uses, so he might cat ~jane/.bashrc and hope that she has a .bashrc file from which he can learn. If jane has a .bashrc file, and it's world-readable, then john will be able to read it, even if he can't do an ls -l on it. If jane wants the contents of her files to be secret as well as the names, while still keeping a public_html directory for Apache to use, then she must take away "other" permissions on the files themselves. This means she probably wants to use a umask of 027. The only remaining issue for jane is that any time she adds a new file to public_html, she must remember to make it world readable for apache. There is, unfortunately, no way for jane to set a umask of 022 for ~/public_html and a umask of 027 for all other directories. umasks are "one per process", not "one per directory".

Sticky, setuid and setgid bits

Finally, there are three additional permission bits that we haven't examined yet, and each of them has a special purpose (sometimes even more than one). We'll take each of these in turn.

The first of these special bits is called the "sticky bit", which is a historic name that no longer tells you what the bit actually does. About the only time you'll ever see it on a Debian system is when you look at the permissions on the /tmp directory:

griffon:~$ ls -ld /tmp
drwxrwxrwt  14 root root 4096 2005-03-11 10:06 /tmp/

The "t" on the end of the permissions string (instead of the "x" that we expected to see) means that this directory has the sticky bit turned on. When a directory has the sticky bit turned on, nobody can delete or rename anybody else's files from that directory, even though they have write permission on the directory. Remember when greg deleted a file owned by root? He can't do that in /tmp:

griffon:/tmp$ sudo touch foo; ls -ld . foo
drwxrwxrwt  14 root root 4096 2005-03-11 10:46 ./
-rw-r--r--   1 root root    0 2005-03-11 10:46 foo
griffon:/tmp$ rm -f foo
rm: cannot remove `foo': Operation not permitted

Despite having write permission on /tmp (and therefore the ability to create new files therein), greg cannot remove root's file, because of the sticky bit on /tmp. This is important, because we don't want users to be able to delete other people's temporary files and screw up their programs that need to use them.

The original meaning of the "sticky bit" comes from back in the early days of Unix when we didn't have sophisticated demand paging algorithms for memory management. A process could either be in memory, or not in memory. If the kernel needed extra memory for a new process, it would have to swap out a whole process to disk, instead of just selected pages of a process, to make room. Also, when programs stopped executing, their whole in-memory image went away (not just the data segment). So on those old systems, the "sticky bit" on a program file (not a directory) meant that the program's code should be left in memory after it was done, because it was likely to be wanted again soon. The classic example of a sticky program was /bin/ls, which everyone used all the time. Keeping it in memory sped up overall system performance. Keeping something huge like the C compiler in memory, though, would have hurt performance on those older systems, because they didn't have much memory to spare. None of this applies to a Debian system, however. This paragraph is included here for historical reference only.

Next, there are two bits that cause programs to be executed with different privileges than those of the person who ran them. These are the "setuid" and "setgid" bits, and they correspond to the user and group of the program. A program with the setuid bit enabled runs as the program's owner (UID), no matter who runs it. A program with the setgid bit enabled runs as the program's group (GID).

The best example of a setuid program is passwd(1) which lets users change their passwords. It has to be able to modify the contents of the /etc/passwd file, but we certainly don't want ordinary users to have that power! So the passwd program is very carefully written, so that it only does the minimal necessary change to let a user change her own password. Then the setuid bit is enabled, so that it has the power to make that change.

griffon:~$ ls -l /usr/bin/passwd
-rwsr-xr-x  1 root root 26616 2004-07-27 10:39 /usr/bin/passwd*

The "s" where we expected to see an "x" for the owner's permissions means that this program has both the execute and setuid bits set. When greg types "passwd", the passwd program will be run as root, instead of greg.

If we ran passwd as a normal user and then open a new terminal we would see something like:

griffon:~$ passwd
Changing password for user griffon.
Changing password for griffon.
(current) UNIX password: 

On the second terminal, we would see that passwd is ran with roots' Effective UID (EUID).

griffon:~$ ps auxw|grep '[p]asswd'
root     11911  0.0  0.0 183324  2320 pts/2    S+   16:52   0:00 passwd

Likewise, setgid programs runs with the group ID of their group-owners, rather than the group ID (gid) of the person who ran them. A good example is xterm(1):

-rwxr-sr-x  1 root utmp 263736 2004-12-15 14:19 /usr/bin/X11/xterm*

xterm runs as a member of the "utmp" group, so that it can write an entry into the utmp(5) file:

-rw-rw-r--  1 root utmp 11904 2005-03-11 08:09 /var/run/utmp

This file is used by who(1) and other programs to keep a record of who is "logged on" to the system. When an xterm is run with certain flags, it writes an entry into this file, so that people who "log in" to the system over the network by running an xterm and a few programs inside it will show up in the output of "who". (This is a very poor and simplistic description of how utmp works, but this page is already getting much too large.)

Finally, one last point and we shall be done. The setuid bit has no meaning on a directory (in Debian), but the setgid bit does. It means that files created in that directory will inherit the directory's group ownership, instead of the gid of whoever made them. When combined with a umask that permits group write privileges (such as 002), this is useful in setting up an area where a group of users can work together. For example:

drwxrwsr-x  15 root src 4096 2005-01-06 08:52 /usr/src

The /usr/src directory is writable by members of the group "src", and it also has the setgid bit enabled (shown as an "s"), so any new files created in it will also be group-owned by "src". If we have two users in the "src" group (say, john and jane), and if they both set their umask to 002 or 007 (at least while working in this directory), then anything they create here will also be group-src-writable:

drwxrwsr-x  15 root src 4096 2005-01-06 08:52 /usr/src
-rw-rw----   1 jane src  856 2004-09-06 15:26 /usr/src/foo.c
-rw-rw-r--   1 john src  952 2004-09-03 13:21 /usr/src/bar.c

In this entirely made-up example, jane is using a umask of 007 while she works here, or has used chmod(1) to enforce the same thing. john is using a umask of 002 (or chmod). Other users on the system will be able to read john's code, but not jane's. But the important part is that they can both read and write to each other's files. This is, of course, no substitute for a rigorous version control system, but it illustrates the most common use of the setgid bit on a directory.

On some systems (including Debian with coreutils version 6.0 or higher), the chmod command will not clear the setuid and setgid bits from a directory when using the octal mode. On other systems, chmod 755 will always set a directory to drwxr-xr-x no matter what. So, if you ever find yourself in a situation where chmod 755 somedir is failing to clear the setuid/setgid bits, you may have to use the symbolic mode to express your intentions. Check your system's man pages for more details.


CategoryUnix

Permissions (last edited 2022-07-31 15:08:26 by emanuele6)