CoursesLinux essentialsSUID, SGID, sticky & umask

SUID, SGID, sticky & umask

The special bits attackers look for.

Intermediate12 min · lesson 16 of 25

Ordinary Linux permissions (read, write, and execute) answer one question about a file: what are you allowed to do to it? Three extra bits sit alongside the ones you already know, and they answer a stranger question. They can change who you get to be while a program runs, and who is allowed to delete things out of a shared folder. Two of them are near the top of an attacker's checklist the moment they land on a machine. So a security or operations engineer has to know them cold.

SUID: Borrowing the Owner's Powers

Here is the everyday version. A bank teller can't open the vault on their own say-so. But the bank gives them a controlled procedure that, for one specific task, acts with the bank's authority instead of the teller's. SUID (Set User ID) is that procedure in software form. When the SUID bit is set on a program, the program runs with the privileges of the file's owner, not the person who started it. Is the owner root? Then any user who runs it is briefly root.

The classic example ships on every Linux machine: passwd, the command you use to change your password. Changing a password means writing into /etc/shadow, a file only root is allowed to touch. You are not root. So passwd is owned by root and wears the SUID bit. The instant you run it, it runs as root, edits that one protected file under tight rules, and exits. You borrowed root's power for a few milliseconds without ever being handed the keys.

~/secopslog — bash
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 Feb 6 2024 /usr/bin/passwd

Read the permission string left to right. After the leading file-type dash come three groups of three: owner, group, others. Normally the owner's third character is x for execute. Here it's s. That lowercase s is the SUID bit sitting on top of the execute bit, and it is the whole story: this program, run by anyone, executes as root.

Every SUID-root file on a host is a door that leads from ordinary-user to root. Most of those doors are meant to be there. Your job is to know exactly which ones exist and to notice the day a new one appears. One command lists them all:

~/secopslog — bash
$ # every SUID file on the system (-4000 means 'has at least the SUID bit') # 2>/dev/null throws away the permission-denied noise find / -perm -4000 -type f 2>/dev/null
/usr/bin/chfn /usr/bin/chsh /usr/bin/gpasswd /usr/bin/newgrp /usr/bin/passwd /usr/bin/su /usr/bin/sudo /usr/bin/mount /usr/bin/umount /usr/bin/pkexec /usr/bin/fusermount3 /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/openssh/ssh-keysign /usr/lib/policykit-1/polkit-agent-helper-1

On a fresh Ubuntu or Debian box that list is short and boring, which is the point. Learn your baseline so the boring list stays boring. One name on it, pkexec, is worth remembering. A security flaw in it, catalogued as CVE-2021-4034 (a CVE, or Common Vulnerabilities and Exposures, number is the public tracking ID every serious bug gets) and nicknamed PwnKit, handed local users an instant root shell on millions of machines in 2022. And a surprising number of ordinary commands turn into root shells the moment they carry the SUID bit by mistake. A public catalog called GTFOBins documents exactly which standard binaries can be abused this way and how. Attackers read it. So should you. To sweep for SUID and SGID in one pass, use find / -perm /6000 -type f.

SGID: The Same Trick, Aimed at Groups

SGID (Set Group ID) is the group-flavored version, and it has two jobs depending on where it lands. On a program it works like SUID but for the group: the program runs with the file's group identity. That one is rare. The genuinely useful job is on a directory. Think of a shared project folder as a filing cabinet that belongs to a team. Without SGID, every document someone drops in is stamped with that person's own group, and the team quickly loses track of who can read what. Put SGID on the folder and every new file inside is automatically stamped with the folder's group instead.

~/secopslog — bash
$ sudo mkdir /srv/shared sudo chgrp devs /srv/shared sudo chmod 2775 /srv/shared # the leading 2 = SGID ls -ld /srv/shared
drwxrwsr-x 2 root devs 4096 Jul 17 10:41 /srv/shared

See the s where the group's execute character would be. Now watch what happens when Alice, whose own default group is alice, creates a file inside:

