Back to Computing & Systems Foundations Series

Part 8: Linux Permissions & Security Model

May 13, 2026Wasil Zafar16 min read

Users, groups, the nine permission bits, special bits (setuid/setgid/sticky), Linux capabilities for fine-grained privilege, ACLs, and mandatory access controls — who can access what and why.

Table of Contents

  1. Users, Groups & UIDs
  2. File Permission Bits
  3. Special Permission Bits
  4. Linux Capabilities
  5. ACLs — Fine-Grained Permissions
  6. Mandatory Access Control
  7. Exercises
  8. Conclusion

Users, Groups & UIDs

Every Linux process runs as a user, identified by a numeric UID (User ID). Files have an owning UID and GID. The kernel makes all access control decisions based on these numbers — names like "alice" and "sudo" are just human-readable labels stored in /etc/passwd and /etc/group.

UID RangeTypeExamples
0root — superuser, bypasses most permissionsroot
1–999System accounts — services and daemonswww-data, nobody, sshd, docker
1000+Regular user accountsalice, bob
65534nobody — used for privilege separationNFS anonymous, nobody
# View your identity
id                   # uid, gid, and all supplementary groups
whoami               # Just the username
id alice             # View another user's IDs

# User and group databases
cat /etc/passwd      # username:x:UID:GID:comment:homedir:shell
cat /etc/group       # groupname:x:GID:members
cat /etc/shadow      # Hashed passwords (root-readable only)

# Manage users
sudo useradd -m -s /bin/bash alice   # Create user with home and shell
sudo passwd alice                     # Set password
sudo usermod -aG docker alice         # Add alice to docker group
sudo userdel -r alice                 # Delete user and home directory

# Switch users
su - alice           # Switch to alice (need alice's password)
sudo su -            # Switch to root (need sudo password)
sudo -u alice ls     # Run single command as alice

File Permission Bits

Every file has 9 permission bits (plus 3 special bits). The 9 bits are divided into three groups of 3: owner (u), group (g), and others (o). Each group has read (r=4), write (w=2), and execute (x=1) bits.

# Reading permission output from ls -la
ls -la /etc/passwd
# -rw-r--r--  1  root  root  2847  May 1  /etc/passwd
#  ^ ^^^  ^^^                          ^
#  |  |    |                           filename
#  |  |    +-- others: r-- = read only (004)
#  |  +------- group:  r-- = read only (040)
#  +---------- owner:  rw- = read+write (600)
# Total octal: 644

# Common permission patterns
# 644  rw-r--r--  Regular files (readable by all, writable by owner)
# 755  rwxr-xr-x  Directories and executables
# 600  rw-------  Private files (SSH keys, secret configs)
# 700  rwx------  Private directories
# 777  rwxrwxrwx  (Avoid! Anyone can modify/delete)

chmod & umask

# chmod — change file mode (permissions)

# Octal notation
chmod 644 file.txt             # rw-r--r--
chmod 755 script.sh            # rwxr-xr-x
chmod 600 ~/.ssh/id_rsa        # rw------- (SSH key must be 600)
chmod -R 755 /var/www/html/    # Recursive

# Symbolic notation (u=user/owner, g=group, o=others, a=all)
chmod u+x script.sh            # Add execute for owner
chmod go-w file.txt            # Remove write from group and others
chmod a+r file.txt             # Add read for everyone
chmod u=rwx,g=rx,o=r file     # Set exact permissions

# umask — default permission mask for new files
umask                          # Show current umask (e.g., 0022)
# New files = 0666 & ~umask   → 0666 & ~0022 = 0644
# New dirs  = 0777 & ~umask   → 0777 & ~0022 = 0755

umask 0077                     # More secure: new files=600, dirs=700
umask 0022                     # Standard: new files=644, dirs=755

chown & chgrp

# chown — change ownership
sudo chown alice file.txt           # Change owner
sudo chown alice:alice file.txt     # Change owner and group
sudo chown :www-data file.txt       # Change group only
sudo chown -R alice:alice /var/www/ # Recursive

# chgrp — change group
sudo chgrp docker /var/run/docker.sock  # Let docker group access socket

