Nine bits, three more, and why a directory outranks a file
◈ 9 cardsReading a 12-bit UNIX mode; what read, write and execute mean on a directory; SetUID, SetGID and the sticky bit; and how a 644 file is destroyed inside a 730 directory.
The inode, and the twelve bits inside it
Every UNIX file is described by an inode holding its attributes, its owner, its group and its permission bits. Several names may point at one inode, but an active inode is exactly one file. And the fact this whole lesson turns on: a directory is just a file whose contents are a list of names paired with inode numbers.
Twelve protection bits live in the mode. Nine of them are three triples of read, write, execute, applied to owner, group, other in that order — one octal digit each. The remaining three sit above them: SetUID, SetGID and the sticky bit.
Read the octal off by expanding each digit into three bits, where r = 4, w = 2, x = 1:
644 = 110 100 100 = rw- r-- r--
730 = 111 011 000 = rwx -wx ---
Mode 644 is a perfectly ordinary data file: the owner reads and writes it, everybody else may only read. Mode 730 is a much stranger animal: the owner has everything, the group has write and execute but not read, and other has nothing at all.
Stop on that last clause, because it collides with the previous lesson. In the abstract access matrix, write is defined to include read — a subject able to alter an object can trivially observe it, and that inclusion is examinable. UNIX does not work that way. Its nine bits are independent flags, tested one at a time by the kernel with no implication running between them, which is exactly why -wx is legal and commonplace: the group may create, delete and traverse, and may not list. Both claims are true of their own model. Carry the inclusion rule into a chmod question and you will get the wrong answer; carry the independent-bits rule into an access-matrix question and you will lose the mark for the inclusion. Read the bits when a mode is on the page; apply the inclusion when a matrix is.
The nine bits mean something different on a directory
This is the trapdoor. On a directory, the same three letters mean:
- read — list the names inside it. Nothing more.
- write — create, rename and delete entries. Nothing about the contents of any file inside.
- execute — traverse it: resolve a name through it,
cdinto it, open a path that passes through it.
Deletion is the one to burn in. Unlinking a name is an edit to the directory, not to the file. The file's own mode is not consulted, because the file is not what is being modified — the list of names is. A file whose mode says r--r--r-- is removed without complaint by anyone holding write on the directory that names it.
Worked example — mode 644 inside mode 730
So: a file with mode 644, owned by alice, sitting inside a directory with mode 730 whose group is staff. Take bob, a member of staff, and work the bits.
On the directory (rwx -wx ---), bob falls in the group class, so he holds -wx:
- no r — he cannot
lsthe directory. He getsPermission denied. - w — he can create, rename and delete entries in it.
- x — he can traverse it and resolve a name through it.
On the file (rw- r-- r--), bob is again in the group class, so he holds r--: he may read the contents, and he may not write them.
Now put the two together. Bob cannot open the file for writing — the file's own mode forbids it, exactly as its owner intended. But he can:
- Read it, if he knows its name. That is already a confidentiality exposure, courtesy of the
rin the file's group triple. - Delete it, because deletion is a directory operation and he holds
won the directory. - Create a new file with the same name, because creation is also a directory operation.
Steps 2 and 3 together are the compromise. The original file is gone and a file bob authored stands in its place, under the same name, with alice's programs and scripts still pointing at it. The file's integrity has been destroyed by someone who never had permission to write it.
The missing read bit on the directory is not a defence — it only means bob must already know the filename, and in the systems where this bites (a build output, a config file, a shared report) the names are entirely predictable. It is worth reproducing this at a real shell before you write about it: with a directory set to -wx for your class, ls is refused while cat known_name succeeds and a new entry can be created in the same breath.
SetUID, SetGID and the sticky bit
The three high bits:
- SetUID on an executable makes it run with the effective user ID of the file's owner for the duration of that execution. This is how an unprivileged user changes their own password:
passwdis owned by root and SetUID, so it runs with root's rights over/etc/shadow. It is also the standing danger of the UNIX model — any SetUID-root program that can be made to misbehave hands its attacker unrestricted access, which is the bridge from this module to buffer overflow. - SetGID does the same for the group. On a directory, it means something else entirely: new files created inside inherit the directory's group rather than the creator's. SetUID on a directory is simply ignored.
- The sticky bit on a directory restricts deletion: only the file's owner (or root) may rename, move or delete an entry, no matter who holds write on the directory. That is exactly the countermeasure for the attack above, and it is why a shared
/tmpis mode1777— everyone may create, nobody may remove anybody else's work.
One blanket exception spans all of it: the superuser is exempt from file access control entirely.
So the fix for the 730 directory is one of two moves. Either add the sticky bit (chmod 1730), which leaves the group able to create but not to remove alice's file, or tighten the directory to 750, which takes the group's write bit away and leaves them read and traverse. Which of the two you pick is a question about what the group is for.