Skip to content

Linux Security Fundamentals & DAC/MAC

Chapter 14: Linux Security Fundamentals & DAC/MAC

Section titled “Chapter 14: Linux Security Fundamentals & DAC/MAC”
  • Understand the Linux security model from first principles
  • Understand DAC (Discretionary Access Control) — the traditional Unix model
  • Understand MAC (Mandatory Access Control) — SELinux and AppArmor
  • Know the Linux capability model
  • Understand the complete permission system

Linux security is about controlling who can do what. At its most basic level, it uses owners, groups, and permissions (Read, Write, Execute) to protect files. If you don’t have the right permissions, the kernel strictly blocks your access.

Linux Security Layers
──────────────────────
┌─────────────────────────────────────────────────────────────┐
│ DEFENSE IN DEPTH │
│ │
│ Layer 1: Physical Security │
│ Layer 2: Network Security (firewall, VPN) │
│ Layer 3: Application Security (input validation, TLS) │
│ Layer 4: OS Security: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ DAC: File permissions, ownership │ │
│ │ Capabilities: Fine-grained privilege control │ │
│ │ MAC: SELinux/AppArmor mandatory policies │ │
│ │ Namespaces: Process isolation │ │
│ │ Seccomp: Syscall filtering │ │
│ │ Audit: logging (auditd) │ │
│ └─────────────────────────────────────────────────┘ │
│ Layer 5: Data Encryption (LUKS, TLS) │
└─────────────────────────────────────────────────────────────┘

DAC is the traditional Unix permission model. The resource owner discretely (at their own discretion) controls access.

Permission Breakdown
─────────────────────
-rw-r--r-- 1 nginx www-data 1234 Jan 15 nginx.conf
│ │ │ │
│ │ │ └── Others: r-- = read only
│ │ └───── Group (www-data): r-- = read only
│ └──────── Owner (nginx): rw- = read + write
└────────── File type: - = regular file
d = directory
l = symlink
c = character device
b = block device
s = socket
Octal representation:
r = 4, w = 2, x = 1
rw-r--r-- = 644
rwxr-xr-x = 755
rwx------ = 700
rw-rw-r-- = 664
Terminal window
# View permissions
ls -la /etc/nginx/nginx.conf
stat /etc/nginx/nginx.conf
# Change permissions
chmod 644 /etc/nginx/nginx.conf # Octal
chmod u=rw,g=r,o=r /etc/nginx/nginx.conf # Symbolic
# Change ownership
chown nginx:www-data /etc/nginx/nginx.conf
chown -R nginx:www-data /var/www/ # Recursive
# Umask: default permissions for new files
umask # View current umask
# 0022 means: new files get 644, directories 755
# Change umask for session
umask 0027 # New files: 640, directories: 750
Special Bits (High-Security Concepts)
──────────────────────────────────────
SetUID (SUID) - bit 4000:
When set on an executable:
→ The program runs with the FILE OWNER's privileges
→ Not the calling user's privileges!
Example: /usr/bin/passwd
ls -la /usr/bin/passwd
-rwsr-xr-x 1 root root ← 's' = SUID set
When user 'alice' runs passwd:
→ passwd runs as root (not alice!)
→ This is how alice can change her password
(which requires writing to /etc/shadow, owned by root)
SetGID (SGID) - bit 2000:
On files: execute with file's group privileges
On directories: new files inherit directory's group
Sticky bit - bit 1000:
On directories: only file owner can delete their own files
Example: /tmp (all can write, but only owner can delete)
ls -la / | grep tmp
drwxrwxrwt ← 't' = sticky bit
Terminal window
# Find SUID files (security audit - these can be privilege escalation paths!)
find / -perm -4000 -type f 2>/dev/null
find / -perm -4000 -user root -type f 2>/dev/null | sort
# Common legitimate SUID binaries:
# /usr/bin/passwd /usr/bin/sudo
# /usr/bin/su /usr/bin/ping (modern Linux: uses capabilities instead)
# Any UNEXPECTED SUID binary is a red flag!
# Set SUID
chmod u+s /path/to/program # Set SUID
chmod 4755 /path/to/program # Set SUID with 755 perms
# Remove all SUID from a filesystem (emergency)
find / -xdev -perm -4000 -exec chmod u-s {} \;
root (UID=0) Problems
──────────────────────
Traditional Unix: Binary model
- Root can do EVERYTHING
- Non-root can do NOTHING privileged
Problems:
1. Any root process can do anything
→ nginx running as root = nginx can read /etc/shadow!
2. All-or-nothing privilege escalation
3. SUID binaries expand attack surface
Solution: Linux Capabilities (subsection 14.3)
→ Split root's omnipotence into granular capabilities