~/secopslog — bash
$ cd /srv/shared touch report.txt ls -l report.txt
-rw-r--r-- 1 alice devs 0 Jul 17 10:42 report.txt

The file's group is devs, inherited from the folder, not alice. That is the payoff of SGID on a directory. One catch worth understanding: SGID fixes the group owner, not the group's write permission. Under the standard umask the file is still -rw-r--r--, so teammates can read it but not edit it. If you want a truly shared, editable folder, you also lower the umask (covered next) to 002 so the group keeps its write bit.

The Sticky Bit: Everyone Writes, You Only Delete Your Own

The office fridge problem: it's shared, anyone can put their lunch in, but nobody should be able to throw out someone else's. A plain world-writable folder can't manage that. If a directory is writable by everyone, then by default everyone can also delete anything inside it, including files they never created, because in Linux the right to delete a file comes from write permission on the folder, not on the file. The sticky bit fixes exactly this. Set it on a directory and the rule changes: you can delete or rename a file inside it only if you own that file. The folder's owner and root can too, but nobody else.

This is why /tmp, a scratch space the whole system shares, is safe to leave world-writable. Look at its final character:

~/secopslog — bash
$ ls -ld /tmp
drwxrwxrwt 18 root root 4096 Jul 17 10:45 /tmp

That trailing t is the sticky bit. Everyone can create files in /tmp, but user sam cannot delete a file that belongs to user alice. You set it with a leading 1, and you want it on any drop-box style folder you make world-writable:

~/secopslog — bash
$ sudo mkdir /srv/dropbox sudo chmod 1777 /srv/dropbox ls -ld /srv/dropbox
drwxrwxrwt 2 root root 4096 Jul 17 10:46 /srv/dropbox

umask: The Stencil That Decides New Permissions

Every file you create is born with some default permissions. What sets them? Not the special bits, but the umask (user file-creation mode mask). Picture a stencil laid over fresh permissions: wherever the stencil is solid, that permission gets blocked out. The umask is a list of permissions to withhold from new files. A umask of 022 means 'withhold write from group and others.'

The starting point before the mask is 666 for files (read and write for everyone, never execute) and 777 for directories. Clear the 022 bits and files land at 644 (rw-r--r--) and directories at 755 (rwxr-xr-x). That is the common desktop default, and it means new files are readable by everyone on the machine. Fine for a laptop. Not always fine for a server holding other people's data.

~/secopslog — bash
$ umask 0022 touch newfile mkdir newdir ls -l
total 4 -rw-r--r-- 1 sam sam 0 Jul 17 10:50 newfile drwxr-xr-x 2 sam sam 4096 Jul 17 10:50 newdir

On a system handling sensitive data, tighten it. A umask of 027 withholds write from the group and every permission from others, so new files come out 640 (rw-r-----) and nothing is world-readable by accident. It is a quiet, real hardening step that costs nothing:

~/secopslog — bash
$ umask 027 touch secret.log ls -l secret.log
-rw-r----- 1 sam sam 0 Jul 17 10:52 secret.log

Setting umask in a shell lasts only for that shell. To make it stick for every login on a Debian or Ubuntu system, set it where the login process reads it, in /etc/login.defs (applied by PAM, the Pluggable Authentication Modules, the system that handles logins):

/etc/login.defs
# Default umask applied to new login sessions via pam_umask.
UMASK 027
Lowercase means live, uppercase means idle
-rwsr-xr-x has a live SUID bit. But -rwSr-xr-x, with a capital S, means the SUID bit is set while the owner's execute bit underneath it is missing, so nothing actually runs elevated and the file is usually broken. Same story for a capital T on a sticky directory that lost its execute bit. When you set these bits by hand with chmod, check that the letter came back lowercase, or the protection you think you added is not really there.

When a Special Bit Is a Backdoor

