CSEC3616Cybersecurity Engineering

    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.

    Permission bits — -rwsr-xr-xs = set-user-ID (SUID)owner's execute slot; runs as the file's owner, not the invoker-rwsr-xr-xtype: regular fileowner: r,w,s → 7group: r,-,x → 5other: r,-,x → 5special bits (SUID·SGID·sticky) = 4·0·0 → leading digit4special7owner5group5other= 4755

    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

    d=4r+2w+xd = 4r + 2w + x
    r,w,xr, w, x
    Each is 1 if that permission is granted, 0 if not.
    dd
    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).

    Unix permission calculator

    Convert octal and symbolic permissions both ways, then walk a process through setuid/seteuid/setreuid/exec-suid.

    Type
    Special bits

      CallResultNote

      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.

      1. As ubuntu (UID 1000), whoami3 is compiled and run before any ownership change. Output: Real 1000, Effective 1000, Saved 1000. With no SUID bit, all three equal the launching user’s UID.

      2. Ownership is changed and SUID set: sudo chown root:root whoami3 then sudo chmod u+s whoami3. ls -l now shows -rwsr-xr-x: the lowercase s replaces the owner’s execute character, since owner execute was already set.

      3. whoami3 is run again as ubuntu. Output: Real 1000, Effective 0, Saved 0. Real stays 1000 because the kernel always preserves who launched the process. Effective becomes 0 because the SUID exec rule sets it to the file owner’s UID the instant the binary starts. Saved also becomes 0, because the kernel copies the freshly-set effective UID into the saved UID, giving the process a way to recover that privilege later.

      4. whoami4, given the same chown and chmod u+s, is run as ubuntu. Its first line (Start) shows Real 1000, Effective 0, Saved 0: the SUID rule applied before any of the program’s own code ran.

      5. The program calls seteuid(ruid), i.e. seteuid(1000), to drop privilege. The Dropped line shows Real 1000, Effective 1000, Saved 0. seteuid() only changes the effective UID and never touches the saved UID, which is what keeps the drop reversible.

      6. The program calls seteuid(suid), i.e. seteuid(0), to restore privilege. The Restored line shows Real 1000, Effective 0, Saved 0. This succeeds because the target, 0, equals the process’s saved UID, and an unprivileged process may seteuid() 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