(“Dummies”, AKA, future me)

I know all this information for some value of the word “know”. Epistemologically speaking, “knowing” in the age of ubiquitous broadband is tantamount to knowing what search term will find the answer you need.

In a hypothetical post-apocalyptic dystopia where search engines are a distant memory and esoteric UNIX knowledge is worth more than gold – I’d like to still “know” this information.

As such, I’ve dutifully stared at lots of man pages, I’ve done a bunch of DuckDuckGo searches, and I’ve amalgamated basically everything that I think I kinda/sorta know about permissions and dumped it all into this post.

Some of this is information is undoubtedly wrong and dumb. I find that writing down all the wrong and dumb information that I currently believe to be right and correct is a useful step in rough-hewing that information towards correctness.

Basics ¶ There are 3 permissions: Read

Write

Execute These permissions are self-explanatory. OK – maybe “execute” is actually not at all self-explanatory – it’s a severely overloaded term in the UNIX permissions model. In the basic sense “execute” means that a file (such as a binary executable) can be run as a program. It can also mean that the operating system can invoke an interpreter to run the script . If a directory has the execute permission it can be searched and have its contents listed (i.e., ls directory ) a user can traverse into the directory and access its contents. Edit Sun, 12 Jan 2020 16:28:38 -0700 /u/mistral on reddit points out that the e x ecute permission on a directory does NOT allow a user to list a directory's contents. It does allow a user to enter a directory and access its contents; however, without the r ead permission a user cannot list the entries in a directory. Oddly, the chmod man page calls this ability " search ". Those 3 permissions can be granted to 3 different sets of users: The user that owns the file

The file’s group

Others

Reading permissions ¶ File permissions are represented by 9 letters – 3 sets of 3 letters each. Each set of letters is for a different set of users: the user who owns a file, the file’s group, and others; Each user can be granted any combination of the 3 permissions: r ead, w rite, and e x ecute. When a set of users doesn’t have a permission it’s represented with a - . rwx/rw-/r-- foo | | +-------> Others can read the file "foo" | +-----------> Group can read and write the file "foo" +---------------> User that owns can read, write, and execute the file "foo"

Octal permissions ¶ Permissions can also be represented as succinct – if somewhat confusing, but totally awesome – octal representations. Each permission has an octal representation: e x ecute: 1

ecute: 1 w rite: 2

rite: 2 r ead: 4 It should be noted that these numbers are all powers of 2 (20 = 1; 21 = 2; 22 = 4), meaning that each permission represents a 0 or 1 in a place in binary. The binary format and the human readable format have a nice symmetry: octal binary human-readable 1 001 --x 2 010 -w- 4 100 r-- Three binary digits can be used to represent 8 numbers (23 == 8) which represents all possible states of read, write, and execute. octal binary human-readable 0 000 --- 1 001 --x 2 010 -w- 3 011 -wx 4 100 r-- 5 101 r-x 6 110 rw- 7 111 rwx Each digit of the octal, just as for the 3 blocks in a human-readable permission, represent a set of users. From left-to-right the 3 digits represent owner, group, and others. 7/6/4 foo | | +-----> Others can read the file (Binary: 100, Human-readable: r--) | +-------> Group can read and write the file (Binary: 110, Human-readable: rw-) +---------> User that owns can read, write, and execute the file (Binary: 111, Human-readable: rwx)

Viewing permissions ¶ You can see permissions for files and directories using the stat(1) command. Passing %A to the --format argument shows “human-readable” output: $ stat --format 'Permissions: %A Filename: %n' foo # %n is filename Permissions: -rwxrw-r-- Filename: foo “human-readable” output is represented by 10 letters. The left-most letter is for node-type which is one of: - : normal file

: normal file d : directory

: directory l : symbolic link

: symbolic link c character device

character device p pseudo-terminal

pseudo-terminal b for a block device

for a block device s for a socket The remaining 9 letters are r ead, w rite, and e x ecute for user that owns the file, file’s group, and other users; same as above. We can see an octal representation using the command stat --format %a $ stat -c 'Octal: %a Filename: %n' foo # Octal (%n is filename) Octal: 764 Filename: foo

