open atlas
↑ Back to track
Linux, the operating system LIN · 05 · 02

permissions deeper

Beyond rwx: setuid makes a binary run as its owner; setgid on a directory forces group inheritance; sticky on /tmp stops users deleting others files. umask gates default permissions, and POSIX ACLs grant per-user access that the nine-bit model cannot express.

LIN Middle ◷ 22 min
Level
FoundationsJuniorMiddleSenior

You already know rwx. But then you find /usr/bin/passwd owned by root with mode -rwsr-xr-x — that s where the execute bit should be is not a typo. Or you create a shared /var/project/ directory and files keep ending up owned by the creator’s primary group instead of the project group, so half the team cannot write to them. Or a penetration tester tells you “that setuid binary is your privilege escalation path”. Or a security review flags a world-writable directory without a sticky bit and you don’t immediately understand the threat. The nine-bit rwx model you know is the floor; the special bits and ACLs above it are where real systems live.

Goal

After this lesson you can explain what setuid, setgid, and sticky bits do (with the real threat model), derive effective permissions from a umask, and grant per-user access using POSIX ACLs when group membership is not sufficient.

1

The setuid bit makes a binary run as its file owner, not as the caller. This is how unprivileged users can change their own passwords: /usr/bin/passwd is owned by root and has the setuid bit set, so when alice runs it, the process’s effective UID becomes 0 (root), allowing it to write to /etc/shadow.

# See setuid in action
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 59976 Feb  6 16:54 /usr/bin/passwd
#    ^-- lowercase 's' in owner-execute position = setuid + executable
#        uppercase 'S' would mean setuid is set but file is NOT executable (useless)

# The octal value: setuid is bit 4 in the high nibble
stat -c '%a %n' /usr/bin/passwd
# 4755 /usr/bin/passwd
# 4 = setuid, 7 = rwx (owner), 5 = r-x (group), 5 = r-x (other)

# Find all setuid-root binaries on the system
find /usr/bin /usr/sbin /bin /sbin -perm -4000 -ls 2>/dev/null
# -rwsr-xr-x root   /usr/bin/passwd
# -rwsr-xr-x root   /usr/bin/sudo
# -rwsr-xr-x root   /usr/bin/su
# -rwsr-xr-x root   /usr/bin/newgrp

Every setuid-root binary is a potential privilege-escalation vector. If /usr/bin/customtool has a command injection or path traversal vulnerability, an attacker can use it to spawn a root shell. This is why security audits count setuid binaries and penetration testers target them — a single exploitable setuid binary defeats all other permission controls.

2

The setgid bit on a directory forces group inheritance. On a plain directory, new files get the creator’s primary GID. With setgid on the directory, new files inherit the directory’s group — the mechanism that makes shared project directories actually work.

# Shared directory without setgid — group chaos
mkdir /var/project
chown root:devteam /var/project
chmod 775 /var/project
# alice (primary group: alice) creates a file:
touch /var/project/notes.txt
ls -l /var/project/notes.txt
# -rw-rw-r-- 1 alice alice 0 ...   <- group 'alice', not 'devteam'
# bob cannot write to it even though he's in devteam

# Fix: add setgid to the directory
chmod g+s /var/project
# or in octal: chmod 2775 /var/project
ls -ld /var/project
# drwxrwsr-x 2 root devteam 4096 ...   <- 's' in group-execute position

# Now alice creates a file:
touch /var/project/report.txt
ls -l /var/project/report.txt
# -rw-rw-r-- 1 alice devteam 0 ...   <- group 'devteam' inherited!

# Setgid on an executable binary has a different meaning:
# the process runs with the file's group as effective GID (analogous to setuid)
# Example: /usr/bin/wall is setgid 'tty' to write to terminals
ls -l /usr/bin/wall
# -rwxr-sr-x 1 root tty 35048 ...

Setgid directories are the correct solution for shared collaboration directories. Without it you end up with a mix of per-user group ownerships and half the team unable to edit each other’s files.

3

