Unix and Linux permissions
UID/GID, the owner/group/other rwx model and its octal encoding, SUID/SGID/sticky on files versus directories, and the real/effective/saved UID triple that Tutorial 3 tests in full.
- Read and write a Unix permission string in both symbolic and octal form.
- State what SUID, SGID and the sticky bit each do on a file, and separately on a directory.
- Trace real, effective and saved UID through a SUID exec, a privilege drop and a privilege restore.
26 min read
Intuition
A Unix machine is built to be shared. Every account on it needs a cheap, kernel-enforced way to say who may read, change or run each of the files on disk, and Unix answers this by treating almost everything, including devices and communication endpoints, as a file. Get the permission model right and one careless or malicious account cannot touch another’s data. Get it wrong, and a single misconfigured bit can hand out root.
Mechanism
Every user has a User ID (UID) and a Group ID (GID), the numbers the kernel actually checks; usernames
are just a label on top. /etc/passwd holds one line per user:
sam:x:1000:1000:Sam Jones,,,:/home/sam:/usr/bin/zshIn order, that’s the username, an x placeholder (the real password hash lives in the separately-protected
/etc/shadow), the UID, the GID, a comment field, the home directory, and the login shell. /etc/group holds one
line per group:
sudo:x:27:sam,pnappaGroup name, an x placeholder, the GID, then a comma-separated list of member usernames. A user can belong to a
group, such as sudo, without that group being their primary one.
Mechanism
Every file and directory carries three permission classes: owner, group and other, each with its own
r/w/x bits.
-rw-r--r-- 1 sam sam 24K Jun 12 10:20 email.pngOwner, group and others can all read this file; only the owner, sam, can write it. On a directory, the same
three bits mean something slightly different: r lets a listing be read, w lets entries be added or removed,
and x lets the directory be traversed, entered as part of a path, which is why a directory can be unlistable
and still walkable, or vice versa.
Each of the three rwx triplets maps to one octal digit: read = 4, write = 2, execute = 1, summed. Here owner has read + write + the SUID bit occupying the execute slot (7), group has read + execute (5), other has read + execute (5). The special-bits digit — SUID, SGID and sticky, 4/2/1 respectively — prefixes the three, giving the full mode 4755, the classic pattern for a SUID root binary such as passwd ormount.
Exam detail
Each rwx triplet collapses to one octal digit: read is 4, write is 2, execute is 1, summed. rwxr-xr-x
is owner 7 (4+2+1), group 5 (4+1), other 5 (4+1), written 0755. The leading fourth digit, when present,
encodes the three special bits the same way: SUID is 4, SGID is 2, sticky is 1, so a SUID-root binary with
rwxr-xr-x underneath is 4755. chmod, chown and chgrp change these bits and the owner/group respectively,
either in octal (chmod 755 file) or symbolically (chmod g+r file adds read for the group; u+r and o+r do
the same for owner and other).
Formula
Octal permission digit
- Each is 1 if that permission is granted, 0 if not.
- The resulting octal digit, 0 to 7.
The same formula gives the leading special-bits digit, with r -> SUID (4), w -> SGID (2), x -> sticky (1).
Mechanism
Beyond the nine standard bits, a file carries three special bits, and each means something different on a file than it does on a directory.
On a file: the set-user-ID bit (SUID) makes the program run with the privileges of the file’s owner,
rather than the user who invoked it, mount is the lecture’s own example, and passwd is another. The
set-group-ID bit (SGID) does the same thing for the file’s group instead of its owner. The sticky bit has
no effect on a regular file.
On a directory: SGID means any file or subdirectory created inside inherits the directory’s group, not the
creating user’s own primary group. The sticky bit restricts deletion: only the owner of a file, or root, can
remove or rename it inside that directory, even if the directory’s own permissions let everyone else write to it.
/tmp is the standard example, shared and world-writable, but nobody can delete another user’s files in it.
SUID’s case is different: the lecture material never discusses SUID on a directory specifically, only SGID and
the sticky bit get directory-specific treatment. Under standard Linux semantics SUID has no defined effect on a
directory; the bit can be set, but the kernel does not act on it there.
Pitfall
The Week 3 notes describe SGID on a directory as giving new files “their group ownership set to that of the
directory owner.” That wording is imprecise to the point of being wrong: what gets inherited is the directory’s
group, not anything about who owns the directory. The unit’s own slides (“new files/directories are created
with group of parent directory”) and Tutorial 3 (a live chmod g+s demo where files created inside a directory
owned by ubuntu with group bertie come out owned by ubuntu:bertie, matching the directory’s group exactly)
both get this right. Follow the slides and tutorial wording. Writing “directory owner” instead of “directory’s
group” on an exam answer describes a different, wrong mechanism.
Mechanism
A running process inherits permissions from the user who launched it, tracked as three separate identities:
- Real UID/GID: the user and group that actually started the process. This never changes just because a program happens to be SUID.
- Effective UID/GID: what permission checks the kernel performs on this process’s behalf actually use. Exec’ing a SUID binary sets this to the file owner’s UID, regardless of who launched it.
- Saved UID/GID: a copy the kernel keeps so the process can later restore an elevated effective UID via
seteuid(), without being re-executed or asking for a password again.
Root itself is UID 0 and GID 0, and is exempt from the ordinary permission checks: it can access any file and
become any user. sudo lets an otherwise unprivileged user run one command as root. It requires the invoking
user to be in the sudo group and, usually, their own password; the sudoers file can also grant specific commands
to users who are not in the sudo group at all.
Worked example
AnswerReal 1000 / Effective 0 / Saved 0 in every run of the SUID-root program, whether or not it drops and restores privilege in between.
Tutorial 3’s whoami3.c and whoami4.c print a process’s real, effective and saved UID by calling
getresuid(). The walkthrough below traces both programs from a plain compile through a SUID exec, a
deliberate privilege drop, and a restore.
As user
ubuntu(UID1000),whoami3is compiled and made executable, then run before any ownership change. Output: Real1000, Effective1000, Saved1000. With no SUID bit set, all three UIDs equal the launching user’s own UID: there is no privilege distinction yet.The file’s ownership is changed to root and its SUID bit is set:
sudo chown root:root whoami3thensudo chmod u+s whoami3.ls -lnow shows-rwsr-xr-x: the lowercasesreplaces the owner’s execute character, since owner execute was already set.whoami3is run again, still asubuntu. Output: Real1000, Effective0, Saved0. Real UID stays1000because the kernel always preserves who actually launched the process. Effective UID becomes0because the SUID exec rule sets it to the file owner’s UID, root, the instant the binary starts. Saved UID also becomes0, because the kernel copies the freshly-set effective UID into the saved UID at the same moment, giving the process a way to recover that privilege later.whoami4builds onwhoami3. It is compiled, given the samechown root:rootandchmod u+streatment, then run asubuntu. Its first printed line, labelled Start, shows Real1000, Effective0, Saved0, the same state as step 3: the SUID rule has already applied before any of the program’s own code runs.The program calls
seteuid(ruid), i.e.seteuid(1000), to voluntarily drop its effective privilege. The Dropped line shows Real1000, Effective1000, Saved0. Real UID is unaffected, since it was never root to begin with. Effective UID moves to1000. Saved UID stays0, becauseseteuid()only ever changes the effective UID; it never touches the saved UID, which is exactly what keeps the drop reversible.The program calls
seteuid(suid), i.e.seteuid(0), to restore its earlier privilege. The Restored line shows Real1000, Effective0, Saved0. This succeeds because the target,0, equals the process’s current saved UID, and an unprivileged process is permitted toseteuid()to any of its own real, effective, or saved UID. No password prompt and no re-exec were needed: the kernel had already “remembered” that this process was entitled to root, via the saved UID.
Exam detail
If the saved UID did not exist, the drop in step 5 would be permanent: once effective UID moved to 1000, the
process would have no value left to prove it was ever allowed to be root again, and would need to be re-exec’d
from the SUID-root binary from scratch to regain privilege. The saved UID is specifically what turns “drop
privilege” into a reversible operation instead of a one-way one. passwd uses exactly this drop-then-restore
pattern in practice: it runs SUID root, drops to the real UID while doing routine work like reading input, and
restores root only for the brief window it needs to write /etc/shadow.
Pitfall
Do not write that SUID “gives the user root access.” SUID does not touch the real UID or the invoking user’s account at all; the user who ran the binary is exactly who they were before. What changes is the effective UID of that one running process, for as long as the process’s own effective UID says so. Confusing real with effective UID is the single most common way to lose marks on this material: name which one you mean, every time.
Recall
A binary is owned by root, with mode -rwxr-xr-x (no SUID bit). A normal user executes it. What is that process's effective UID?
The invoking user’s own UID. Without the SUID bit, effective UID equals real UID regardless of who owns the file; ownership by root only matters once the SUID bit tells the kernel to use it.
Threat
A SUID-root binary that is more permissive than its job requires turns “run this one trusted program with elevated rights” into “gain root outright.” Anyone who can execute the binary inherits the file owner’s effective UID, so a program that is writable by a non-root user, or that execs another program without controlling its environment, hands every one of its callers a path to root. The tutorial’s own framing is blunt about this: misuse of the setuid or setgid bits can grant elevated privileges it was never meant to.
Control
Keep the set of SUID-root binaries small and audited. find /usr/bin -user root -perm -4000 is the tutorial’s
own command for listing every SUID-root binary on a system, and a real one only turns up a short, well-known list
such as passwd, sudo, mount and su. For any program that does carry SUID, follow the passwd discipline:
drop effective privilege back to the real UID with seteuid() for as much of the program’s execution as
possible, and restore it, via the saved UID, only for the narrow operation that actually needs root. Minimising
the time spent at full privilege minimises the window in which a bug or malicious input can do damage with root’s
authority behind it.
Source
Week 3 notes PDF