Reading the setuid, setgid, and sticky bit ¶ Both the human-readable and the octal representation above are simplified. Aside from normal UNIX permissions, there are three extra bits of information: setuid, setgid, and the sticky bit. If one of these bits of information is set you can see it using the stat(1) command. $ stat -c 'Octal: %a Filename: %n' foo # Octal (%n is filename) Octal: 4764 Filename: foo The left-most digit that has been prepended onto the normal permissions is the setuid/setgid/sticky bit. Again, each of the states – setuid, setgid, sticky – can be represented in combination and alone as octal digits that map to 3 bits of information: name octal binary 0 000 sticky 1 001 setgid 2 010 sticky+setgid 3 011 setuid 4 100 setuid+sticky 5 101 setuid+setgid 6 110 sticky+setuid+setgid 7 111 The octal permission for foo translates as: 4/7/6/4 foo | | | +-------> Others can read the file (Binary: 100) | | +---------> Group can read and write the file (Binary: 110) | +-----------> User that owns can read, write, and execute the file (Binary: 111) +-------------> setuid-bit is set (Binary: 100) The human-readable representation of the sticky/setuid/setgid bit is not given its own column of 3 letters but is – for some awful reason – overlaid on the 10 letter human-readable output: name octal binary human-readable 0 000 --------- sticky 1 001 --------T setgid 2 010 -----S--- sticky+setgid 3 011 -----S--T setuid 4 100 --S------ setuid+sticky 5 101 --S-----T setuid+setgid 6 110 --S--S--- sticky+setuid+setgid 7 111 --S--S--T Because this additional bit is not given its own column in the human-readable permission representation it hides the normal executable bit. That is, in each of the 3 blocks representing each of the 3 sets of users, the right-most column of each block represents BOTH whether or not a particular user set has the execute permission AND the sticky/setuid/setgid bits. When the executable bit is set for a group of users, the human-readable output uses a lowercase s for setuid/setgid and a lowercase t for the sticky bit. name human-readable setuid --S------ setuid+user can execute --s------ setgid -----S--- setgid+group can execute -----s--- sticky --------T sticky+others can execute --------t We can see the human-readable permissions for foo using the stat(1) command with the %A format option: $ stat -c 'Permissions: %A Filename: %n' foo # (%n is filename) Permissions: -rwsrw-r-- Filename: foo The lowercase s instead of the normal x in the fourth column from the left indicates two things: the user that owns the file has the e x ecute permission the setuid bit is set The setuid/setgid/sticky bit have different effects based on operating system; whether they’re set on a file or a directory; whether a file has the executable bit set; and whether a file with an executable bit is binary or a script.

setuid on a non-executable file ¶ AFAIK, the setuid bit has no effect on files no one can execute. There is a pedantic exception of a file with setuid and no executable bits set for any of the 3 sets of users (i.e., owner, group, or other users), BUT the file is on a file system that supports ACL mounting and has special ACL permissions setup. If the file has ACL permissions that allow a user or group to execute the file, the file will execute following setuid rules for files with executable bits set. This exception, it should be noted, is not actually an exception since it is in practice the same scenario as an executable file with setuid set – we’ve just dragged ACLs into it for fun and pedantry.

setuid on a directory ¶ AFAIK, the setuid bit has no effect directories. I’ve heard tell of a few systems where the setuid bit on a directory will cause files created inside that directory to have the same owner as the parent directory. This seems insane to me from a security perspective.

setgid on a file ¶ If the setgid bit is set WITHOUT the group executable bit being set; e.g., rwxrwS--- some file systems use that as an indicator that the file uses mandatory file locking. Caveat emptor – the 0th item in the Linux kernel documentation on mandatory file locking is, “Why you should avoid mandatory locking” – this, to me, makes it seems like a bit of a fraught endeavour.

