Zum Inhalt

Linux Discretionary Access Control: Mode Bits, SUID/SGID/Sticky, umask, POSIX ACLs & Capabilities

Executive Summary: Executing chmod -R 777 to "fix" a permission error destroys multi-tenant security boundaries and causes hardened daemons like sshd and sendmail to refuse startup immediately. This architectural reference dissects Linux Discretionary Access Control (DAC): file vs. directory bit semantics, special high-order bits (SUID 4000, SGID 2000, Sticky 1000), process umask bitmask subtraction, fine-grained POSIX Access Control Lists (setfacl / getfacl), ext4/XFS immutable attributes (chattr +i), and replacing root SUID binaries with Linux Kernel Capabilities (setcap).

  • File vs. Directory Bit Semantics


    Understand why x (Execute) on a directory (search/traverse bit) is required to stat() or cd into Inodes inside that directory.

  • SUID, SGID & Sticky Bit (1777)


    Master group-inheritance directories (chmod 2770) for shared team repositories and deletion-restricted Sticky directories (/tmp).

  • POSIX Extended ACLs (setfacl)


    Grant granular read/write access to specific service accounts (www-data, postgres) without altering primary UID/GID ownership.

  • Kernel Capabilities (setcap) & chattr


    Grant isolated privileges like cap_net_bind_service or cap_net_raw to unprivileged binaries and lock critical files with chattr +i.


1. Architecture: How the Kernel Evaluates DAC Permissions

When a process (carrying an Effective UID euid, Effective GID egid, and supplementary groups groups) invokes the open(2) or execve(2) syscall on a path, the Linux kernel evaluates permissions in strict short-circuit order—it does not OR the User, Group, and Other permissions together:

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TD
    Syscall["Process Calls open('/srv/app/config.env')"] --> Traverse{"1. Does Process Have 'x' (Traverse)<br/>on /, /srv, and /srv/app?"}
    Traverse -->|"No"| EACCES1["Return -EACCES (Permission Denied)"]
    Traverse -->|"Yes"| MatchUID{"2. Does Process euid == File UID?"}

    MatchUID -->|"Yes"| EvalUser["Evaluate ONLY User Bits (rwx------)<br/>(Short-Circuit: Group/Other Ignored!)"]
    MatchUID -->|"No"| MatchGID{"3. Is Process egid/groups in<br/>File GID or POSIX ACL?"}

    MatchGID -->|"Yes"| EvalGroup["Evaluate Group Bits / ACL Mask"]
    MatchGID -->|"No"| EvalOther["Evaluate Other Bits (------rwx)"]

The Crucial Difference Between File Bits and Directory Bits

Permission Bit Octal Value Meaning on a Regular File (-) Meaning on a Directory (d)
r (Read) 4 (100₂) Read the data blocks of the file (open with O_RDONLY). Read the list of filenames (readdir / ls) stored in the directory block.
w (Write) 2 (010₂) Modify or truncate the file's data blocks (O_WRONLY / O_RDWR). Create, rename, or delete (unlink) files inside the directory (requires x bit as well!).
x (Execute) 1 (001₂) Execute the file as a program (execve). Traverse / Search (lookup): Access Inode metadata (stat) or cd into the directory.

Why a User Can Delete a Root-Owned 0400 File Inside a Group-Writable Directory

Remember from VFS architecture: deleting (unlink) or renaming a file modifies the parent directory's entry table, not the file's own data blocks! If /srv/shared has permissions drwxrwxr-x (0775) and contains a file owned by root:root with mode 0400 (-r--------), any user with write (w + x) permission on /srv/shared can rm that root-owned file unless the Sticky Bit (1000 / +t) is set on /srv/shared!


2. Special Mode Bits (SUID, SGID, Sticky) & Process umask

Above the standard 9-bit rwxrwxrwx triad sit 3 high-order special bits (4000, 2000, 1000):

Special Bit Octal Symbolic Effect on Executable Files Effect on Directories
SUID 4000 u+s (rws) Process runs with Effective UID of the file owner (e.g., /usr/bin/passwd, /usr/bin/sudo). Ignored on Linux.
SGID 2000 g+s (rws) Process runs with Effective GID of the file's group. New files & subdirectories created inside automatically inherit the parent directory's GID instead of the creator's primary group!
Sticky 1000 +t (rwt) Ignored on modern Linux. Deletion Restriction: Only the file's owner, the directory's owner, or root can delete or rename files inside (e.g., /tmp is 1777).

(Note: An uppercase S or T in ls -l—such as rwS--- or rwt vs rwT—indicates that the special bit is set, but the underlying execute (x) bit is missing.)

Provisioning a Collaborative Team Directory with SGID + Capital 'X' & umask
# 1. Create a shared engineering directory where all new files inherit the 'devops' group (SGID 2770)
sudo groupadd -f devops
sudo install -d -m 2770 -o root -g devops /srv/devops-repo

# 2. Use symbolic capital '+X' to grant execute ONLY on directories (and already-executable scripts),
#    never accidentally making plain text/config files executable!
sudo chmod -R u=rwX,g=rwX,o= /srv/devops-repo

# 3. Set a strict default umask (0027) in /etc/profile.d/umask.sh:
#    Default File Base (0666) & ~0027 = 0640 (-rw-r-----)
#    Default Dir Base  (0777) & ~0027 = 0750 (drwxr-x---)
umask 0027

3. Fine-Grained Access Control: POSIX ACLs (setfacl & getfacl)

When a web server (www-data) needs read-only access to logs written by an application daemon (appuser:appgroup), adding www-data to appgroup might grant excessive access to other files owned by appgroup. Instead, apply a targeted POSIX Access Control List (ACL) together with a Default ACL (-d) so future rotated files automatically inherit the rule:

Applying & Auditing POSIX Extended ACLs
# 1. Grant user 'www-data' read/traverse access to existing files and directories in /var/log/custom-app
sudo setfacl -R -m u:www-data:rX /var/log/custom-app

# 2. Set a Default ACL (-d) on the directory so newly created log files automatically grant www-data r-x
sudo setfacl -R -d -m u:www-data:rX /var/log/custom-app

# 3. Inspect active ACLs and the effective mask (notice the '+' sign in ls -ld /var/log/custom-app)
getfacl /var/log/custom-app

4. Linux Capabilities (setcap) & Immutable Ext4/XFS Flags (chattr)

Instead of setting the dangerous SUID-root bit (chmod 4755) on a custom Go or C binary just so it can bind port 443 or send ICMP echo requests, grant only the specific Linux Kernel Capability required:

Granting Granular Kernel Capabilities & Locking Critical Config Files
# 1. Allow an unprivileged binary to bind ports < 1024 without running as root or SUID
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy

# 2. Verify capabilities attached to the binary's security.capability extended attribute
getcap /usr/local/bin/caddy

# 3. Make /etc/resolv.conf or backup directories immutable (+i) or append-only (+a) even against root!
sudo chattr +i /etc/resolv.conf
lsattr /etc/resolv.conf
# (Remove immutable flag before legitimate maintenance via: sudo chattr -i /etc/resolv.conf)