The permission model looks simple and contains two behaviours that regularly catch people out. The first is that the same bits mean different things on a directory than on a file. The second is that the three classes are evaluated in order and the first applicable one wins outright.
The same bits, two meanings
| Bit | On a file | On a directory |
|---|---|---|
| r | Read the contents | List the names inside |
| w | Modify the contents | Create, rename, or delete entries |
| x | Execute it | Enter it and access what is inside |
The directory column explains several confusing situations. Execute without read allows access to a known filename while preventing listing. Read without execute allows listing names while denying access to any of them.
The write row is the more surprising. Deleting a file is a modification of the directory containing it, not of the file, so a read only file in a writable directory can be removed by someone with no permission on the file at all.
First match wins
Access is decided by checking whether the requester is the owner, then whether they are in the owning group, then falling through to everyone else. The first category that applies is used and the others are ignored.
The consequence is that permissions do not accumulate. A file owned by a user with no owner permissions but full group permissions is inaccessible to that user, even though they are in the group, because the owner check matched first and denied it.
The three special bits
Beyond the nine there are three more, and each solves a specific problem.
| Bit | Applied to | Effect |
|---|---|---|
| setuid | Executable | Runs as the file owner rather than the caller |
| setgid | Executable | Runs as the owning group |
| setgid | Directory | New entries inherit the directory's group |
| sticky | Directory | Only the owner of an entry may remove it |
The setgid behaviour on directories is the practical one for shared workspaces, because it keeps group ownership consistent without relying on every user setting it correctly. The sticky bit is what makes a world writable temporary directory usable without letting users delete each other's files.
Why setuid deserves suspicion
A setuid program owned by the administrator runs with administrative authority regardless of who started it, which is how ordinary users are permitted to change their own password.
It is also the most direct route to privilege escalation, because any flaw in such a program is a flaw executing with elevated rights. Auditing which files carry the bit is a standard hardening step, and the correct number on a modern system is small.
What the creation mask does
New files do not receive the permissions the creating program requests. A mask subtracts bits from that request, which is why files typically appear without write permission for group and others even when the program asked for it.
The mask is inherited from the shell or service that started the process, which means the same program can create files with different permissions depending on how it was launched. That is a common cause of files being unexpectedly private or unexpectedly readable.
Where the model runs out
Three classes cannot express access for two groups with different rights, and the traditional answer was to create more groups until the model fitted.
Access control lists remove that constraint by attaching arbitrary user and group entries to a file. They are not visible in a standard listing beyond a small marker, which is why a file can appear to deny access while permitting it, and is worth checking when permissions seem to contradict observed behaviour.
The two habitual mistakes
Setting everything world writable to make a problem go away creates a larger problem and rarely fixes the original, which is usually ownership or a missing execute bit on a parent directory.
Applying permissions recursively across a tree is the other, because files and directories need different bits. Removing execute from directories in the process makes the entire tree inaccessible, including to the person who ran the command.
Note: when access fails unexpectedly, check the parent directories before the file. Execute permission is required on every directory in the path, and a missing bit several levels up produces an error that appears to be about the file.