04 — Files, Directories & Permissions
Linux permissions control who can read, write, and execute files, and who owns them. This is the foundation of filesystem-level security. This chapter covers ownership, permission bits, special bits, ACLs, capabilities, and the traps that cause "permission denied" mysteries.
Ownership: Users and Groups
Every file has an owner (user, by UID), a group (by GID), and permissions for three classes: owner (user), group, and others.
$ ls -l /etc/passwd
-rw-r--r-- 1 root root 3218 Jun 15 10:23 /etc/passwd
└──┘└─┘└─┘ └──┘ └──┘
│ │ │ │ └ group (root)
│ │ │ └ owner (root)
│ │ └ permissions for others (r--)
│ └ permissions for group (r--)
└ permissions for owner (rw-)
Permission Bits — Meaning by File Type
| Bit | Regular File | Directory |
|---|---|---|
r | Read contents (cat) | List entries (ls) |
w | Modify contents (echo >) | Create/delete/rename entries inside |
x | Execute (run as program) | Enter (cd) / traverse (use in a path) |
Key difference: directory x is "traverse/search" permission, not "execute." Without x on a directory, you can't cd into it or access files inside it by path — even if you know their names. r lets you list; x lets you actually access.
# Complex Implementation: demonstrate r vs x on directories
# r but no x: can list names, can't access content
chmod 444 somedir/
ls somedir/ # works (lists names)
cat somedir/file # Permission denied (no x to traverse)
# x but no r: can access by name, can't list
chmod 111 somedir/
cat somedir/knownfile # works (if you know the exact name)
ls somedir/ # Permission denied (can't list)
Numeric (Octal) Mode
Each triplet sums to 0–7:
| Octal | Symbolic | Meaning |
|---|---|---|
| 0 | --- | No access |
| 1 | --x | Execute/traverse |
| 2 | -w- | Write |
| 3 | -wx | Write + execute |
| 4 | r-- | Read |
| 5 | r-x | Read + execute |
| 6 | rw- | Read + write |
| 7 | rwx | All |
chmod 644 file # rw-r--r-- (typical file)
chmod 755 file # rwxr-xr-x (typical executable/dir)
chmod 600 file # rw------- (private file, e.g., SSH key)
chmod 700 dir # rwx------ (private directory)
Special Permission Bits
Setuid (4000) — Run as Owner
When set on an executable, it runs as the file's owner regardless of who invokes it:
ls -l /usr/bin/sudo
# -rwsr-xr-x 1 root root ... /usr/bin/sudo
↑
setuid (s in owner execute position)
chmod 4755 program # setuid + 755
chmod u+s program # symbolic
# Security audit — find all setuid root binaries:
find / -perm -4000 -type f 2>/dev/null
Setgid (2000) — Inherit Group on Directories
On a directory: new files/dirs inside inherit the directory's group (instead of the creator's primary group). Essential for shared directories:
# Complex Implementation: collaborative directory with setgid + default ACL
sudo mkdir /shared
sudo chown :developers /shared
sudo chmod 2775 /shared # setgid + rwxrwsr-x
# Now all new files in /shared inherit the "developers" group
# Add default ACL so new files also get group write (regardless of umask):
sudo setfacl -d -m g:developers:rw /shared
Sticky Bit (1000) — Restricted Deletion
On a directory, the sticky bit means only the file's owner (or the directory's owner, or root) can delete/rename files inside — even if others have write permission. The canonical use is /tmp:
ls -ld /tmp
# drwxrwxrwt 20 root root 4096 ... /tmp
↑
sticky bit (t in others execute position)
chmod 1777 /tmp # sticky + 777
Without the sticky bit, since /tmp is world-writable (777), any user could delete any other user's temp files.
umask — Default Permissions
umask is a mask (bits to remove) from the default mode:
- Files default to
666(rw-rw-rw-). - Directories default to
777(rwxrwxrwx).
umask 077 # files: 600, dirs: 700 (private)
umask 022 # files: 644, dirs: 755 (default on most systems)
umask 002 # files: 664, dirs: 775 (shared group, e.g., team dirs)
Calculation: mode = default & ~umask. umask 022 → files 666 & ~022 = 644, dirs 777 & ~022 = 755.
Edge Case: umask Doesn't Add Execute
Files are created 666 & ~umask — never 777. So umask 022 gives files 644 (not executable), dirs 755. You must chmod +x scripts after creating them.
ACLs (Access Control Lists)
Traditional permissions have only three classes. ACLs allow per-user or per-group permissions:
::code-wrapper{language="language="bash"}
# Grant alice read-write, developers group read
setfacl -m u:alice:rw file
setfacl -m g:developers:r file
# Set default ACL on a directory (inherited by new files)
setfacl -d -m g:developers:rw /shared
# View
getfacl file
# Remove all ACLs
setfacl -b file
::
In ls -l, a + after the mode indicates an ACL is present: -rw-rw-r--+.
Edge Case: ACLs Lost on Copy
# NAIVE: cp strips ACLs and capabilities
cp file /backup/file # ACLs gone, capabilities gone
# PRODUCTION: preserve everything
cp -a file /backup/file # preserves mode, owner, timestamps, ACLs
rsync -A -X file /backup/ # -A ACLs, -X xattrs (full preservation)
File Capabilities
Instead of setuid root, grant a binary specific capabilities (fine-grained privileges):
# Complex Implementation: allow a binary to bind to port 80 without root
# — safer than setuid (grants only one capability, not all root powers)
sudo setcap 'cap_net_bind_service+ep' /usr/bin/myapp
getcap /usr/bin/myapp # verify
sudo setcap -r /usr/bin/myapp # remove
# Anti-Pattern: setuid root for the same purpose
# sudo chmod u+s /usr/bin/myapp → grants ALL root privileges (dangerous)
Capabilities are stored in the security.capability extended attribute.
chown — Change Owner
sudo chown alice file # change owner
sudo chown alice:staff file # change owner and group
sudo chown :staff file # change group only (or use chgrp)
sudo chown -R alice:alice dir/ # recursive
Only root can change the owner (chown). A regular user can chgrp to a group they belong to.
💡 Tips & Tricks
- Idiom: use
install -m 755 script /usr/local/bin/to copy + set permissions in one step —installsets the mode explicitly (ignoresumask), so the result is predictable. Better thancp+chmod. - Idiom: use
chmod -Rwith symbolic modes, not octal —chmod -R g+rw diradds group read/write without touching other bits.chmod -R 644 dirwould remove execute from everything (including subdirs, breakingcd). - Idiom: set
umask 077for private systems andumask 027for shared —077makes all new files600/700(only owner).027allows group read. - Idiom: prefer file capabilities over setuid root —
setcap cap_net_bind_service+ep ./serverlets a binary bind to port 80 without running as root. - Debug: use
namei -l /path/to/fileto see permissions on each component of a path — reveals when a "Permission denied" is due to a parent directory lackingx, not the file itself.
⚠️ Edge Cases & Gotchas
- Directory
x≠ execute: on directories,xis "search/traverse" — you need it tocdin or access files by path.ralone lets you list names but not access content.xalone lets you access known files but not list. rmdoesn't use the trash: deletion is permanent. There's no undo.rm -rf $UNSET_VAR/withset +ucan delete/. Alwaysset -uin scripts.cpsilently overwrites:cp important.conf old.conf.bak— ifold.conf.bakexists, it's replaced with no warning. Usecp -n(no-clobber) orcp -i(interactive).- Symlink permissions are meaningless:
chmodon a symlink changes the target's permissions (on Linux). Symlinks always showlrwxrwxrwxinls -l. - setuid on scripts is ignored (on Linux):
chmod u+s script.shhas no effect on shell scripts — the kernel ignores setuid on interpreted scripts (a security measure). Usesudoor a setuid C wrapper instead. - Deleting a file requires write on the directory, not the file: a file with
r--r--r--(no write for anyone) can be deleted if its directory is writable by the user. The sticky bit (+t) prevents deleting others' files in a shared dir. - Hard links to directories are forbidden (on most filesystems):
ln dir/ linkfails — ext4 disallows it to avoid filesystem loops. Useln -s(symlink) for directories.
🧠 Spot the Bug
A team sets up a shared directory. They run:
sudo mkdir /shared
sudo chown :developers /shared
sudo chmod 777 /shared
Alice creates /shared/report.txt. Bob can read it but can't edit it. The team wanted all developers to edit each other's files. What's wrong, and what are two fixes?
Answer
The problem: files created in /shared are owned by the creator's primary group (e.g., alice, bob), not developers. So Alice's file is alice:alice, and Bob (not in the alice group) only has others permissions — typically r (from umask 022 → 644). No group write for Bob.
Fix 1 — setgid directory (classic Unix way):
sudo chmod 2770 /shared # setgid + rwxrws---
Now new files inherit the developers group. Combined with umask 002 (gives group write: 664), Bob can edit Alice's files.
Fix 2 — default ACL (more robust, doesn't depend on umask):
sudo setfacl -d -m g:developers:rw /shared
sudo setfacl -m g:developers:rwx /shared
New files automatically get group:developers:rw, regardless of the creator's umask. This is the modern approach and works even if users keep umask 022.
The key insight: directory write permission lets you create/delete files, but it doesn't grant write on files created by others — that depends on each file's own group and permissions.