Permission Bits and umask
Topic 25

Permission Bits and umask

PermissionsConcept

Every file and directory carries nine permission bits, grouped into three triads — owner, group, other — each holding read, write, and execute. The kernel checks exactly one triad per access, picked by who is asking, and grants or denies based on those three bits alone. chmod sets the bits in either octal (640) or symbolic (u=rw,g=r,o=) form; umask decides which bits a newly created file gets stripped of before it ever exists.

The model is small enough to memorize in a minute, and that is exactly the trap. Execute means something completely different on a directory than on a file, the triads are evaluated first-match rather than additively, and a wrong umask on a service account can leave every log and config it writes world-readable. Getting these three details wrong is how secrets leak and how people lock themselves out of their own directories.

The rwx Triads

A long listing shows the bits as a ten-character string. The first character is the file type (- regular, d directory, l symlink); the remaining nine are three triads of rwx for owner, group, and other, in that order. A dash in any slot means the bit is off.

# type | owner | group | other
$ ls -l app.conf
-rw-r----- 1 www-data www-data 418 May 30 12:01 app.conf
# rw- for owner, r-- for group, --- for other

Each bit means something concrete. On a regular file, r permits reading the contents, w permits modifying them, and x permits running the file as a program. The owner triad applies to the file's owning user, the group triad to members of the owning group, and the other triad to everyone else on the system.

Octal and Symbolic Notation

Octal notation packs each triad into a single digit by summing read (4), write (2), and execute (1). So rw- is 6, r-- is 4, and r-x is 5; the file above is mode 640. Octal is absolute — it sets all three triads at once and overwrites whatever was there. Symbolic notation is relative and surgical: it names the targets (u, g, o, a) and the operation (+ add, - remove, = set exactly).

$ chmod 640 app.conf          # absolute: owner rw, group r, other none
$ chmod u=rw,g=r,o= app.conf  # identical result, written symbolically
$ chmod g+w app.conf          # add group write, leave everything else alone
$ chmod -R o-rwx /srv/secret   # strip all 'other' access, recursively

Reach for octal when you want a known end state regardless of the current bits — deployment scripts and Ansible's mode: "0640" both rely on that determinism. Reach for symbolic when you want to flip one bit without disturbing the rest, which is safer on a tree where files legitimately differ.

Execute on Files versus Directories

The execute bit is the part of the model that catches everyone, because it means two unrelated things. On a regular file, x means "may be run as a program." On a directory, x means "may traverse" — may enter it and access entries by name. Without x on a directory, you cannot cd into it, cannot stat a file inside it, and cannot open any file by its full path, no matter what that file's own permissions say.

Read and execute on a directory are likewise separable. r alone lets you list the names (ls) but not access the entries; x alone lets you reach a known entry by name but not enumerate the directory. The practical default for a directory you want usable is r-x (octal 5), and for one you can also create files in, rwx (7).

$ chmod 750 /srv/app        # owner full, group enter+list, other locked out
$ sudo -u nobody cat /srv/app/app.conf
cat: /srv/app/app.conf: Permission denied   # blocked by the directory, not the file

umask and Default Modes

New files do not appear with all bits set. The process creating a file requests a mode — typically 666 for files and 777 for directories — and the kernel removes whatever bits the umask has turned on. The umask is a mask of bits to clear, so it subtracts: a umask of 022 turns a requested 666 into 644 and a requested 777 into 755. Files never get execute from this path, because the standard request already lacks it.

$ umask
0022
$ touch a.txt && ls -l a.txt
-rw-r--r-- ...                # 666 minus 022 = 644
$ umask 027 && touch b.txt && ls -l b.txt
-rw-r----- ...                # 666 minus 027 = 640, group read-only, other nothing

The common values encode an access policy. 022 is the desktop and general-server default — everyone can read, only the owner writes. 027 keeps files readable within the owning group but invisible to other; it is the right default for a service account whose files no unrelated user should see. 077 is the strictest — owner only — and belongs to accounts handling keys, tokens, or personal data. Set it in the service's systemd unit (UMask=0027) rather than relying on the inherited login shell, because daemons do not source ~/.profile.

Effective Access Evaluation

When a process touches a file, the kernel does not blend the triads. It picks exactly one: if the process's effective UID owns the file, the owner triad decides and nothing else is consulted; otherwise, if the process's effective or supplementary GID matches the file's group, the group triad decides; otherwise the other triad applies. First match wins, and the search stops there.

This is why permissions can be counterintuitive. If you own a file with mode 047 (---r--rwx), you — the owner — get nothing, even though group and other are wide open, because the owner triad matched first and granted no access. Permissions restrict the owner just as readily as they restrict strangers; ownership is not a privilege override. Only root bypasses the bits entirely, which is a property of root, not of the permission model.