The sticky bit on a directory prevents users from deleting each other’s files. Without it, any user with write permission to a directory can delete any file in it — even files they don’t own.

# /tmp is the canonical example: world-writable, but sticky
ls -ld /tmp
# drwxrwxrwt 20 root root 4096 ...
#          ^-- 't' in other-execute = sticky bit + world-executable
#              'T' = sticky but NOT world-executable (rare)

# Without sticky: alice can delete bob's file if she has write on the dir
# With sticky:
# - You can delete your own files
# - You can delete files you OWN regardless of who else is in the dir
# - root can delete anything
# - You CANNOT delete files owned by others

# Set sticky bit
chmod +t /var/shared
# octal: chmod 1777 /var/shared   (1 = sticky, 777 = rwxrwxrwx)
stat -c '%a %n' /var/shared
# 1777 /var/shared

# The real threat: a world-writable dir WITHOUT sticky bit
# Anyone with write access can unlink() any file in the dir
# Classic attack: rm /var/run/app.pid ; ln -s /etc/cron.d/evil /var/run/app.pid
# Then wait for the app to write its PID — it overwrites /etc/cron.d/evil

The sticky-without-bit attack is a classic symlink race. A world-writable directory without sticky lets any user delete then replace any file in that directory with a symlink, then wait for a privileged process to write through it. This is why find / -type d -perm -0002 ! -perm -1000 (world-writable dirs without sticky) is a standard security audit check.

4

umask determines the permission bits that are removed from newly created files and directories. It is a mask of bits to strip, applied at creation time.

# See current umask
umask
# 0022

# How new-file permissions are derived:
# Files start at 0666 (rw-rw-rw-) — execute is never set by default for files
# Dirs  start at 0777 (rwxrwxrwx)
# Effective = start & ~umask

# umask 022:
# ~022 = 755 (binary: 111101101)
# new file: 666 & ~022 = 666 & 755 = 644  (rw-r--r--)
# new dir:  777 & ~022 = 777 & 755 = 755  (rwxr-xr-x)

touch test_file && ls -l test_file
# -rw-r--r-- 1 alice alice 0 ...   <- 644, confirmed

# More restrictive umask for shared environments:
umask 027    # removes group write + all other permissions
# new file: 666 & ~027 = 666 & 750 = 640  (rw-r-----)
# new dir:  777 & ~027 = 777 & 750 = 750  (rwxr-x---)

# Set umask in shell profile for persistence
echo 'umask 027' >> ~/.bashrc

# System-wide default is in /etc/login.defs (UMASK line)
grep '^UMASK' /etc/login.defs
# UMASK    022

A umask of 022 means “don’t let group or other write”. A umask of 027 adds “don’t let other read or execute either”. Services that write log files or temp files inherit the daemon’s umask — a daemon with umask 000 creates world-writable files, which is a common misconfiguration in init scripts that don’t explicitly set the umask before dropping privileges.

5

POSIX ACLs give per-user and per-group grants beyond the nine-bit model. When you need “alice, bob, and the ci-runner group can read this file, but nobody else can”, ACLs are the answer — the standard owner/group/other model cannot express that without contrived group management.

# Install ACL tools (usually pre-installed)
apt install acl   # Debian/Ubuntu

# Grant alice read+write on a file she doesn't own
setfacl -m u:alice:rw /etc/app/config.conf
setfacl -m g:ci-runners:r /etc/app/config.conf

# Read the ACL
getfacl /etc/app/config.conf
# # file: etc/app/config.conf
# # owner: root
# # group: root
# user::rw-          <- owner (root) permissions
# user:alice:rw-     <- alice gets rw
# group::r--         <- owning group (root) gets r
# group:ci-runners:r-- <- ci-runners group gets r
# mask::rw-          <- effective permission ceiling for named entries
# other::---         <- others get nothing

# The mask: named ACL entries are ANDed with mask at enforcement time
# Setting mask explicitly:
setfacl -m m::r /etc/app/config.conf  # now alice:rw & mask:r → effective r only