# View file ownership in numeric form
ls -lan /etc/shadow   # Shows UID/GID numbers instead of names

Special Permission Bits

setuid & setgid

The setuid bit on an executable causes it to run with the owner's UID rather than the caller's UID. This is how passwd (owned by root) can write to /etc/shadow even when run by a non-root user.

# Find setuid/setgid executables on the system
find / -xdev -perm -4000 -type f 2>/dev/null   # setuid (SUID)
find / -xdev -perm -2000 -type f 2>/dev/null   # setgid (SGID)

# Common setuid programs
ls -la $(which passwd)    # -rwsr-xr-x  root root  (s = setuid set)
ls -la $(which sudo)      # -rwsr-xr-x  root root
ls -la $(which ping)      # -rwxr-xr-x  (modern kernels use capabilities instead)

# How setuid appears in ls output
# -rwsr-xr-x  s in owner's execute position = setuid + execute
# -rwSr-xr-x  S in owner's execute position = setuid WITHOUT execute (unusual)

# Set setuid on a file (for academic understanding)
chmod u+s /tmp/test-binary   # Adds setuid
chmod 4755 /tmp/test-binary  # Equivalent: 4=setuid, 755=permissions
SUID Security Risk: A setuid program that has a vulnerability (buffer overflow, injection) gives an attacker root access because the exploit runs with root UID. This is why the number of SUID binaries on a system should be minimised. Modern systems prefer Linux capabilities over setuid — they grant only the specific privilege needed (e.g., CAP_NET_RAW for ping) rather than full root.

Sticky Bit

The sticky bit on a directory means: only the file's owner (or root) can delete or rename it, even if others have write permission on the directory. This is why /tmp is writable by everyone but you can only delete your own files there.

# /tmp has sticky bit set
ls -la / | grep " tmp"
# drwxrwxrwt  — t at the end = sticky bit set (T = sticky without execute)

# Demonstrate sticky bit
chmod +t /shared-dir    # Set sticky bit
chmod 1777 /shared-dir  # Equivalent: 1=sticky, 777=permissions

# Without sticky bit: anyone with write access to directory can delete ANY file
# With sticky bit: only file owner can delete their files

Linux Capabilities

Linux capabilities break root's privileges into ~40 fine-grained units. Instead of "run as root" (all-or-nothing), you can grant just the specific capability a program needs.

CapabilityWhat It GrantsExample Use
CAP_NET_BIND_SERVICEBind to ports < 1024nginx on port 80 without root
CAP_NET_RAWUse raw/packet socketsping, tcpdump
CAP_SYS_PTRACETrace other processesstrace, gdb
CAP_SYS_ADMINWide-ranging admin operationsmount, cgroup ops (very broad!)
CAP_CHOWNChange file ownership arbitrarilyInstallation scripts
CAP_KILLSend signals to any processProcess managers
CAP_NET_ADMINNetwork config (routes, interfaces)ip command
CAP_DAC_OVERRIDEBypass file permission checksBackup tools
# View capabilities of a binary
getcap /usr/bin/ping     # cap_net_raw=ep  (e=effective, p=permitted)
getcap /usr/bin/tcpdump  # cap_net_admin,cap_net_raw=eip

# View capabilities of a running process
cat /proc/$$/status | grep -i cap
# CapPrm, CapEff, CapBnd — in hex, decode with capsh

capsh --decode=$(cat /proc/1/status | grep CapEff | awk '{print $2}')
# Shows: cap_chown, cap_dac_override, cap_setuid...

# Grant capabilities to a binary (root required)
# sudo setcap cap_net_bind_service=+ep /usr/local/bin/myapp
# Now myapp can bind port 80 without running as root

# Docker capability management
# docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx

ACLs — Fine-Grained Permissions

Standard Unix permissions only support one owner, one group, and others. ACLs (Access Control Lists) allow granting specific permissions to any number of users and groups on any file.

# View ACLs (if supported by filesystem)
getfacl /etc/passwd         # Shows: owner, group, ACL entries

