Special Permissions: setuid, setgid, Sticky Bit
Beyond the nine standard read/write/execute bits you already know from chmod, Linux defines three special permission bits: setuid, setgid, and the sticky bit. These bits change how the kernel behaves when a file is executed or when files are created inside a directory. They are what let an ordinary user run passwd and update a system file they don’t own, what lets a whole team share a project folder without constantly running chgrp, and what keeps one user from deleting another user’s files in a shared, world-writable folder like /tmp.
Overview: How Special Permissions Work
Recall that a file’s permissions are three sets of three bits: owner, group, and other, each covering read (4), write (2), and execute (1). Special permissions add a fourth set of bits layered on top of that, which is why you sometimes see a 4-digit octal mode like 4755 instead of the usual 3-digit 755. That leading digit is a bitmask: 4 for setuid, 2 for setgid, 1 for sticky, and they can be added together (a directory with both setgid and sticky is 3).
setuid (Set User ID)
When the setuid bit is set on an executable file, the kernel does something special during execve(): instead of running the new process with the effective UID of the user who launched it, it runs the process with the effective UID of the file’s owner. This is how /usr/bin/passwd, owned by root, lets a regular user edit /etc/shadow — a file only root can normally write — for the single, narrow purpose of changing their own password. The process still remembers who really invoked it (the real UID), but its effective UID — the one the kernel checks for permission decisions — is temporarily the file owner’s.
setgid (Set Group ID)
On an executable file, setgid works the same way as setuid but for the group: the process’s effective GID becomes the file’s group owner instead of the invoking user’s primary group. On a directory, setgid means something different and, in practice, more useful day to day: any file or subdirectory created inside inherits the directory’s group instead of the creating user’s default group, and new subdirectories also inherit the setgid bit itself, so the behavior propagates. This is the standard trick for shared team folders — everyone’s files automatically end up owned by the team group.
Sticky Bit
On a directory, the sticky bit restricts deletion: normally, if you have write permission on a directory, you can delete or rename any file inside it, even one you don’t own, because the write check happens against the directory, not the file. With the sticky bit set, the kernel adds an extra rule: a user may only delete or rename a file inside that directory if they own the file, own the directory, or are root. This is exactly why /tmp is world-writable but users still can’t delete each other’s temp files — it’s mode 1777. (Historically, the sticky bit had a different meaning on executable files — keeping the program’s memory image on swap for faster reloading — but that behavior is obsolete on modern Linux and the bit only matters on directories today.)
Syntax
Special permissions are set with the same chmod command used for ordinary permissions, either symbolically or with a 4-digit octal mode.
chmod
| Symbolic form | Octal digit | Meaning |
|---|---|---|
u+s |
4000 | setuid — run as the file’s owner |
g+s |
2000 | setgid — run as the file’s group, or new files in a directory inherit its group |
+t |
1000 | sticky bit — only the owner can delete their own files in this directory |
In an ls -l listing, these bits replace the executable-position character: s in the owner’s execute slot means setuid, s in the group’s execute slot means setgid, and t in the other’s execute slot means sticky. If the underlying execute bit isn’t also set, you’ll see a capital S or T instead — more on why that matters below.
Examples
Example 1: Spotting setuid on a real system binary
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Jan 6 2022 /usr/bin/passwd
The lowercase s where owner-execute normally goes tells you setuid is on, and the file is owned by root. Any user who runs passwd gets a process whose effective UID is 0 for the duration of that run, which is the only way an unprivileged user can modify /etc/shadow.
Example 2: A shared team directory with setgid
sudo mkdir /srv/projects
sudo groupadd devteam
sudo chown root:devteam /srv/projects
sudo chmod 2775 /srv/projects
ls -ld /srv/projects
drwxrwsr-x 2 root devteam 4096 Aug 4 10:15 /srv/projects
chmod 2775 sets rwxrwxr-x plus the setgid bit (the leading 2). Now watch what happens when a team member, alice, whose primary group is alice and not devteam, creates a file inside:
touch /srv/projects/notes.txt
ls -l /srv/projects/notes.txt
-rw-r--r-- 1 alice devteam 0 Aug 4 10:16 /srv/projects/notes.txt
Even though alice’s own default group is alice, the new file’s group is devteam, inherited from the directory. Without setgid, the file would have landed in alice’s personal group and other team members with only group-read access to devteam wouldn’t be able to read it.
Example 3: A public upload folder with the sticky bit
sudo mkdir /srv/public-uploads
sudo chmod 1777 /srv/public-uploads
ls -ld /srv/public-uploads
drwxrwxrwt 2 root root 4096 Aug 4 10:20 /srv/public-uploads
Everyone can write into this directory (the trailing rwt), but the t means only the owner of a given file — or root — can remove it. This is the exact mode /tmp uses on every mainstream distribution.
Auditing setuid binaries on a system
find / -perm -4000 -type f 2>/dev/null
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/su
/usr/bin/mount
find‘s -perm -4000 matches any file with the setuid bit set, regardless of its other permission bits; the 2>/dev/null discards “permission denied” noise from directories you can’t traverse.
How It Works Step by Step
When you run a setuid binary, several things happen in sequence: (1) your shell forks a child process, which at that point still carries your real and effective UID; (2) the child calls execve() on the target file; (3) before handing control to the new program, the kernel checks the file’s mode bits, sees setuid is set, and copies the file owner’s UID into the new process’s effective UID (the real UID stays yours, which is how the program can still tell who invoked it, via getuid()); (4) for the rest of that process’s life, every permission check the kernel performs — opening files, sending signals, and so on — uses the effective UID, not your real one. For setgid directories, the mechanism is different: it’s not execve() at all, but the file-creation syscalls (open() with O_CREAT, mkdir()) — when the kernel creates a new inode inside a setgid directory, it copies the parent directory’s group ownership onto the new inode instead of using the creating process’s primary group. For the sticky bit, the extra check happens inside unlink() and rename(): normally passing the directory’s write-permission check is sufficient to remove any entry in it, but if the sticky bit is set, the kernel adds a second condition — the caller’s effective UID must match either the file’s owner, the directory’s owner, or be 0 (root) — and only then does it proceed.
Common Mistakes
Mistake 1: Expecting setuid to work on a shell script
#!/usr/bin/env bash
echo "Effective UID: $(id -u)"
sudo chown root:root backup.sh
sudo chmod 4755 backup.sh
./backup.sh
You might expect this to print Effective UID: 0. It won’t — Linux’s kernel deliberately ignores the setuid and setgid bits on scripts (anything starting with #!). The reason is a well-known race condition: between the kernel reading the shebang line and the interpreter actually opening the script, an attacker could swap the file out from under it and get an arbitrary script run as root. If you need controlled privilege elevation for a script, use sudo with a tightly scoped /etc/sudoers entry instead of setuid.
Mistake 2: Recursive chmod silently stripping the setgid bit
sudo chmod -R 777 /srv/projects
This is doubly wrong: it destroys the setgid bit you carefully set (a numeric mode like 777 has no special-bit digit, so it resets it to off) and it makes every file world-writable, defeating the point of a group-restricted shared folder. Fix it with a symbolic mode that only touches the bits you mean to change, and re-apply setgid explicitly:
sudo chmod -R u=rwX,g=rwX,o=rX /srv/projects
sudo chmod g+s /srv/projects
The capital X here sets execute only on directories and files that already have execute set on some bit, which avoids accidentally making plain data files executable.
Mistake 3: Misreading a capital S or T
-rwSr--r-- 1 root root 12040 Aug 4 09:02 /usr/local/bin/oldtool
A capital S (or T for sticky) means the special bit is set but the matching execute bit is not. Here setuid is on, but the owner has no execute permission, so the file can’t actually run as anyone — it’s a broken, likely accidental, configuration worth investigating rather than a stronger form of setuid.
Best Practices
- Prefer
sudowith a narrowsudoersrule over writing your own setuid programs or scripts — setuid on custom code is a classic path to privilege-escalation bugs. - Periodically audit setuid/setgid binaries with
find / -perm -4000 -o -perm -2000 -type f 2>/dev/nulland remove the bit from anything that doesn’t need it. - Use setgid directories (
chmod 2775) for team-shared project folders so new files automatically get the right group. - Use the sticky bit (mode
1777) on any world-writable directory to stop users from deleting each other’s files. - Never combine setuid with world-writable (
777) — an attacker who can overwrite a setuid binary’s contents inherits its privilege. - On modern systems, consider Linux capabilities (
setcap) instead of full setuid-root when a program only needs one specific privilege, such as binding to a low port. - Always verify the result with
ls -lorls -ldafter using octal modes — a missing leading digit silently drops the special bit.
Practice Exercises
- Run a system-wide search for every setuid and setgid binary on your machine and list which ones you recognize; for each unfamiliar one, look up what it does before deciding whether it’s expected.
- Create
/srv/team-docs, make a new group calledwriters, set the directory’s group towriters, and set mode2775. Create a file inside as yourself and confirm withls -lthat it inherited the group instead of your personal one. - Create
/srv/shared-dropwith mode1777. Explain (you don’t need two separate user accounts to reason about it) why a second user could create a file there but not delete a file owned by the first user, referencing the extra check the kernel performs onunlink().
Summary
- setuid (
4000, shown assin the owner-execute slot) makes an executable run with the file owner’s effective UID instead of the caller’s. - setgid (
2000, shown assin the group-execute slot) does the same for group on executables, and on directories makes new files inherit the directory’s group. - The sticky bit (
1000, shown astin the other-execute slot) restricts file deletion inside a directory to the file’s owner, the directory’s owner, or root — the mode/tmpuses. - A capital
SorTinls -loutput means the special bit is set without the matching execute bit — usually a misconfiguration. - The Linux kernel ignores setuid/setgid on shell scripts by design, to prevent a shebang-swap race condition; use
sudoinstead. - Recursive numeric
chmodcommands can silently strip special bits — use symbolic modes or re-apply the bit explicitly afterward.