Linux capabilities break root’s omnipotent power into 37+ granular capabilities. A process can have some capabilities without being full root.

Root Powers Split Into Capabilities
─────────────────────────────────────
Traditional root → CAP_NET_ADMIN (configure network interfaces)
Traditional root → CAP_NET_BIND_SERVICE (bind to ports < 1024)
Traditional root → CAP_KILL (kill any process)
Traditional root → CAP_SETUID (change UID)
Traditional root → CAP_SYS_ADMIN (many admin operations)
Traditional root → CAP_DAC_OVERRIDE (bypass file permissions)
Traditional root → CAP_CHOWN (change file ownership)
Traditional root → CAP_SYS_PTRACE (trace processes)
... (37+ total)
Modern approach: Grant ONLY what's needed

Each process has multiple capability sets:

Process Capability Sets
───────────────────────
Permitted: Maximum capabilities the process can use
Effective: Currently active capabilities (can be subset of permitted)
Inheritable: Capabilities passed to exec'd programs
Bounding: Maximum capabilities ANY exec can gain
Ambient: Capabilities for non-privileged exec (Linux 4.3+)
Terminal window
# ── View Process Capabilities ─────────────────────────────
# Your own capabilities
cat /proc/$$/status | grep Cap
# CapInh: 0000000000000000 (inheritable)
# CapPrm: 0000000000000000 (permitted)
# CapEff: 0000000000000000 (effective)
# CapBnd: 000001ffffffffff (bounding)
# CapAmb: 0000000000000000 (ambient)
# Decode capability mask
capsh --decode=000001ffffffffff
# View capabilities of a process by name
getpcaps $(pidof nginx)
# ── Grant Capabilities to an Executable ───────────────────
# Allow nginx to bind to port 80 WITHOUT running as root
setcap cap_net_bind_service=+ep /usr/sbin/nginx
# Verify
getcap /usr/sbin/nginx
# /usr/sbin/nginx = cap_net_bind_service+ep
# Remove all capabilities from a binary
setcap -r /usr/sbin/nginx
# ── Important Capabilities for Security Review ─────────────
# CAP_SYS_ADMIN: Most dangerous — can do almost anything root can
# (mount filesystems, load modules, access raw I/O)
# CAP_NET_ADMIN: Configure interfaces, routing, iptables
# CAP_SETUID: Change UID (can become root!)
# CAP_DAC_OVERRIDE: Bypass ALL file permission checks
# CAP_SYS_PTRACE: Debug processes (can read memory of ANY process)
# Security audit: find processes with dangerous capabilities
for pid in /proc/[0-9]*/; do
cap=$(cat $pid/status 2>/dev/null | grep CapEff | awk '{print $2}')
if [ "$cap" != "0000000000000000" ] && [ -n "$cap" ]; then
decoded=$(capsh --decode=$cap 2>/dev/null)
comm=$(cat $pid/comm 2>/dev/null)
echo "PID=$(basename $pid) COMM=$comm CAPS=$decoded"
fi
done | grep -v "^$"

MAC goes beyond DAC by enforcing security policies that even root cannot override (unless root explicitly modifies the policy). Linux MAC is implemented via the Linux Security Module (LSM) framework.

DAC vs MAC
───────────
DAC (file permissions):
✓ User controls their own files
✗ root can bypass all DAC
✗ Compromised root = game over
MAC (SELinux/AppArmor):
✓ Kernel enforces policy regardless of user identity
✓ Even root is constrained by MAC policy
✓ Process confined to specific operations (deny everything else)
✓ Compromised nginx = attacker can only do what nginx is allowed
✗ Complex to configure
✗ Can break applications if policy is wrong
Linux Security Module (LSM) Architecture
─────────────────────────────────────────
Application calls: open("/etc/shadow", O_RDONLY)
▼ VFS layer
┌──────────────────────────────────────────────────┐
│ DAC Check: file permissions │
│ Does process have permission? (yes → continue) │
└──────────────────────────────────────────────────┘
│ passed DAC
┌──────────────────────────────────────────────────┐
│ LSM Hook: security_inode_permission() │
│ │
│ SELinux or AppArmor checks its policy: │
│ "Is process nginx_t allowed to read shadow_t?" │
│ │
│ If YES: access granted │
│ If NO: EACCES returned even if DAC says OK! │
└──────────────────────────────────────────────────┘

