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 readJump to the quiz ↓
Unix checks numeric IDs, not usernames
A Unix machine is shared, so every account needs a cheap, kernel-enforced way to say who may read, change or run each file. Unix treats 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.
Every user has a User ID (UID) and a Group ID (GID), the numbers the kernel actually checks; usernames are
a label on top. Root is UID 0 and GID 0. /etc/passwd holds one line per user:
sam:x:1000:1000:Sam Jones,,,:/home/sam:/usr/bin/zsh
In order: username, an x placeholder (the real password hash lives in the separately-protected /etc/shadow),
UID, GID, a comment field, home directory, login shell. /etc/group holds one line per group:
sudo:x:27:sam,pnappa
Group 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 it being their primary one.
Nine rwx bits in three classes decide ordinary access
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.png
Everyone can read this file; only the owner, sam, can write it. On a directory the same 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 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.
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. A leading fourth digit 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 the bits and the owner/group, 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).
SUID, SGID and the sticky bit mean different things on files and directories
Beyond the nine standard bits, a file carries three special bits.
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 example, and passwd is another. The
set-group-ID bit (SGID) does the same for the file’s group. 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 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 write. /tmp is the
standard example. The lecture never discusses SUID on a directory; under standard Linux semantics 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 is wrong: what gets inherited is the directory’s group, not anything about who
owns the directory. The unit’s slides (“new files/directories are created with group of parent directory”) and
Tutorial 3 (a live chmod g+s demo where files created in a directory owned by ubuntu with group bertie
come out owned by ubuntu:bertie) both get this right. Writing “directory owner” on an exam answer describes a
different, wrong mechanism.
A process carries real, effective and saved IDs, and SUID changes only the effective one
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 is SUID.
- Effective UID/GID: what the kernel’s permission checks 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 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 outside the sudo group.
Try it — worked example
User ubuntu (UID 1000) runs a SUID-root copy of whoami3/whoami4. What are the real, effective and saved UIDs in each run?
Show answer and steps
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 traces both programs from a plain compile through a SUID exec, a privilege drop
and a restore.
As
ubuntu(UID1000),whoami3is compiled and run before any ownership change. Output: Real1000, Effective1000, Saved1000. With no SUID bit, all three equal the launching user’s UID.Ownership is changed and SUID 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 asubuntu. Output: Real1000, Effective0, Saved0. Real stays1000because the kernel always preserves who launched the process. Effective becomes0because the SUID exec rule sets it to the file owner’s UID the instant the binary starts. Saved also becomes0, because the kernel copies the freshly-set effective UID into the saved UID, giving the process a way to recover that privilege later.whoami4, given the samechownandchmod u+s, is run asubuntu. Its first line (Start) shows Real1000, Effective0, Saved0: the SUID rule applied before any of the program’s own code ran.The program calls
seteuid(ruid), i.e.seteuid(1000), to drop privilege. The Dropped line shows Real1000, Effective1000, Saved0.seteuid()only changes the effective UID and never touches the saved UID, which is what keeps the drop reversible.The program calls
seteuid(suid), i.e.seteuid(0), to restore privilege. The Restored line shows Real1000, Effective0, Saved0. This succeeds because the target,0, equals the process’s saved UID, and an unprivileged process mayseteuid()to any of its own real, effective or saved UID. No password prompt or re-exec was needed.
Without the saved UID the drop in step 5 would be permanent: the process would have no value left to prove it was ever allowed to be root, and would need re-exec’ing from the SUID-root binary to regain privilege. The saved UID turns “drop privilege” into a reversible operation.
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. Confusing real with effective UID is the 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.
A SUID-root binary is a path to root, so keep them few and drop privilege early
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 it inherits the 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 caller a path to root.
The control is to keep the set of SUID-root binaries small and audited. find /usr/bin -user root -perm -4000 is
the tutorial’s command for listing every SUID-root binary; a real system turns up a short, well-known list such as
passwd, sudo, mount and su. For any program that carries SUID, follow the passwd discipline: it runs SUID
root, drops to the real UID with seteuid() for routine work like reading input, and restores root, via the saved
UID, only for the brief window it needs to write /etc/shadow. Less time at full privilege means a smaller window
in which a bug or malicious input can act with root’s authority.
Check yourself
Source
Week 3 slides PDF