setgid on a directory ¶ The setgid bit on a directory will cause files and subdirectories created inside the directory to have the same group as the parent directory – even if the user creating the file is not in the parent directory’s group. $ stat --format '%a %U:%G %n' setgid 2775 thcipriani:wikidev setgid $ touch setgid/thcipriani $ sudo -u mwdeploy touch setgid/mwdeploy stat --format '%a %U:%G %n' setgid/thcipriani 664 thcipriani:wikidev setgid/thcipriani $ stat --format '%a %U:%G %n' setgid/mwdeploy 644 mwdeploy:wikidev setgid/mwdeploy Users not within the directory’s group will not be able to create files unless the directory is writable by others: $ stat --format '%a %U:%G %n' setgid 2775 thcipriani:wikidev setgid $ sudo -u 💩 touch setgid/💩 # Not a user in wikidev touch: cannot touch 'setgid/💩': Permission denied $ chmod o+w setgid $ sudo -u 💩 touch setgid/💩 # Not a user in wikidev $ stat --format '%A %U:%G %n' setgid/💩 644 💩:wikidev setgid/💩

Sticky bit ¶

Sticky bit on an executable binary ¶ AFAIK, the sticky bit has no effect on file execution. In older systems, setting the sticky bit on a file caused the file to remain in swap after execution. Since swap images were contiguous sectors of disk on older systems, this was faster than re-reading an executable from disk – or so stackoverflow tells me.

Sticky bit on a directory (AKA, restricted-deletion flag) ¶ If the sticky bit is set on a directory, no one can delete files in that directory except root , the owner of the directory, or the owner of the file in the directory. Even a member of the directory’s group cannot delete a file put there by someone not in that group who also has write permission on the directory. $ stat --format '%a %U:%G %n' stickybit 1777 thcipriani:wikidev stickybit $ sudo -u 💩 touch stickybit/💩 $ sudo -u mwdeploy rm stickybit/💩 rm: remove write-protected regular empty file 'stickybit/💩'? y rm: cannot remove 'stickybit/💩': Operation not permitted Under normal conditions anyone with w rite permission to a directory could delete files in that directory. $ stat --format '%a %U:%G %n' stickybit 777 thcipriani:wikidev stickybit $ sudo -u 💩 touch nostickybit/💩 $ sudo -u mwdeploy rm nostickybit/💩 rm: remove write-protected regular empty file 'nostickybit/💩'? y # File is removed The standard /tmp directory on a Linux system has the sticky bit set for this exact reason. No one besides root and the owner of the file in /tmp/ should be able to remove a file from /tmp .

Bitwise operations ¶ Every permission can be represented as binary bits: 1 or 0 . These bits can be combined using bitwise operations. Bitwise operations are a bit of Jedi-level black magic. I have to lookup bitwise operations more often than not. Each time I look this stuff up, I’m positive that it’ll stick, but my brain is Teflon™ for bit-twiddling it seems. Bitwise operations are often used in low-level computer programs because they execute quickly. A simple bitwise operation might be a logical OR , represented by the symbol | which has some simple rules for how to combine 1s and 0s: Logical OR | 0 | 0 == 0 0 | 1 == 1 1 | 0 == 1 1 | 1 == 1 If either bit passed to a logical OR is 1 , the output is 1 ; otherwise the output is 0 . There is a complementary bitwise function called logical AND ( & ) that works in the exact opposite-ish way: Logical AND & 0 & 0 == 0 0 & 1 == 0 1 & 0 == 0 1 & 1 == 1 That is, if either of the bits passed as arguments to a logical AND is 0 , the output is 0 . Only when both bits are 1 is the output 1 . There are other ways to change the bits in a bit field that don’t involve combining two binary numbers. A unary operation operates on a single argument. For example, you can find the complement of a binary number by flipping all the bits. This is called a logical NOT (or logical complement) and is represented by the symbol ~ . Logical Complement ~ ~0 == 1 ~1 == 0 If you combine these two functions – you can take the logical AND of a bit and another bit’s NOT . This is called the abjunction: Logical AND & with Logical Complement ~ (abjunction) 0 & ~0 == 0 0 & ~1 == 0 1 & ~0 == 1 1 & ~1 == 0 Internally, bitwise functions are used to set the modes for a file. For example, if I wanted the file’s user to be able to read ( 4 ) and write ( 2 ) a file I could OR those individual permissions together in code: >>> 4 | 2 # S_IRUSR | S_IWUSR 6 It’s a bit easier to see what’s happening by looking at the permissions’ binary representations: bitwise octal binary human-readable 2 010 -w- OR | 4 100 r-- = 6 110 rw- If I now decide: I’d like to have the file’s user also execute the file, I can OR the existing permission, with the execute permission ( 1 ): bitwise octal binary human-readable 6 110 rw- OR | 1 001 --x = 7 111 rwx

