Computer & OS Fundamentals · beginner · ~11 min
By the end of this lesson you will be able to: - Navigate the Unix filesystem tree and tell absolute paths apart from relative paths. - Read any permission string such as `-rwxr-x---` and translate it to and from its octal form (for example `750`). - Explain what read, write, and execute mean **differently** for files versus directories. - Describe the owner / group / other model and how the kernel decides which triad applies to a given user. - Explain what the setuid, setgid, and sticky bits do, and why setuid binaries are a recurring security concern. - State the root (UID 0) exception and why dropping privileges matters for real services.
On a Unix-like system (Linux, macOS, the BSDs) almost everything is represented as a file: your documents, your programs, your configuration, even devices like the keyboard and the disk. All of these files live in a single tree that begins at the root directory, written /. This builds directly on What an operating system does: one of the OS's central jobs is to manage resources and decide who is allowed to do what. File permissions are how that decision is recorded and enforced for the filesystem.
When any program tries to open, change, or run a file, the kernel checks the file's permissions against the identity of the user running that program. If the check fails, the operation is refused with a "Permission denied" error. This single mechanism underpins multi-user systems, server security, and most day-to-day access control on Unix.
The vocabulary you will meet here:
/etc/passwd).750) that encodes those bits.These ideas connect forward to the next lesson, Environment variables and the command line, because the shell you use to inspect and change permissions runs as you, with your identity and your limits.
Permissions are the most fundamental access-control model on Unix, and you will audit them again and again throughout a career in software or security.
Real incidents trace back to mis-set permissions:
644 (readable by everyone) leaks the moment another account is compromised.Whether you are deploying a web server, hardening a laptop, or reviewing someone else's system, "who can read, write, or run this?" is one of the first questions worth answering. Getting permissions right is cheap; getting them wrong is one of the most common and most exploited mistakes in real systems.
/Definition. Every file and directory on the system hangs off a single hierarchy whose top is the root directory /.
Directories are themselves just files; their "contents" are a list mapping names to other files. There are no separate drive letters as on Windows — additional disks are mounted at some point inside the one tree.
A path is the route from somewhere to a file:
/ and is unambiguous: /home/ana/notes.txt.notes.txt, or ../logs/today.log (.. means the parent directory, . means the current one)./ <- root
|-- etc/
| |-- passwd absolute path: /etc/passwd
|-- home/
| |-- ana/
| | |-- notes.txt absolute path: /home/ana/notes.txt
| |-- bob/
|-- tmp/
When it matters / pitfall. Scripts that use relative paths break when run from a different directory. Prefer absolute paths in cron jobs, services, and anywhere the working directory is not guaranteed.
Knowledge check. If your current directory is
/home/ana, what file does the relative path../bob/notes.txtrefer to?
Definition. Every file records a single owner (a user) and a single group. Every other user falls into the catch-all category other.
When a process accesses a file, the kernel picks exactly one triad to test, in this order:
This "first match wins" rule surprises people: if you are the owner and the owner bits forbid an action, you are denied even if the group or other bits would have allowed it.
Process user = ana, file owned by ana (group: staff)
is user == owner? yes -> use OWNER bits, stop
no
is user in group? ...
otherwise ... OTHER bits
Knowledge check (predict the outcome). A file is owned by
ana, mode isr--rw----(ownerr--, grouprw-, other---).anais also a member of the file's group. Cananawrite to the file?
Definition. Each triad has three bits:
| Bit | On a file | On a directory |
|---|---|---|
| read (r) | view the file's contents | list the names inside |
| write (w) | change the file's contents | create, rename, or delete entries inside |
| execute (x) | run the file as a program | enter / traverse the directory (use names in it) |
The directory column trips up almost everyone. Notable consequences:
cd into it and open files whose names you already know, but you cannot list its contents.cd into anything.dir 'secret' = --x (execute only)
ls secret/ -> Permission denied (no read)
cat secret/known.txt -> works IF you know the name (have execute)
Pitfall. Removing execute on a parent directory silently blocks access to everything beneath it, regardless of the children's own permissions.
Definition. Each triad of three bits is one octal digit, because three bits count 0–7. Add the values that are set:
r = 4 w = 2 x = 1
rwx = 4+2+1 = 7
r-x = 4+0+1 = 5
rw- = 4+2+0 = 6
r-- = 4+0+0 = 4
--- = 0
So -rwxr-x--- is owner 7, group 5, other 0 = 750. Common modes you will see:
| Octal | Meaning | Typical use |
|---|---|---|
| 644 | owner rw, group/other r | regular documents |
| 600 | owner rw, nobody else | private keys, secrets |
| 755 | owner rwx, group/other r-x | programs, public directories |
| 750 | owner rwx, group r-x, other nothing | group-only program/dir |
| 777 | everyone full access | almost always a mistake |
Pitfall. chmod 777 is a frequent "just make it work" fix that makes a file world-writable — anyone on the system can replace its contents. Treat any 777 as a red flag.
Knowledge check (explain in your own words). Why does octal work so neatly for permissions? (Hint: how many bits are in one triad, and how many values can that many bits represent?)
Definition. Beyond the nine rwx bits there are three special bits, shown as a leading octal digit (so a full mode is four digits, e.g. 4755).
passwd: a normal user runs it, but it must edit the root-owned password database, so it is setuid root. Shown as s in the owner execute position: -rwsr-xr-x./tmp, it restricts deletion: even though everyone can write there, you may only delete files you own. Shown as t: drwxrwxrwt.-rwsr-xr-x root root /usr/bin/passwd
^
setuid: runs AS root even when ana launches it
When to use / when NOT to. setgid directories are a good fit for team folders. setuid programs should be rare, small, and carefully written — every setuid-root binary is a potential privilege-escalation path if it has a bug. Never set setuid on a shell script (most systems ignore it precisely because it is so dangerous).
Pitfall. Adding setuid to your own helper "so it can write a system file" is a textbook way to introduce a vulnerability. Prefer a properly scoped service or sudo rule instead.
Definition. The user root (UID 0) bypasses all file permission checks. root can read, write, and (with execute set anywhere) run essentially anything.
This is why gaining root is the objective of most local privilege-escalation work, and why defensive practice insists that services drop privileges and never run as root unless they genuinely must. A bug in a process running as root is a bug with root's power.
Pitfall. "It only works when I run it with sudo" is usually a permissions problem to fix, not a reason to run everything as root.
Reading a long listing (ls -l) and the structure of a mode string:
-rwxr-x--- 1 ana staff 4096 Jun 27 10:12 report.sh
^|__||__||__|
| | | |
| | | +-- other : --- (nothing)
| | +------ group : r-x (read, execute)
| +---------- owner : rwx (read, write, execute)
+------------- type : - (regular file; 'd' = directory, 'l' = symlink)
The leading character is the file type, not a permission. The nine characters after it are the three triads. A s or t replaces an x to indicate a special bit.
Common commands (you read these here; you will practice them in a real shell later):
ls -l file # show type, permissions, owner, group
chmod 750 file # set mode numerically (owner rwx, group r-x, other ---)
chmod g-w,o-rwx file # symbolic: remove group write and all 'other' access
chmod u+x script.sh # add execute for the owner
chmod 4755 prog # set setuid + 755 (use with great caution)
chown ana:staff file # change owner to ana, group to staff (needs privilege)
stat -c '%a %U %G' file # print octal mode, owner, group (GNU/Linux)
Almost everything on a Unix system is a file. Access to each file is controlled by permissions.
Files live in a single hierarchy that is rooted at /. Directories are themselves files; their contents are lists of other files.
Paths come in two forms:
/etc/passwd.../notes.txt.Each file has an owner, a group, and three permission triads: owner, group, and other. Each triad has three bits:
-rwxr-x--- owner=rwx group=r-x other=---
The bits are often written in octal (base 8). Each triad becomes one digit:
rwx = 7r-x = 5--- = 0So the example above is 750.
For a directory, the execute bit has a special meaning: it grants permission to enter the directory.
/tmp, the sticky bit stops users from deleting each other's files.The root user (UID 0) bypasses all permission checks.
This is why gaining root is the goal of Linux privilege escalation. It is also why services should drop privileges and should never run as root unless they truly must.
Here is a small, realistic audit snippet a learner could run safely on a local, isolated machine to find files worth a second look. It only reads metadata — it changes nothing — which is the right default for any review.
#!/usr/bin/env bash
# perm-audit.sh — read-only permission review for a directory you own.
# Usage: ./perm-audit.sh /path/to/review
# Safe by design: it only lists; it never chmod/chown/deletes anything.
set -euo pipefail # stop on errors and unset variables
TARGET="${1:?usage: perm-audit.sh <dir>}" # require an argument
if [ ! -d "$TARGET" ]; then
echo "error: '$TARGET' is not a directory" >&2
exit 1
fi
echo "== World-writable files (anyone can modify) =="
# -perm -0002 matches files where the 'other write' bit is set
find "$TARGET" -xdev -type f -perm -0002 2>/dev/null || true
echo
echo "== setuid / setgid programs (run as owner/group) =="
# -4000 = setuid, -2000 = setgid; -o means logical OR
find "$TARGET" -xdev -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null || true
echo
echo "== Private keys readable by group or other (should be 600) =="
find "$TARGET" -xdev -type f -name 'id_*' ! -name '*.pub' -perm /0077 2>/dev/null || true
echo
echo "Audit complete. Review each result; nothing was changed."
What it does. Given a directory, it prints three categories that every permission review checks: world-writable files, setuid/setgid binaries, and private-key files that are readable beyond their owner. find ... -perm -0002 matches the "other write" bit; -perm /0077 matches any group/other access bit being set.
Expected output (illustrative, on a directory with one bad file):
== World-writable files (anyone can modify) ==
/home/ana/project/shared.conf
== setuid / setgid programs (run as owner/group) ==
== Private keys readable by group or other (should be 600) ==
/home/ana/project/keys/id_rsa
Audit complete. Review each result; nothing was changed.
Edge cases. 2>/dev/null hides "Permission denied" noise for subtrees you cannot enter (a normal, expected limitation). -xdev keeps the scan on one filesystem so it does not wander into mounted disks or /proc. The script intentionally takes no action; remediation (e.g. chmod 600) is a deliberate, separate step a human decides on.
Walking through perm-audit.sh:
| Line / piece | What happens |
|---|---|
set -euo pipefail |
Makes the script fail fast: -e exits on any command error, -u errors on an undefined variable, pipefail catches failures inside pipelines. This prevents a half-broken audit from looking like a clean one. |
TARGET="${1:?usage...}" |
Reads the first argument. The :? form prints the usage message and exits if no argument was given, so the script never runs against the wrong place. |
if [ ! -d "$TARGET" ] |
Confirms the target is actually a directory before scanning; otherwise it reports an error to stderr and exits non-zero. |
find ... -perm -0002 |
The leading - on -0002 means "all of these bits are set." Bit 2 in the last octal digit is other write, so this matches every world-writable file. |
\( -perm -4000 -o -perm -2000 \) |
Grouped condition: setuid (4000) or setgid (2000). The backslashes protect the parentheses from the shell so find sees them. |
-name 'id_*' ! -name '*.pub' |
Targets private key files (id_rsa, id_ed25519) while excluding their public .pub counterparts, which are safe to share. |
-perm /0077 |
The leading / means "any of these bits set." 0077 covers every group and other permission bit, so this flags a key that is readable or writable by anyone but the owner. |
2>/dev/null || true |
Discards permission-denied warnings and keeps the script from aborting (under set -e) just because find returned non-zero on an unreadable subtree. |
Trace example. Suppose keys/id_rsa has mode 640 (owner rw, group r, other none). The bitmask 0077 includes the group-read bit, mode 640 has the group-read bit set, so -perm /0077 matches and the file is printed. If the key were 600, no group/other bit is set, the match fails, and it is correctly not reported.
Mistake 1 — Reaching for chmod 777.
# WRONG: "the upload kept failing so I opened it up"
chmod -R 777 /var/www/uploads
This makes every file world-writable: any local user (or a compromised web process) can overwrite content or drop a malicious script. The real problem is almost always ownership, not a lack of permission.
# CORRECT: give the right user/group access, keep others out
chown -R www-data:www-data /var/www/uploads
chmod -R 750 /var/www/uploads
Recognize it: any 777 in a deployment script or ls -l output (rwxrwxrwx) deserves scrutiny.
Mistake 2 — Misreading octal digit order. People sometimes think 750 means "7 for everyone" or read the digits right-to-left. It is always owner, group, other, left to right. 750 = owner 7 (rwx), group 5 (r-x), other 0 (none).
Mistake 3 — Forgetting directory execute.
# WRONG: read on the dir but no execute -> still can't open files
chmod 644 /srv/shared # rw-r--r-- on a DIRECTORY
Without the execute bit you cannot traverse into the directory, so even readable files inside become unreachable.
# CORRECT: directories that should be enterable need execute
chmod 755 /srv/shared
Recognize it: "Permission denied" when opening a file even though the file's own bits look fine — check every parent directory's execute bit.
Mistake 4 — Leaving a private key group/other readable.
# WRONG
chmod 644 ~/.ssh/id_ed25519 # everyone can read your private key
# CORRECT
chmod 600 ~/.ssh/id_ed25519 # owner only; SSH will otherwise refuse to use it
Recognize it: SSH printing "WARNING: UNPROTECTED PRIVATE KEY FILE" is the system enforcing this for you.
Mistake 5 — Adding setuid to a script as a shortcut. Marking your own helper setuid-root to let it touch a system file is a classic way to create a privilege-escalation hole, and most kernels ignore setuid on scripts anyway. Use a narrowly scoped sudo rule or a proper service instead.
"Permission denied" when reading or running a file.
ls -l file and id (to see your user and groups). Decide which triad applies: are you the owner? in the group? otherwise other.x). A missing x upstream blocks everything below it: ls -ld /a /a/b /a/b/c."Permission denied" when deleting a file you can read. Deletion depends on the directory's write+execute bits, not the file's. Check ls -ld on the containing directory; also check for the sticky bit (t) on shared dirs like /tmp, which restricts deletion to owners.
A program won't run. Confirm the execute bit (chmod +x if you own it) and that it is on a filesystem not mounted noexec. For scripts, check the #! shebang line points to an interpreter that exists.
Octal isn't matching what you expect. Re-derive it: r=4, w=2, x=1, summed per triad, in owner/group/other order. Use stat -c '%a' file (Linux) or stat -f '%A' file (macOS) to print the numeric mode and compare.
Questions to ask when it doesn't work:
id)?noexec, read-only) involved?This is concept-track material with no C memory model, so the focus is robustness and validation when you write code or scripts that touch permissions:
-d "$TARGET". This avoids acting on the wrong target.600 for secrets, 750 for group programs) rather than opening them up and "locking down later." Later rarely comes.600 and owned by the right account. World-readable secrets are equivalent to no secret at all on a multi-user box.sudo rule or a dedicated service account will do. If a service must start as root to bind a low port, have it drop privileges immediately afterward.chmod -R/chown -R can lock you out or expose data if a wildcard is wrong. Make changes deliberately, on reviewed results.Where this shows up in real systems:
755/644 owned by the web user; secrets and TLS private keys are 600. A world-writable web directory is a classic foothold for attackers.~/.ssh directory if the permissions are too open — the tool actively enforces 600/700./usr/bin/passwd, /usr/bin/sudo, and ping (historically) are setuid root so unprivileged users can perform tightly scoped privileged actions.Professional best-practice habits
Beginner rules:
chmod 777 essentially never.600, programs/directories at 755 (or 750 when access should be limited).Advanced habits:
Beginner 1 — Translate modes. Convert each symbolic permission to octal and vice versa: (a) -rwxr-xr--, (b) -rw-r-----, (c) 640, (d) 700. Requirements: show the per-triad arithmetic (r=4, w=2, x=1). Hint: work one triad at a time, owner then group then other. Concepts: rwx bits, octal modes.
Beginner 2 — Predict access. A file report.txt is owned by ana, group staff, mode 640. For each user decide read/write/neither and say which triad applies: (a) ana; (b) bob, who is in staff; (c) carol, in no relevant group. Requirements: name the triad used for each and remember first-match-wins. Concepts: owner/group/other, triad selection.
Intermediate 1 — Directory traversal puzzle. You have /srv/data (mode 711, owned by root) containing notes.txt (mode 644, owned by root). As an ordinary user: can you ls /srv/data? Can you cat /srv/data/notes.txt? Explain each answer. Hint: distinguish read vs execute on the directory. Concepts: directory rwx semantics.
Intermediate 2 — Lock down a secrets folder. Write the chmod/chown commands to make ~/app/secrets a directory only its owner can enter (no group or other access), containing files only the owner can read or write. State the final octal modes for the directory and the files, and explain why each digit is what it is. Constraints: do not use 777; do not give other any bits. Concepts: least privilege, file vs directory permissions.
Challenge — Build a read-only permission auditor. Extend the lesson's perm-audit.sh idea: write a script that takes a directory and reports, in clearly labeled sections, (1) world-writable files, (2) setuid and setgid programs (labeled separately), and (3) files where other has any access but the owner's home would normally keep private. Requirements: validate the argument is a directory; make no changes; exit non-zero on bad input; suppress permission-denied noise; stay on one filesystem (-xdev). Provide an example invocation and sample output. Constraints: read-only, authorized targets only. Hint: find -perm -MODE means "all bits set", -perm /MODE means "any bit set". Concepts: special bits, world-writable detection, defensive scripting. (Do not just copy the lesson script — add the separate setuid/setgid labeling and your own private-file check.)
/, addressed by absolute or relative paths.r=4, w=2, x=1, summed per triad — so -rwxr-x--- is 750. Watch for 777 (world-writable) as a red flag and keep secrets at 600.s/t in the listing. setuid binaries are powerful and security-sensitive.chmod 777, misreading digit order, forgetting directory execute, leaving secrets world-readable, and abusing setuid. When access fails, check ownership, the right triad, and every parent directory's execute bit.