# Set ACL to grant bob read+write on a file owned by alice
touch /tmp/shared-file
sudo setfacl -m u:bob:rw /tmp/shared-file    # User bob: rw
sudo setfacl -m g:devs:r  /tmp/shared-file   # Group devs: r
getfacl /tmp/shared-file
# user::rw-    (owner)
# user:bob:rw- (bob's ACL)
# group::r--   (group)
# group:devs:r-- (devs ACL)
# other::r--   (others)

# Remove ACL entry
sudo setfacl -x u:bob /tmp/shared-file
sudo setfacl -b /tmp/shared-file   # Remove all ACLs

Mandatory Access Control

Standard Linux permissions are discretionary (DAC) — the file owner decides who can access what. Mandatory Access Control (MAC) adds a second layer enforced by the kernel regardless of owner decisions, preventing even root from accessing certain resources.

SELinux (RHEL, CentOS, Fedora): Labels every file, process, and port with a security context. Policy rules define which contexts can access which. If nginx (labelled httpd_t) tries to read a file labelled user_home_t that SELinux policy forbids, the access is denied even if DAC permissions allow it.

AppArmor (Ubuntu, Debian): Profile-based — each program has a profile defining which files it can access and which capabilities it can use. Easier to understand than SELinux but less expressive.

# SELinux status
sestatus 2>/dev/null || echo "SELinux not installed"
getenforce 2>/dev/null     # Enforcing / Permissive / Disabled

# View SELinux context of files
ls -Z /etc/passwd          # system_u:object_r:passwd_file_t:s0

# View SELinux context of processes
ps -eZ | grep nginx        # system_u:system_r:httpd_t:s0  nginx

# AppArmor status
sudo aa-status 2>/dev/null | head -20
# Lists profiles in enforce/complain mode

# View AppArmor profile for a binary
sudo cat /etc/apparmor.d/usr.sbin.nginx 2>/dev/null | head -20

# View AppArmor denials
sudo dmesg | grep "apparmor=\"DENIED\"" | tail -10
sudo journalctl | grep "apparmor" | grep DENIED | tail -10
Container Security

How Docker Uses All These Mechanisms Together

Docker containers use a defence-in-depth combination of the mechanisms in this article: (1) UIDs — containers run as non-root users when configured with USER app in Dockerfile; (2) Capabilities — Docker drops all capabilities by default except ~14, and you can use --cap-drop ALL for minimal privilege; (3) Seccomp — a syscall filter (similar in spirit to capabilities) that blocks dangerous syscalls like ptrace, reboot, mount; (4) AppArmor — Docker applies the docker-default AppArmor profile; (5) Linux namespaces — UID namespace remapping makes container root (UID 0) map to an unprivileged host UID. This is why "rootless Docker" is the modern security recommendation.

Docker SecuritySeccompRootless Containers

Exercises

# Exercise 1: Read and decode file permissions
ls -la /usr/bin/sudo /usr/bin/passwd /tmp
# What do the 's' and 't' characters mean?

# Exercise 2: Find all setuid files on the system
find / -xdev -perm /4000 -type f 2>/dev/null | head -20
# Review which ones are truly necessary

# Exercise 3: Check capabilities of common binaries
for bin in ping traceroute tcpdump python3 node; do
    path=$(which $bin 2>/dev/null)
    [ -n "$path" ] && echo -n "$bin: " && (getcap $path || echo "no caps")
done

# Exercise 4: Create and test ACLs
mkdir /tmp/acl-test
touch /tmp/acl-test/secret.txt
echo "secret data" > /tmp/acl-test/secret.txt
chmod 700 /tmp/acl-test/secret.txt    # Only owner can read
getfacl /tmp/acl-test/secret.txt
# Now grant read access to another user via ACL:
# setfacl -m u:www-data:r /tmp/acl-test/secret.txt
# getfacl /tmp/acl-test/secret.txt

# Exercise 5: Explore your process capabilities
cat /proc/$$/status | grep Cap   # In hex
# To decode: capsh --decode=

Conclusion & Next Steps

Linux security is layered: UIDs for identity, permission bits for basic access control, setuid for controlled privilege escalation, capabilities for fine-grained privilege, ACLs for complex multi-user scenarios, and SELinux/AppArmor for mandatory, policy-enforced access control. Understanding this stack is essential for secure container configuration, cloud IAM integration, and privilege separation in application design.