# Remove a specific ACL entry
setfacl -x u:alice /etc/app/config.conf

# Remove all ACLs (revert to standard permissions)
setfacl -b /etc/app/config.conf

# ACLs on directories: -d sets default ACLs that new files inherit
setfacl -d -m u:alice:rw /var/project/
# New files created in /var/project/ will inherit alice:rw

The + at the end of ls -l output flags that a file has an ACL (-rw-r--r--+). The mask is the ACL feature that surprises operators most: if you chmod g+w file on a file with ACLs, you are actually setting the mask, which silently caps all named entries’ effective permissions.

Worked example

Setting up a shared build directory where developers and CI both write, but only the project group owns files.

Goal: /var/builds/myapp/ where developers (group dev) and CI runner (user ci-runner) can both write. Files should always be owned by group dev. Nobody outside dev or ci-runner should read anything.

# Step 1: create directory with setgid + restrictive permissions
sudo mkdir -p /var/builds/myapp
sudo chown root:dev /var/builds/myapp
sudo chmod 2770 /var/builds/myapp
# 2 = setgid, 7 = rwx (owner/root), 7 = rwx (group/dev), 0 = --- (others)
ls -ld /var/builds/myapp
# drwxrws--- 2 root dev 4096 ...

# Step 2: grant ci-runner write access via ACL (it's not in group dev)
sudo setfacl -m u:ci-runner:rwx /var/builds/myapp
sudo setfacl -d -m u:ci-runner:rwx /var/builds/myapp  # inherit to new files

# Step 3: verify
getfacl /var/builds/myapp
# user::rwx
# user:ci-runner:rwx
# group::rwx
# mask::rwx
# other::---
# default:user::rwx
# default:user:ci-runner:rwx
# default:group::rwx
# default:mask::rwx
# default:other::---

# Step 4: test — ci-runner creates a file
sudo -u ci-runner touch /var/builds/myapp/artifact.tar.gz
ls -l /var/builds/myapp/artifact.tar.gz
# -rw-rw----+ 1 ci-runner dev 0 ...   <- group 'dev' inherited via setgid; + means ACL

The combination of setgid (group inheritance) and a default ACL (per-user grants that propagate to children) is the idiomatic solution for exactly this kind of multi-principal write scenario.

Common mistake

A common mistake when setting ACLs is forgetting the mask. If you set setfacl -m u:alice:rw file and then run chmod 644 file, you have just set the mask to r-- (chmod on a file with ACLs sets the mask for the group permission bits). Alice’s rw entry now has an effective permission of r because it is ANDed with the mask. Always re-check getfacl after any chmod on an ACL-bearing file to confirm effective permissions are what you expect.

Why this works

Why does the sticky bit have such a strange name? In the original PDP-11 Unix (early 1970s), the sticky bit meant “keep this program’s text segment in swap even when not running” — a performance hint for frequently-used binaries. Modern kernels ignore it entirely on files (the VM subsystem handles this automatically). The bit was repurposed for directories in BSD Unix to mean “only delete your own files”. Linux inherited the directory semantics but dropped the file semantics. This is why man chmod still documents both meanings — one is historical, one is what actually matters.

Check yourself
Quiz

You run 'umask 027' then 'mkdir newdir'. What are the permissions on newdir, and what would they be for a new file created with 'touch newfile'?

Recap

Beyond rwx, three special bits exist. Setuid (4000) makes a binary run as its file owner — the mechanism behind /usr/bin/passwd and a privilege-escalation vector if abused. Setgid (2000) on a directory forces group inheritance for new files — the fix for shared project directories. Sticky (1000) on a world-writable directory prevents users from deleting files they don’t own — /tmp uses this. umask is a bit mask applied at creation: new files start at 0666, dirs at 0777, minus the umask bits. POSIX ACLs (setfacl/getfacl) add per-user and per-group grants beyond the nine-bit model; the ACL mask caps effective permissions for named entries. Use chmod + on a file with ACLs and you change the mask, not just the group permission.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.