skip to content

questions

5

On Linux, `/usr/bin/passwd` is owned by root with mode `-rwsr-xr-x`, yet any user may run it to change their own password. What does that `s` cause the kernel to do at exec time, and how do the process's real, effective and saved user IDs differ afterwards?

level: middleimportance: must knowfreq 74%

answer

  1. privilege attached to the file, not the user
  2. one identity for checks, one for accounting
  3. three UIDs, not one
  4. the s sits in the owner-execute slot
  5. saved copy lets you go back

basics

~20 s

The set-user-ID bit makes the kernel set the new process's effective UID to the file owner's UID at exec time. So passwd runs with root's effective identity and can update the shadow file, while the real UID still records who launched it.

solid answer

~50 s

When the kernel execs a file that has the set-user-ID bit, it does not give the new program the caller's identity — it sets the process's **effective UID** to the UID that owns the executable, and copies that value into the **saved set-user-ID**. The **real UID** stays as the invoking user. Permission checks on files use the effective UID, so `passwd` running as effective-root can open a password database no ordinary user can write; accounting and the `passwd` program's own logic use the real UID to know *whose* password to change. The saved set-user-ID exists so a program can drop to the real UID with `seteuid()` for the risky parts and regain privilege later. `chmod u+s` sets the bit, `chmod u-s` clears it, and `ls` shows it as `s` in the user-execute slot — or `S` if the execute bit is missing, which means the bit is inert.

code

c · 7 lines
c
#include <stdio.h>
#include <unistd.h>

int main(void) {
    printf("real=%d effective=%d\n", (int) getuid(), (int) geteuid());
    return 0;
}

go deeper

for a junior

Recall that the s in -rwsr-xr-x means the program runs with the file owner's privileges rather than yours, and that this is how passwd can touch a root-owned file.

for a middle

Be ready to name real, effective and saved set-user-IDs, say which one permission checks use, and describe exactly what execve does to them when the bit is present.

for a senior

Show the privilege-hygiene judgment: raise privilege only around the operation that needs it, know that setuid drops permanently while seteuid does not, and know that chown and writes clear the bit.

for a principal

Own the policy question — argue for keeping the fleet's setuid inventory minimal and reviewed, and be able to explain to a team why an all-or-nothing identity transfer is a heavier grant than the task usually requires.