Execute on a File vs a Directory

x on a file — permission to run it as a program: a binary the kernel loads, or a script the kernel hands to the interpreter named in its shebang. A data file never needs x; setting it does nothing useful and muddies audits.

x on a directory — permission to traverse it: to enter it and resolve names inside it. It governs path access, not listing. A directory with r but no x lets you read the list of names yet denies access to every entry; a directory with x but no r lets you reach a file whose name you already know but hides the listing. Usable directories need x.

Common Mistakes
  • chmod 777 "to make it work" — this grants write to every account on the host, so any user or compromised service can replace the file's contents or, on a directory, drop files into it. It almost always masks a real ownership problem that chown should fix.
  • Stripping x from a directory with a recursive chmod -R 644 meant for files — it removes traverse from every subdirectory, and you can no longer cd into your own tree until you restore x with something like chmod -R a+X.
  • Leaving a service account on the default 022 umask while it writes logs and config that contain tokens — every file lands 644, world-readable, and any local user can read the secrets.
  • Assuming the group and other triads add up — a file mode of 604 gives a group member nothing (group triad is ---), even though other has read, because evaluation stops at the first matching triad.
  • Using lowercase chmod -R a+x to "fix" a tree — it marks every data file executable too. Use a+X (capital), which adds execute only to directories and to files that already have an execute bit set.
  • Expecting chmod 600 on a sensitive file to protect it while the parent directory is 755 — the file is safe, but if the directory is also writable by others they can rename or delete the file regardless of its own mode.
Best Practices
  • Set the tightest mode that still works: 640 for config a group must read, 600 for secrets, 644 for genuinely public data. Never reach for 777 — if access fails, fix ownership with chown first.
  • Set UMask=0027 (or 0077 for secret-handling daemons) directly in the systemd service unit, because a daemon never sources the login shell's umask.
  • Use chmod -R a+X with a capital X when restoring traverse on a tree, so directories regain x without turning every data file into an executable.
  • Choose octal in automation (install -m 640, Ansible mode: "0640") for a deterministic end state, and symbolic interactively when flipping a single bit on a mixed tree.
  • Audit for over-broad modes with find /srv -perm -o+w -type f to surface world-writable files and find / -perm -o+r in secret directories to catch readable leaks.
  • Protect the directory, not just the file: a 600 key inside a 700 directory is genuinely private; the same key inside a group-writable directory can still be renamed out from under you.
Comparable toolsWindows — NTFS ACLs, a richer per-principal model with inheritance and explicit deny, set via icacls; no rwx-triad equivalentPOSIX ACLsgetfacl/setfacl on Linux for per-user entries when three triads are too coarsemacOS — the same Unix permission bits plus an additional extended-ACL layer

Knowledge Check

A directory has mode r-- for your account (read, no execute). What can you actually do with it?

  • List the entry names with ls, but you cannot cd in or access any file inside, because traverse requires the execute bit
  • Both list the names and read the files inside, since the read bit on a directory recursively covers the entire subtree beneath it
  • Nothing at all — a directory needs the execute bit set before its read bit has any effect whatsoever, so listing fails too
  • Enter the directory and open files directly by name, but you cannot produce a listing of everything it actually contains

A service account writes config files containing API tokens. Which umask should its systemd unit set, and why?

  • 0077 — it strips all group and other bits, so new files land owner-only and no other local account can read the tokens
  • 0022 — the standard server default, which is already safe enough here because daemons are well isolated from other local accounts
  • 0777 — to guarantee the service account can always write its own config files without ever hitting a permission error
  • 0000 — so files inherit the maximum requested mode and the service account is never once blocked from writing

You own a file with mode 047 (---r--rwx). Why can't you read it, even as the owner?

  • The kernel evaluates first-match: you are the owner, so only the owner triad applies — and it grants nothing, regardless of the open group and other bits
  • The three triads are summed together bit by bit, and the owner's leading zero bits cancel out the read permission that the group and other triads would otherwise grant
  • Owners are always held to the strictest of the three triads as a deliberate safety default, so the empty owner triad wins here
  • A mode like 047 is invalid because the owner is more restricted than other, so the kernel refuses to honor it and denies all access until you correct it

Why is chmod -R a+X (capital X) preferred over chmod -R a+x when restoring access to a directory tree?

  • Capital X adds execute only to directories and to files that already have an execute bit, so it restores traverse without marking every data file executable
  • Capital X is simply the recursive form of lowercase x and behaves identically to it on any single file you happen to pass it
  • Capital X sets the execute bit only for the owner triad on every entry, leaving the group and other permission classes entirely untouched right across the directory tree
  • Capital X strips the execute bit from regular files while adding it to directories, fixing the whole tree in a single pass

You got correct