Flip the SUID story around and you see why attackers love it. Someone who briefly gains root wants a way back to root that survives losing their original foothold. A tidy trick: copy a shell, give the copy to root, and stamp it SUID. From then on any ordinary account can run that copy and become root on demand, with no password and no exploit to re-run.

~/secopslog — bash
$ # attacker, holding root for a moment, plants the backdoor: cp /bin/bash /tmp/.cache chmod 4755 /tmp/.cache # later, as an ordinary user: /tmp/.cache -p bash-5.1# id
uid=1000(sam) gid=1000(sam) euid=0(root) groups=1000(sam)

The -p flag tells bash not to drop the borrowed privileges on startup. Read the id output closely. The real UID (user ID, the fixed number Linux assigns your account) is still 1000 (sam), but the euid (effective user ID, the identity the kernel, the core of the operating system that enforces every access decision, actually checks) is 0. That is root, for every practical purpose. The file hides in /tmp under a name that looks like harmless junk, and it will keep handing out root shells until someone notices it.

An unfamiliar SUID-root binary is a compromise signal
A SUID-root file outside the standard system directories, especially anything sitting in /tmp, /home, or /dev/shm, is a classic backdoor and one of the first things a defender goes hunting for. Baseline the SUID and SGID set on every host, store that list, and alert when a new entry shows up (diff today's find / -perm /6000 output against yesterday's). Strip the special bit from anything that does not genuinely need it with chmod u-s. Running find / -perm -4000 to check is a habit you will keep for the rest of your career.
The three special bits, and what they actually do
SUID · 4000
On a program
Runs as the file's owner, not you
On a directory
Ignored on Linux
Spot it
the s in -rwsr-xr-x
SGID · 2000
On a program
Runs as the file's group
On a directory
New files inherit the folder's group
Spot it
the s in -rwxr-sr-x
Sticky · 1000
On a directory
Only a file's owner can delete it
On a program
Ignored on modern Linux
Spot it
the t in drwxrwxrwt
Octal values stack: 4000 + 2000 + 1000. On modern Linux, a special bit on the wrong kind of target is quietly ignored rather than an error.
Quick check
01A file /tmp/helper is owned by root and shows -rwsr-xr-x. A normal user with no sudo rights runs it. What happens?
Incorrect — the SUID bit overrides the caller's identity with the owner's.
Correct — SUID means the program runs with the owner's privileges, and root owns it.
Incorrect — a lowercase s sits on top of an active execute bit, so it does run.
Incorrect — SUID depends on the file's owner, not the caller's group membership.
02A hardened server is configured with umask 027. A service running on it creates a brand-new file. What permissions does that file end up with, and why does it help?
Incorrect — the umask applies to files too, and 666 is the starting base before the mask, not the result.
Correct — the file base of 666 minus the 027 mask leaves owner rw, group r, others nothing, so nothing is world-readable by accident.
Incorrect — files are never born executable; the base for files is 666, so the execute bit is never granted by default.
Incorrect — the 2 in 027 withholds only write from the group, so the group keeps read; only others are fully blocked.
03Your nightly find / -perm /6000 -type f sweep prints one line that was not there yesterday: -rwsr-xr-x 1 root root /dev/shm/.syscache. What is the most likely explanation and the right response?
Correct — a SUID-root file in a scratch area like /dev/shm is a classic backdoor, and a new entry against your baseline is guilty until explained.
Incorrect — nothing legitimate ships a SUID-root binary in /dev/shm, which is exactly why it stands out.
Incorrect — the string shows a lowercase s, so the SUID bit is live, not idle.
Incorrect — -type f restricts the match to files, and /6000 matches the SUID or SGID bit on them.

Baseline first, react second. A host whose SUID and SGID files you have already written down is a host where a planted backdoor stands out the instant it lands. Run the find sweep on a schedule, diff it against your known-good list, and treat every new line as guilty until you have explained it.

Try this

Work through “When a Special Bit Is a Backdoor” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: lowercase means live, uppercase means idle. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related