## The problem setuid solves Some operations genuinely require privilege but must be available to unprivileged users. Changing your own password writes a root-owned account database that no ordinary user may write. Rather than granting users write access to that file — which would let them edit anyone's entry, or the root entry — Unix grants access to a *specific program* and trusts that program to police what it does. That is the whole idea of the set-user-ID bit: privilege is attached to an executable, not to a user. ## What happens at exec Every process carries several user IDs: - **real UID (RUID)** — who the process belongs to, for accounting and for deciding who may signal it. - **effective UID (EUID)** — the identity used for almost all permission checks, including file access. - **saved set-user-ID (SUID)** — a stash the process can swap back to. (There is also a Linux-specific *filesystem UID*, which normally tracks the effective UID and exists for historical NFS-server reasons.) Normally `execve()` leaves all of these alone: run a program as user 1000 and it runs with RUID and EUID both 1000. If the file has the set-user-ID bit, the kernel instead sets **EUID = the file's owner UID** and copies that into the **saved set-user-ID**. The **real UID is untouched**. The set-group-ID bit does the same for the effective GID using the file's group. So for `-rwsr-xr-x root root /usr/bin/passwd`, invoked by user 1000: ``` real UID = 1000 (still you) effective UID = 0 (root, for permission checks) saved set-UID = 0 ``` `passwd` uses both halves: the effective UID gets it write access to the account database, and the real UID tells it which account it is allowed to modify. Strip the real UID away and the program would not know whose password to change. You can observe the split directly: ```c printf("real=%d effective=%d\n", (int) getuid(), (int) geteuid()); ``` and from the shell, `id -u` prints the effective UID while `id -ru` prints the real one. ## Dropping and regaining privilege The saved set-user-ID is what makes least-privilege possible inside a setuid program. A well-written helper raises privilege only around the operation that needs it: - `seteuid(getuid())` drops the effective UID to the real one **temporarily** — the saved set-user-ID still holds 0, so a later `seteuid(0)` restores it. - `setuid(getuid())` called while effective-root sets *all three* IDs, which drops privilege **permanently**; there is nothing left to return to. Getting this wrong is a classic vulnerability: a program that calls `seteuid()` thinking it has permanently dropped root, then execs a helper or gets hijacked, can have root handed straight back. ## Reading and managing the bit `ls -l` overloads the execute columns. Set-user-ID shows in the **user-execute** slot as `s`; a capital `S` means the set-user-ID bit is on but the owner-execute bit is off, so the file is not executable and the bit does nothing. Set-group-ID shows the same way in the group-execute slot. ``` chmod u+s /usr/local/bin/helper # or chmod 4755 chmod u-s /usr/local/bin/helper ``` Two behaviours surprise people. First, **changing the owner of an executable clears the set-user-ID and set-group-ID bits** — so copying a setuid binary elsewhere and re-owning it does not silently carry root along. Second, **writing to the file clears them too**, which is why a package update must re-apply the mode. Both are deliberate: they stop an unprivileged user from constructing a setuid-root file out of content they control. ## The security shape of the bit Set-user-ID is **all or nothing**. It hands over the file owner's complete identity, not the one permission the program actually needs, and it does so for every user on the machine. That is why the modern instinct is to keep the set of setuid-root binaries small and audited, and why the kernel refuses to honour the bit at all on filesystems mounted `nosuid`. Also worth knowing: a setuid binary starts in what glibc calls **secure-execution mode**, in which the dynamic loader ignores or sanitises environment variables such as `LD_PRELOAD` and `LD_LIBRARY_PATH`. Without that, anyone could inject a library into a root-privileged process. Finally, the bit has no meaning on a directory on Linux (only set-group-ID does), and none on a shell script — the kernel deliberately ignores it there.

  • Why does the kernel bother keeping the real UID once the effective UID has changed?
    Because the program still needs to know who invoked it. `passwd` uses the real UID to decide which account you are allowed to modify; accounting, auditing and signal permission checks use it too. If exec simply replaced your identity with root's, a setuid helper would have no trustworthy way to restrict you to your own resources.
  • What is the saved set-user-ID for?
    It lets a privileged program drop and later regain its privilege. On exec of a setuid file the kernel copies the new effective UID into the saved set-user-ID; `seteuid(getuid())` then runs the risky part unprivileged and `seteuid(0)` restores root from the saved value. Calling `setuid()` while effective-root instead overwrites all three IDs, dropping privilege irreversibly.
  • You see -rwSr--r-- on a file. What does the capital S tell you?
    That the set-user-ID bit is set but the owner-execute bit is not, so the bit is inert — the file cannot be executed at all. `ls` overloads the execute column to show the special bits, using lowercase when execute is also present and uppercase when it is not. It usually signals a mistaken chmod rather than a working privileged helper.
  • Why do chown and a plain write clear the set-user-ID bit on an executable?
    To stop an unprivileged user from manufacturing a privileged binary. If the bits survived, you could write arbitrary code into a file you own, hand it to root, and have it come back as setuid-root. Clearing them on ownership change and on modification means the bit can only be (re)applied by someone already privileged enough to set it.

saying these in an interview costs you the question

  • Says the program runs as root for everyone, including the real UID.
  • Cannot name the effective UID as the one used for permission checks.
  • Thinks setuid works on shell scripts on Linux.
  • Claims the setuid bit grants only the permissions the program needs.
  • Believes seteuid(getuid()) drops privilege permanently.

context

open as a page

On a Linux host, `ls -ld /tmp` prints `drwxrwxrwt`. What does that trailing `t` do, and why is it needed on a world-writable directory?

level: juniorimportance: should knowfreq 62%

basics

~20 s

The trailing t is the sticky bit, also called the restricted-deletion flag. On a world-writable directory it still lets anyone create files, but only a file's owner, the directory's owner, or root may rename or delete an entry.

open as a page

Several engineers share a directory owned by the group `devs`, but files each of them creates come out owned by that person's own primary group, so their colleagues cannot write them. What does setting the set-group-ID bit on the directory change, and what does it deliberately not change?

level: middleimportance: should knowfreq 48%

basics

~20 s

A set-group-ID directory makes new entries inherit the directory's group instead of the creator's primary group, and new subdirectories inherit the bit itself. It changes group ownership only — the permission bits on the new file still come from the creating process.

open as a page

A vendor package installs a set-user-ID root helper binary under `/opt` on a Linux server. What risk does that single mode bit introduce, and what changes if the filesystem holding it is mounted with the `nosuid` option?

level: seniorimportance: should knowfreq 44%

basics

~20 s

A set-user-ID root binary runs with root's effective identity for every user on the box, so any flaw in it becomes a local root exploit. Mounting its filesystem with nosuid makes the kernel ignore the bit, so the program runs unprivileged instead.

open as a page

You run `chmod 4755` on a root-owned shell script, and it still executes with the caller's own privileges rather than root's. Why does Linux ignore the set-user-ID bit here, when it honours it on a compiled binary?

level: middleimportance: nice to knowfreq 34%

basics

~20 s

Linux deliberately ignores set-user-ID and set-group-ID bits on interpreted scripts. The kernel applies them only when exec'ing a real executable image, so the interpreter named on the #! line starts unprivileged and the script runs as the caller.

open as a page