Linux Fundamentals, March 2022

File Permissions and Ownership, the Real Model

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

BitOn a fileOn a directory
rRead the contentsList the names inside
wModify the contentsCreate, rename, or delete entries
xExecute itEnter 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.

BitApplied toEffect
setuidExecutableRuns as the file owner rather than the caller
setgidExecutableRuns as the owning group
setgidDirectoryNew entries inherit the directory's group
stickyDirectoryOnly 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.