Common Linux Privilege Escalation Paths
────────────────────────────────────────
1. Exploiting SUID binaries with vulnerabilities
→ Mitigation: Minimize SUID binaries, use capabilities instead
2. Sudo misconfigurations
→ Example: "user ALL=(ALL) NOPASSWD: /usr/bin/vim"
→ vim can run shell commands → instant root
→ Mitigation: Audit /etc/sudoers, use specific commands
3. Writable cron jobs owned by root
→ If /etc/cron.d/myjob is world-writable, attacker adds commands
→ Mitigation: strict permissions on cron directories
4. PATH hijacking
→ Vulnerable SUID binary calls "ls" without full path
→ Attacker creates /tmp/ls that's actually a shell
→ Binary calls attacker's ls as root
→ Mitigation: Use full paths in SUID programs
5. Exploiting services running as root
→ Vulnerable nginx/Apache running as root
→ RCE gives attacker root shell
→ Mitigation: Run services as non-root users
6. Kernel vulnerabilities
→ Dirty Cow (CVE-2016-5195), DirtyCred, etc.
→ Mitigation: Keep kernel patched, use MAC (SELinux/AppArmor)
Terminal window
# Security audit checklist
echo "=== SUID Binaries ==="
find / -xdev -perm -4000 -type f -printf "%p %u %g %m\n" 2>/dev/null | sort
echo "=== World-Writable Files ==="
find / -xdev -perm -0002 -type f 2>/dev/null | grep -v proc | head -20
echo "=== Sudo Configuration ==="
cat /etc/sudoers
ls -la /etc/sudoers.d/
echo "=== Processes Running as Root ==="
ps aux | awk '$1 == "root" && $11 != "[" {print $1, $2, $11}' | head -20
echo "=== Capabilities on Executables ==="
find / -xdev -not -path "*/proc/*" -exec getcap {} \; 2>/dev/null | grep -v "^$"
echo "=== Unowned Files (orphaned) ==="
find / -xdev \( -nouser -o -nogroup \) 2>/dev/null | head -20

Scenario 1: Service Needs Port 80 Without Root

Section titled “Scenario 1: Service Needs Port 80 Without Root”
Terminal window
# Bad approach: Run nginx as root
# [Service]
# User=root ← DANGEROUS
# Good approach 1: Use capabilities
setcap cap_net_bind_service=+ep /usr/sbin/nginx
# nginx can bind to port 80 without root
# Good approach 2: Use systemd capabilities
# In nginx.service [Service]:
User=nginx
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# Good approach 3: Redirect port 80 to 8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
# nginx listens on 8080, no special privileges needed
# Good approach 4: Use unprivileged port binding (Linux 4.11+)
sysctl net.ipv4.ip_unprivileged_port_start=80

Scenario 2: Investigating “Permission Denied” Errors

Section titled “Scenario 2: Investigating “Permission Denied” Errors”
Terminal window
# Application getting EACCES even though DAC looks correct
# Step 1: Check DAC permissions
ls -laZ /path/to/resource # -Z shows SELinux context
stat /path/to/resource
# Step 2: Check if SELinux is blocking
ausearch -m avc -ts today # Recent SELinux denials
sealert -a /var/log/audit/audit.log # Human-readable SELinux alerts
# Step 3: Check AppArmor
dmesg | grep "apparmor.*DENIED"
cat /var/log/kern.log | grep apparmor
# Step 4: Check capabilities
getpcaps $(pidof myapp)
# Step 5: Run strace to see exact error
strace -e trace=open,read,write,access -p $(pidof myapp) 2>&1 | grep "EACCES\|EPERM"
# The fix depends on the root cause:
# DAC: chmod/chown the file
# SELinux: semanage fcontext or audit2allow
# AppArmor: update profile to allow the access
# Capabilities: setcap or add AmbientCapabilities to service

