Chapter 8: Mandatory Access Control: SELinux and AppArmor

Updated

October 31, 2026

Chapter 8: Mandatory Access Control: SELinux and AppArmor

Discretionary Access Control (DAC)—the standard Unix user, group, and permission bit model—suffers from a fatal structural flaw: the owner of a resource completely dictates who can access it, and any process running as root can bypass all DAC checks entirely. If an unprivileged daemon running as root (or compromised via a local exploit) is tricked into reading /etc/shadow or writing to /etc/ld.so.preload, the operating system permits the action without hesitation.

Mandatory Access Control (MAC) solves this vulnerability. Under MAC, the operating system enforces centralized, policy-defined constraints that neither users nor root processes can override. This chapter examines the Linux Security Module (LSM) framework and explores the two premier MAC implementations: SELinux and AppArmor.

Mandatory Access Control Fundamentals

In DAC, access decisions depend on the identity of the user and the file’s permission bits. In MAC, every operation is evaluated against an immutable system security policy:

┌────────────────────────────────────────────────────────────────────────┐
│                        DAC VS MAC DECISION ENGINE                      │
│                                                                        │
│   Discretionary Access Control (DAC)                                   │
│   Process (UID 0 / root) ──► Wants /etc/shadow ──► Allowed immediately │
│                                                                        │
│   Mandatory Access Control (MAC)                                       │
│   Process (httpd_t)      ──► Wants /etc/shadow ──► DENIED BY POLICY   │
│   (Even if process runs with EUID 0, policy forbids httpd_t from       │
│    reading shadow_t objects)                                           │
└────────────────────────────────────────────────────────────────────────┘
  1. Subjects: Active entities that request operations (processes executing in specific domains or profiles).
  2. Objects: Passive system resources requested by subjects (files, sockets, IPC queues, block devices, memory regions).
  3. The Principle of Strict Containment: A compromised web server process should only be capable of reading web files, writing to its designated log directory, and binding to its assigned TCP port. Even if an attacker executes a shell inside that process, MAC rules prevent the shell from reading user home directories, inspecting other processes, or tampering with system configurations.

SELinux Architecture and Policy Models

Developed initially by the United States National Security Agency (NSA) and Red Hat, Security-Enhanced Linux (SELinux) implements Flask architecture via the Linux Security Module (LSM) hooks:

┌────────────────────────────────────────────────────────────────────────┐
│                        SELINUX ARCHITECTURE                            │
│                                                                        │
│   Userspace Process (Subject)                                          │
│          │                                                             │
│          ▼ Syscall (e.g., open("/var/www/index.html"))                 │
│   Linux Kernel VFS / DAC Check (Passes)                                │
│          │                                                             │
│          ▼                                                             │
│   LSM Hook: selinux_file_open()                                        │
│          │                                                             │
│          ▼ Query Access Vector Cache (AVC)                             │
│   ┌──────────────────────────────────────────────────────────────┐     │
│   │ AVC (In-Memory Cache): Cached decision?                      │     │
│   │   ├─► YES: Allow or Deny immediately                         │     │
│   │   └─► NO:  Consult Security Policy in Security Server        │     │
│   └──────────────────────────────┬───────────────────────────────┘     │
│                                  ▼                                     │
│   Security Server: Evaluates Rule (httpd_t -> httpd_sys_content_t:read)│
│                                  │                                     │
│   Result: Allow ──► Update AVC Cache ──► Complete Syscall              │
│           Deny  ──► Generate Audit Log ──► Return -EACCES              │
└────────────────────────────────────────────────────────────────────────┘

The SELinux Context Format

Every process and file in SELinux carries a security context composed of four fields:

\[\text{user} : \text{role} : \text{type} : \text{level}\]

Example file context: system_u:object_r:httpd_sys_content_t:s0

  • User (system_u): The SELinux user identity.
  • Role (object_r): The role (used in Role-Based Access Control).
  • Type (httpd_sys_content_t): The core attribute for Type Enforcement (TE).
  • Level (s0): Multi-Level Security / Multi-Category Security (MLS/MCS) sensitivity rating.

SELinux Configuration and Administration

SELinux operates in one of three modes: 1. Enforcing: Security policy is strictly enforced; unauthorized actions are blocked and logged. 2. Permissive: Unauthorized actions are allowed, but denials are logged to the audit system. Essential for testing and troubleshooting. 3. Disabled: SELinux hooks are completely deactivated in the kernel.

