Linux has always been built with multiple users in mind. From the very beginning, its design assumed that many people would be sharing the same system — and that not everyone should be able to touch everything. The mechanism that enforces this is Discretionary Access Control (DAC), built into the kernel through file permissions and ownership rules.
For anyone working in cybersecurity, knowing how these permissions work is not optional. Misconfigured file permissions are one of the most common ways attackers escalate privileges or walk off with data they shouldn’t have. Whether you’re hardening a server, auditing a system, or just trying to understand why a script won’t run, this is foundational knowledge.
Let’s break it all down — from how the permission string is read, to the special bits that trip people up, to the audit commands you should be running regularly.
Understanding the Permission String
In Linux, everything is a file — regular files, directories, sockets, device handles, all of it. Access rules for each are stored in the file’s inode, a metadata structure the kernel maintains. When you run ls -l, that first column of characters is your window into those rules.
(-, d, l, c, b, s, p)"]:::accent Root --> Owner["Owner (User)
r w x — Octal 7"]:::accent Root --> Group["Group Access
r - x — Octal 5"]:::accent Root --> Others["Others (World)
r - - — Octal 4"]:::accent Owner --> SUID["Special Bit: SUID (s)
Runs as File Owner"]:::accent Group --> SGID["Special Bit: SGID (s)
Runs as Group / Inherits Group"]:::accent Others --> Sticky["Special Bit: Sticky (t)
Only Owner Can Delete"]:::accent
The 10-character string breaks down like this:
-
Character 1 — File Type: Tells you what kind of object this is.
-: Regular filed: Directoryl: Symbolic linkc: Character device (e.g.,/dev/tty)b: Block device (e.g.,/dev/sda)s: Local socketp: Named pipe (FIFO)
-
Characters 2–4 — Owner Permissions: What the file’s owner can do.
-
Characters 5–7 — Group Permissions: What members of the file’s group can do.
-
Characters 8–10 — Others Permissions: What everyone else on the system can do.
Each of those three-character blocks can contain Read (r), Write (w), and Execute (x) — or a dash if that permission isn’t granted.
From Bits to Octal: How Numbers Map to Permissions
Under the hood, Linux represents permissions as binary bits — each permission is either on (1) or off (0). Knowing this makes octal notation feel much less arbitrary:
- Read (r) = binary
100= 4 - Write (w) = binary
010= 2 - Execute (x) = binary
001= 1
Add them up for each segment and you get a single digit representing that category’s access level:
| Octal | Permissions | Symbolic | Binary |
|---|---|---|---|
| 0 | None | --- | 000 |
| 1 | Execute only | --x | 001 |
| 2 | Write only | -w- | 010 |
| 3 | Write + Execute | -wx | 011 |
| 4 | Read only | r-- | 100 |
| 5 | Read + Execute | r-x | 101 |
| 6 | Read + Write | rw- | 110 |
| 7 | Full access | rwx | 111 |
Take rwxr-xr-x as an example:
- Owner:
rwx= 4 + 2 + 1 = 7 - Group:
r-x= 4 + 0 + 1 = 5 - Others:
r-x= 4 + 0 + 1 = 5 - Result: 755
Common Permission Modes in Practice
chmod 777 file— Everyone gets full access. Never use this in production.chmod 755 script.sh— Owner can modify and run it; everyone else can read and execute it.chmod 644 config.txt— Owner can read and edit; others can only read.chmod 600 id_rsa— Only the owner can read and write. Essential for SSH private keys.chmod 700 private_dir— Only the owner can enter the directory at all.
[!WARNING] A common mistake: thinking
chmod 700means read-only for others. It doesn’t —rwx------means group and world users have zero access, not read-only access. If you want full access for the owner and read/execute for everyone else, use755. For files where execute isn’t needed, use744.
Changing Permissions and Ownership
chmod — Two Ways to Use It
You can set permissions using octal numbers (as above) or symbolic notation, which targets specific categories and makes incremental changes easier to reason about.
Symbolic notation uses scope letters — u (user/owner), g (group), o (others), a (all) — combined with operators (+ to add, - to remove, = to set exactly).
| Command | What It Does |
|---|---|
chmod +x run.sh | Adds execute permission for everyone |
chmod u-w config.cfg | Removes write permission from the owner |
chmod o=r public.txt | Sets others to read-only, removing any other access they had |
chmod u+wx,g-x,o=rx file | Mixed update across all three categories in one command |
chown and chgrp — Changing Who Owns the File
Only root (or a user with sudo) can change file ownership.
# Change the owner
sudo chown alice database.db
# Change the group
sudo chgrp security database.db
# Change both at once
sudo chown alice:security database.db
# Apply recursively to a directory and all its contents
sudo chown -R alice:security /var/www/html/
Special Permission Bits: SUID, SGID, and the Sticky Bit
Standard permissions cover most situations, but Linux has three additional bits that modify how execution works. These come up constantly in security assessments, so it’s worth understanding them well.
SUID — Set User ID
- Octal value:
4000(shows assin the owner’s execute position:rws------) - When set on an executable, it runs with the file owner’s privileges, not the caller’s.
- The classic example is
/usr/bin/passwd— it’s owned by root and SUID-flagged, which is how a regular user can update their own password entry in/etc/shadowwithout having root access themselves. - The security risk is real: if a binary that can drop into a shell (like
find,vim, ornmap) is SUID root, any local user can use it to run commands as root. This is a staple of privilege escalation.
SGID — Set Group ID
- Octal value:
2000(shows assin the group’s execute position) - On files: the binary executes with the owning group’s permissions.
- On directories: files created inside inherit the directory’s group, not the creator’s primary group. This is useful for shared project folders where you want consistent group ownership on all new files.
Sticky Bit
- Octal value:
1000(shows astin the others’ execute position:rwxrwxrwt) - On a directory, only the file’s owner, the directory’s owner, or root can delete or rename files inside — even if the directory itself is world-writable.
- The
/tmpdirectory (drwxrwxrwt) is the canonical example. Anyone can write there, but no one can delete someone else’s files.
| Special Bit | Octal | File Effect | Directory Effect |
|---|---|---|---|
| SUID | 4000 | Runs as file owner | N/A |
| SGID | 2000 | Runs as file group | New files inherit parent group |
| Sticky Bit | 1000 | N/A | Only owners can delete |
To set these:
chmod u+s executable_file # SUID
chmod g+s shared_directory # SGID
chmod +t shared_directory # Sticky bit
chmod 4755 script # SUID + rwxr-xr-x in one command
Hardening Default Permissions
umask — Setting Sane Defaults at Creation Time
Every time a file or directory is created, the system needs a starting point for permissions. That’s what umask does — it subtracts from the maximum default templates:
- File template:
666(rw-rw-rw-) - Directory template:
777(rwxrwxrwx)
With the standard umask of 022:
- New files: 666 minus 022 = 644 (
rw-r--r--) - New directories: 777 minus 022 = 755 (
rwxr-xr-x)
For environments that handle sensitive data, tighten the umask to 027:
- New files: 666 minus 027 = 640 (no access for others at all)
- New directories: 777 minus 027 = 750
For strict isolation, use 077:
- New files: 600 (owner read/write only)
- New directories: 700 (owner only)
# Check your current umask
umask
# Set it temporarily
umask 027
To make it permanent, add umask 027 to /etc/profile (system-wide) or ~/.bashrc / ~/.zshrc (per user).
File Attributes: Protection Beyond Permissions
Standard permissions, even when set correctly, can still be overridden by root. Filesystem-level attributes go further.
Immutable bit (+i) — No one, not even root, can modify, rename, append to, or delete the file:
sudo chattr +i /etc/resolv.conf
lsattr /etc/resolv.conf
sudo chattr -i /etc/resolv.conf # Remove it
Append-only bit (+a) — Data can only be added to the end of the file; existing content can’t be altered or deleted. This is ideal for log files you want to protect from tampering:
sudo chattr +a /var/log/secure_audit.log
Access Control Lists — Granular Per-User Rules
Sometimes the owner/group/other model isn’t fine-grained enough. What if you need to give a single user read access to a file, but they’re not the owner and not in the group? That’s what POSIX ACLs are for.
# See current ACLs on a file
getfacl sensitive_report.txt
# Grant read-only access to a specific user
setfacl -m u:bob:r sensitive_report.txt
# Revoke that access
setfacl -x u:bob sensitive_report.txt
Security Audit: What to Check Regularly
These commands should be part of any standard system audit or hardening review.
Find SUID Binaries
find / -perm -4000 -type f 2>/dev/null
Cross-reference the results against GTFOBins — a curated list of binaries that can be abused when they carry SUID.
Find SGID Binaries
find / -perm -2000 -type f 2>/dev/null
Find World-Writable Files
These should almost never exist outside /tmp and similar scratch directories:
find / -perm -o+w -type f ! -path "/proc/*" ! -path "/sys/*" 2>/dev/null
Find Orphaned Files
When a user account is deleted, their files remain — owned by a UID that no longer maps to anyone. If a new user is later created with the same UID, they automatically inherit those files:
find / -nouser 2>/dev/null
find / -nogroup 2>/dev/null
Verify Critical Config Files
# /etc/passwd should be world-readable but only root-writable
ls -l /etc/passwd # Expected: -rw-r--r-- (644)
# /etc/shadow holds password hashes — no world access at all
ls -l /etc/shadow # Expected: -rw-r----- (640) or -r-------- (400)
Building these checks into your configuration management pipeline — whether that’s Ansible, Chef, or a simple cron job running a shell script — means misconfigurations get caught before they become incidents.