Q1: What is the difference between DAC and MAC?

Answer: DAC (Discretionary Access Control) is the traditional Unix/Linux file permission system where the resource owner discretely controls access via permissions (rwxrwxrwx) and ownership. Root can bypass all DAC. MAC (Mandatory Access Control) enforces security policies at the kernel level regardless of user identity — even root is constrained by MAC. SELinux and AppArmor implement MAC on Linux. The key difference: with DAC, a compromised root account = total system compromise. With MAC, even a compromised root is limited to what the MAC policy permits. In practice, you run both: DAC handles normal access control, MAC provides defense-in-depth.

Q2: What are Linux capabilities and why were they introduced?

Answer: Linux capabilities break root’s omnipotent power into ~37 granular privileges. Before capabilities, privilege escalation was binary: either you had full root power or none. Capabilities allow granting specific powers: CAP_NET_BIND_SERVICE to bind to port 80, CAP_NET_ADMIN to configure networking, CAP_SYS_PTRACE to debug processes — without giving full root. This enables the principle of least privilege: nginx only gets CAP_NET_BIND_SERVICE, not CAP_SYS_ADMIN. If nginx is compromised, the attacker has limited privileges.

Q3: You discover a file with SUID bit set to an unexpected binary. What are your concerns and next steps?

Answer: SUID on an unexpected binary is a serious security red flag. When executed, the binary runs with the file owner’s (typically root’s) privileges. Concerns: (1) Could be backdoor planted by an attacker who previously had root access. (2) Could be a legitimate tool that was misconfigured with SUID. (3) Could be an exploitable binary providing privilege escalation. Steps: (1) Immediately note the path, hash, and creation/modification time. (2) Compare against known-good SUID list (from package manager: dpkg -V or rpm -Va). (3) Check if the binary is in the package database: dpkg -S /path/to/binary. (4) If unknown, treat as potential compromise — begin incident response. (5) Remove SUID: chmod u-s /path/to/binary and investigate further.


ConceptKey Point
DACTraditional Unix permissions, owner controls
SUIDRun with file owner’s privileges (powerful + risky)
CapabilitiesFine-grained root privilege decomposition
MACKernel-enforced policy, constrains even root
LSMFramework enabling SELinux/AppArmor hooks
Defense in DepthMultiple security layers

Basic Linux usage. Understand file permissions (rwx).


Advantages: Simple, proven permission model (UGO/RWX), ubiquitous across all Unix-like systems. Disadvantages: Too simplistic for complex enterprise needs (solved by ACLs/SELinux).


  • Using chmod 777 to fix permission issues — this gives everyone full access. Find the correct user/group instead.
  • Running applications as root — always create a dedicated service account with minimal permissions.
  • Thinking standard Linux permissions (DAC) stop root — root bypasses all DAC. Only MAC (SELinux/AppArmor) can constrain root.

SymptomCauseDiagnosisFix
Permission denied reading fileWrong owner or group, or missing read bitls -l filename; idchown or chmod to grant minimal necessary access
Service fails to bind to port 80Non-root user trying to bind privileged port (<1024)journalctl -u serviceUse capabilities (setcap cap_net_bind_service=+ep) or run behind a reverse proxy

Objective: Explore basic security boundaries.

Terminal window
# 1. View file permissions and ownership
ls -l /etc/shadow
# Should be owned by root:shadow, permissions -rw-r-----
# 2. Check your effective user and group IDs
id
# 3. Find files with SUID bit set
find /usr/bin -perm -4000 -ls | head -10
# 4. Try reading a file without permissions
cat /etc/shadow # Permission denied

  1. Create a new group, add two users to it, and create a shared directory where both can read/write, but others cannot.
  2. Find all world-writable files in /tmp using the find command.
  3. Explain why the passwd command requires the SUID bit to be set.

  • DAC (Discretionary Access Control) relies on the owner setting permissions (rwx).
  • UID 0 (root) bypasses all DAC checks.
  • SUID allows a program to run with the privileges of its owner (usually root), which is risky if the program has vulnerabilities.

  • man chown, man chmod
  • Linux and UNIX Security by Simson Garfinkel

  • Chapter 16 — Mandatory Access Control (SELinux)

Last Updated: July 2026