# Query current SELinux status
sestatus

# Temporarily switch between Permissive and Enforcing modes
sudo setenforce 0    # Permissive
sudo setenforce 1    # Enforcing

# Inspect security contexts on files and processes
ls -Z /var/www/html/
ps -eZ | grep nginx

Restoring File Contexts with restorecon

When files are copied or moved, their SELinux types may become mismatched, leading to spurious permission errors. Use restorecon to reset file labels to match the system policy specifications:

# Recursively restore standard contexts according to policy database
sudo restorecon -Rv /var/www/html/

SELinux Policy Writing and Customization

When an application requires legitimate access blocked by default policies:

  1. Analyze Denials with ausearch and audit2why:

    # Search audit logs for AVC denials
    sudo ausearch -m AVC -ts recent
    
    # Translate cryptic denial messages into human-readable explanations
    sudo ausearch -m AVC -ts recent | audit2why
  2. Generate and Install Custom Policy Modules with audit2allow:

    # Generate a type enforcement (.te) draft and compiled module (.pp)
    sudo ausearch -m AVC -c "mydaemon" | audit2allow -M mydaemon_policy
    
    # Inspect generated rules before loading!
    cat mydaemon_policy.te
    
    # Install the compiled policy module
    sudo semodule -i mydaemon_policy.pp

AppArmor Architecture and Profiles

AppArmor is the default MAC system in Ubuntu, Debian, and openSUSE. Unlike SELinux’s label-based approach, AppArmor is path-based: it binds security profiles directly to the absolute filesystem path of the executable program (/usr/sbin/nginx).

┌────────────────────────────────────────────────────────────────────────┐
│                        APPARMOR PROFILE STATES                         │
│                                                                        │
│   Profile Mode      Behavior                                           │
│   ────────────      ─────────────────────────────────────────────────  │
│   Enforce           Violations are blocked immediately and logged.     │
│   Complain          Violations are permitted, but logged as warnings.  │
│   Disabled          Profile is unloaded from the kernel.               │
└────────────────────────────────────────────────────────────────────────┘

Profiles are defined as human-readable plain text files located in /etc/apparmor.d/.

Anatomy of an AppArmor Profile

# /etc/apparmor.d/usr.sbin.myapp
#include <tunables/global>

/usr/sbin/myapp {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Network permissions
  network inet stream,
  network inet6 stream,

  # Capability restrictions (Only allow binding low ports)
  capability net_bind_service,

  # Explicit filesystem access rules
  /usr/sbin/myapp mr,
  /etc/myapp/** r,
  /var/log/myapp/** rw,
  /var/lib/myapp/** rw,
  /run/myapp/myapp.pid rw,

  # Explicitly deny access to sensitive files
  deny /etc/shadow r,
  deny /etc/ssh/** r,
}
  • r: Read
  • w: Write
  • m: Memory map executable
  • x: Execute

AppArmor Configuration and Administration

AppArmor provides simple, interactive utilities for creating, testing, and managing profiles:

# Check loaded profiles and running confined processes
sudo aa-status

# Place a profile in complain mode for testing
sudo aa-complain /usr/sbin/myapp

# Place a profile in strict enforcement mode
sudo aa-enforce /usr/sbin/myapp

# Interactive profile profiling: parse audit logs and generate rule updates
sudo aa-logprof

# Wizard for authoring a new profile from scratch by monitoring execution
sudo aa-genprof /usr/sbin/mydaemon

Choosing Between SELinux and AppArmor

Organizations frequently debate which MAC framework to deploy:

Criterion SELinux AppArmor
Primary Distributions RHEL, Rocky, Alma, Fedora Ubuntu, Debian, SUSE
Model Type Label/Type-based (Inodes tagged with xattrs) Path-based (Bound to executable paths)
Granularity Extremely fine (System-wide, MLS/MCS, users/roles) Application-focused, straightforward
Learning Curve Steep (Complex policy language, debugging tooling) Gentle (Human-readable rules, simple syntax)
Container Integration First-class (container_t, automated MCS categories) Standard profile support via Docker/CRI
Path Rename Robustness Invariant across file moves or renames Requires updated path rules if binary moves

Recommendation: Align with your distribution’s upstream standard. Do not attempt to force SELinux onto Ubuntu or AppArmor onto RHEL in production. Both frameworks, when actively running in Enforcing mode, neutralize over 90% of zero-day local privilege escalation and service breakout attacks.