setuid, setgid, and the Sticky Bit
Beyond the nine read/write/execute bits sits a fourth octal digit holding three special bits: setuid (4000), setgid (2000), and the sticky bit (1000). They do not grant more access in the ordinary sense — they change who you become when a program runs and who may delete files in a directory. setuid makes an executable run with the privileges of its owner instead of the caller; setgid does the same for the group, and on a directory forces new files to inherit that directory's group; the sticky bit on a directory says only a file's owner, the directory's owner, or root may unlink it, regardless of write permission on the directory.
These bits are the reason an unprivileged user can change their own password: /usr/bin/passwd is setuid-root, so it runs as root and edits /etc/shadow, which no normal user can read. That same mechanism is the most-mined privilege-escalation surface on a Linux box — a bug in any setuid-root binary, or one you installed without thinking, is a direct path from a shell to root. Every special bit you set is a question an attacker will ask later.
setuid on Executables
When a setuid bit is set on an executable, the kernel sets the process's effective UID to the file's owner at exec() time, not the UID of the user who launched it. Owned by root, the program runs as root no matter who started it. In ls -l the owner-execute slot shows s instead of x — -rwsr-xr-x — and you set it with chmod u+s file or chmod 4755 file. A capital S means the setuid bit is set but the execute bit is not, which is almost always a mistake.
A handful of system binaries genuinely need this. passwd writes /etc/shadow; sudo and su switch identity; ping historically opened raw sockets; mount and umount manipulate the mount table. The weight of setuid-root is total: the program holds full root authority for its entire run, so a buffer overflow, an unsanitized environment variable, or a careless call to system() inside it hands root to the caller. This is why modern Linux is steadily replacing setuid-root with file capabilities — ping on current Debian and Ubuntu carries cap_net_raw instead of the setuid bit, granting raw-socket access without the rest of root's powers.
# A real setuid-root binary: the s in the owner-execute slot $ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 59976 /usr/bin/passwd # Set and clear the setuid bit $ sudo chmod u+s ./myprog # symbolic $ sudo chmod 4755 ./myprog # octal: leading 4 = setuid $ sudo chmod u-s ./myprog # remove it
setgid on Executables and Directories
The setgid bit (octal 2000) wears two completely different hats depending on what it is set on. On an executable it mirrors setuid for the group: the process runs with the file's group as its effective GID. The classic example is /usr/bin/wall or terminal-writing tools that run setgid tty. In ls -l it shows as s in the group-execute slot — -rwxr-sr-x — set with chmod g+s file or a leading 2 in octal.
On a directory, setgid does something far more useful day to day: every new file and subdirectory created inside it inherits the directory's group rather than the creator's primary group, and new subdirectories inherit the setgid bit too. This is the standard pattern for a shared team directory — set the directory's group to devs, set setgid, and every file dropped in is owned by devs automatically, so teammates can read and write each other's files without anyone running chgrp by hand. Without it, files land owned by each user's own primary group and the share quietly breaks.
# Build a setgid shared directory for the "devs" group $ sudo mkdir /srv/shared $ sudo chgrp devs /srv/shared $ sudo chmod 2775 /srv/shared # leading 2 = setgid $ ls -ld /srv/shared drwxrwsr-x 2 root devs 4096 /srv/shared # A file created here is group-owned by devs, not the creator's group $ touch /srv/shared/report.txt $ ls -l /srv/shared/report.txt -rw-rw-r-- 1 alice devs 0 /srv/shared/report.txt
The Sticky Bit
The sticky bit (octal 1000) is meaningful only on directories on modern Linux, where it changes deletion semantics. Normally, write permission on a directory lets you delete any file in it, because unlinking a name is a write to the directory, not to the file. On a world-writable directory that is a disaster: anyone could delete or replace anyone else's files. The sticky bit fixes this by restricting unlink and rename to the file's owner, the directory's owner, and root.
This is exactly what protects /tmp and /var/tmp. Both are mode 1777 — world-writable so any process can create temp files, plus the sticky bit so one user cannot delete another's. In ls -l it appears as t in the other-execute slot — drwxrwxrwt — and a capital T means the sticky bit is set without the other-execute bit. Set it with chmod +t dir or a leading 1 in octal. Any directory you make world-writable should carry it.
# /tmp: world-writable plus sticky = mode 1777 $ ls -ld /tmp drwxrwxrwt 18 root root 4096 /tmp # Add the sticky bit to a shared drop directory $ sudo chmod 1777 /srv/dropbox # or: chmod +t /srv/dropbox
Finding and Auditing SUID and SGID Binaries
Every setuid-root binary on the system is a candidate root exploit, so the full inventory is something you should be able to produce on demand and diff against a known-good baseline. find matches on the special bits directly: -perm -4000 finds setuid files, -perm -2000 finds setgid, and -perm -6000 finds either. Run it from /, prune nothing, and treat anything unexpected — especially under /home, /tmp, or a freshly installed package's directory — as a finding to investigate.
A clean baseline matters because the set changes over time: a package update can add a setuid helper, and an attacker who briefly gets root will often drop a setuid-root shell somewhere obscure to keep a way back in. Comparing today's list to a snapshot taken at install is one of the cheapest intrusion checks there is. Tools like debsums on Debian and Ubuntu, or AIDE, formalize this by tracking permission and checksum changes across the whole filesystem.
# Inventory every setuid and setgid file, ignoring permission-denied noise $ sudo find / -type f -perm -4000 2>/dev/null # setuid $ sudo find / -type f -perm -2000 2>/dev/null # setgid $ sudo find / -type f -perm -6000 -printf '%M %u %p\n' 2>/dev/null # Save a baseline, then diff after any install or incident $ sudo find / -type f -perm -6000 2>/dev/null | sort > /root/suid-baseline.txt
Why setuid Scripts Are Ignored
Setting the setuid bit on a shell or Python script does nothing — the kernel deliberately ignores setuid and setgid bits on interpreted files. The reason is a classic race: the kernel reads the #! line and execs the interpreter on the script's path, and in the window between the privilege check and the interpreter opening the file, an attacker can swap the file (a time-of-check-to-time-of-use race) and run their own code as root. Rather than try to close that hole, Linux refuses to honor the bit on scripts at all.
The right tools for "let an unprivileged user run this one privileged action" are sudo with a tightly scoped rule, or file capabilities for a specific kernel privilege instead of all of root. If you genuinely need a setuid program, write it in C as a small, audited wrapper that drops privileges immediately, sanitizes its environment, and uses absolute paths — never a script, and never something large enough to hide a bug. In most cases a sudoers entry naming the exact command is simpler and safer than any setuid binary you would build.
setuid (4000) — on an executable, the process runs as the file's owner. Use it (sparingly) when an unprivileged user must perform an action only the owner, usually root, is allowed to. Shows as s in the owner-execute slot. Ignored on directories and on scripts.
setgid (2000) — on an executable, the process runs as the file's group; on a directory, new files inherit the directory's group and the bit propagates to subdirectories. Reach for the directory behavior to build shared team directories. Shows as s in the group-execute slot.
sticky bit (1000) — on a directory, only a file's owner, the directory's owner, or root may delete or rename it. Use it on any world-writable directory such as /tmp. Shows as t in the other-execute slot. No effect on regular files on modern Linux.
- Putting the setuid bit on a shell or Python script and expecting elevation — the kernel silently ignores it, so the script runs as the caller and the "privileged" action just fails, often with a confusing permission error.
- Shipping a custom setuid-root binary that calls out to
system(), trusts$PATH, or reads unsanitized environment variables — every one of those is a documented root-escalation pattern, and you now own that attack surface. - Making a shared or drop directory world-writable (
chmod 777) without the sticky bit — any user can delete or replace any other user's files in it, including swapping a script for a malicious one. - Forgetting setgid on a team directory, then wondering why teammates get "permission denied" on each other's files — new files keep landing under each creator's primary group instead of the shared group.
- Never re-auditing SUID/SGID binaries after a package install or upgrade — a new setuid helper, or an attacker's planted setuid shell, sits there unnoticed because no baseline diff is run.
- Confusing the capital
S/Tinls -lwith the lowercase form — the capital means the special bit is set but the matching execute bit is missing, so a setuid binary with-rwSr--r--is not even executable.
- Baseline the SUID/SGID set at install with
find / -perm -6000and diff it after every package change or suspected incident; on Debian and Ubuntu pair it withdebsumsor AIDE for checksum tracking. - Prefer a scoped
sudoersrule naming the exact command over writing any setuid binary — it is auditable, logged, and revocable in one file. - Use file capabilities (
setcap cap_net_raw+ep ...) instead of setuid-root when a program needs one specific kernel privilege; grant the capability, not all of root. - Build team shares with a dedicated group and
chmod 2775(setgid) so every file inherits the shared group automatically and the share keeps working without manualchgrp. - Set the sticky bit (
chmod +t, mode1777) on any directory you make world-writable so users cannot delete one another's files. - If you must ship a setuid program, write it in C, keep it tiny, drop privileges as early as possible, sanitize the environment, and use absolute paths for every external call.
cap_net_raw) instead of all of rootsudo — policy-driven, logged elevation by named command, usually preferable to a custom setuid binaryWindows — UAC elevation and "Run as administrator", the closest mainstream analogue to running a program with elevated identityKnowledge Check
You add the setuid bit to a Bash script that needs to run as root. Why does it still run as the calling user?
- The kernel deliberately ignores setuid/setgid bits on interpreted scripts, to avoid a time-of-check-to-time-of-use race when the interpreter reopens the file
- Bash itself detects and strips the setuid bit off any script it is asked to execute, purely as a deliberate built-in safety measure
- setuid only ever takes effect when the file happens to be owned by the calling user rather than by root itself, so a root-owned script can never elevate this way
- The script needs the sticky bit set alongside its setuid bit before the privilege elevation will ever actually take effect at runtime
A directory is mode 1777 (drwxrwxrwt). What does the trailing t guarantee?
- Only a file's owner, the directory's owner, or root may delete or rename a file in it, even though everyone can write to the directory
- Files created in it are automatically reassigned to be owned by root rather than by the user who created them
- New files inherit the directory's group rather than the creator's own primary group, which is the behavior that keeps the shared folder consistent
- Programs executed from inside the directory run with the directory owner's privileges instead of the caller's own
Why is a setgid bit on a directory the standard tool for a shared team folder?
- New files inherit the directory's group instead of each creator's primary group, so teammates can read and write each other's files without manual
chgrp - It makes every file created inside the directory writable by all users on the system, so the whole team can freely edit one another's files
- It runs every program stored inside the directory as the directory's own group owner, so all the teammates effectively share a single execution identity
- It prevents users from deleting or renaming any of the files inside the directory that they do not personally own themselves
On modern Debian and Ubuntu, why does ping carry cap_net_raw instead of being setuid-root?
- A capability grants only the one privilege ping needs (raw sockets), so a bug in ping no longer exposes the full power of root
- Capabilities run measurably faster than setuid binaries because they skip the exec-time UID switch that setuid forces on every run
- setuid is no longer supported on current Linux kernels at all, so capabilities are the only mechanism left for elevation
- It lets ordinary users edit
/etc/shadowdirectly the same way thepasswdbinary does when changing a password
During an audit you find -rwsr-xr-x root root /tmp/.cache/helper. Why is this worth investigating immediately?
- A setuid-root binary in a world-writable temp path is a classic planted backdoor — anyone who runs it gets root
- Files in
/tmpcannot legally carry the setuid bit at all, so this listing must be the result of filesystem corruption - The sticky bit on
/tmpwould normally have stripped this file's setuid bit off automatically the moment it was created - A setuid binary owned by root always runs as the unprivileged caller anyway, so a file like this is entirely harmless
You got correct