Setting Permissions ¶ You can set any permissions (AKA, the file mode) via the chmod (i.e., ch ange mod e) command. You can use either an octal mode in chmod or you can use human-readable permissions along with u , g , o , and a to refer to u ser that owns the file, users in the file’s g roup, o thers, and a ll to refer to all 3. With chmod you can add ( + ) permissions, remove permissions ( - ), or set permissions directly ( = ) for any set of users. The format of that command is: chmod [options] [[ugoa][+-=][human-readable],...] <file> For example, to remove all permissions from a file, you could mark all sets of users as having no permissions: $ chmod u=,g=,o= tmp/foo $ stat --format '%A' tmp/foo ---------- Then you could add back read for the file’s u ser: $ chmod u+r foo $ stat --format '%A' foo -r-------- When adding permissions, chmod uses the logical OR to combine file permissions with additional permissions you add. In the example above, we added the read permission: bitwise octal binary human-readable 0 000 --- OR | 4 100 r-- = 4 100 r-- We could have achieved the same result by specifying the octal mode for r ead for the user that owns the file, i.e. 400 : $ chmod 400 foo $ stat --format '%A' foo -r-------- You can also remove permissions from sets of users with chmod . For instance, to remove the ability of a ll users to e x ecute a file we could use a-x ; i.e.: $ stat --format '%A' foo -rwxr-xr-x $ chmod a-x foo $ stat --format '%A' foo -rw-r--r-- Removing permissions is done via a logical abjunction: bitwise octal binary human-readable NOT ~ 111 001 001 001 u=--x,g=--x,o=--x = 666 110 110 110 u=rw-,g=rw-,o=rw- bitwise octal binary human-readable 755 111 101 101 u=rwx,g=r-x,o=r-x AND & 666 110 110 110 u=rw-,g=rw-,o=rw- = 644 110 100 100 u=rw-,g=r--,o=r--

Umask ¶ Another aspect of permissions to consider is the umask of a process. Umask is a per-process value that tells mkdir , open , and other file-creation calls what permissions to take-away. Umask is defined in POSIX as both a built-in for the shell and a function . Both are used to read and set a umask. The umask for a system is typically set in /etc/profile and may optionally be overridden in various initialization files or in process using a call to the umask utility or function. There are 3 octal digits in a umask and the default umask for many systems is 022 . To determine the permissions that a file or directory will be created with we take the logical abjunction of the default creation mode for files or directories and the umask. For directories, the default creation mode is 777 . When mkdir is called, the logical abjunction of 777 and 022 determines the new directory’s permissions: bitwise octal binary human-readable NOT ~ 022 000 010 010 u=,g=w,o=w = 755 111 101 101 u=rwx,g=rx,o=rx bitwise octal binary human-readable 755 111 101 101 u=rwx,g=rx,o=rx AND & 777 111 111 111 u=rwx,g=rwx,o=rwx = 755 111 101 101 u=rwx,g=rx,o=rx For files, the default creation mode is 666 . When a user creates a file using touch , the logical abjunction of 666 and 022 determines the new file’s permissions: bitwise octal binary human-readable 755 111 101 101 u=rwx,g=rx,o=rx AND & 666 110 110 110 u=rw,g=rw,o=rw = 644 110 100 100 u=rw,g=r,o=r

History ¶ Basic permissions for file owners and other users of a system were in-place in UNIX from the beginning – PDP-7 era – circa 1970. Owner and other-user permissions actually pre-date UNIX and were a part of Multics. According to McIlroy the concepts of a file owner and other users acting on files were common 10 years prior to the first UNIX. In late 1973, version 4 of UNIX was released. This release is when group file permissions first show up in the chmod man page. Oddly (to me anyway) the setuid concept pre-dates the concept of group permissions by 3 years or so – it’s described by Ritchie in early UNIX manuals – and goes all the way back to PDP-7 UNIX. Dennis Ritchie was granted a patent for setuid in 1973.