Skip to content

Mandatory Access Control: AppArmor & SELinux Enterprise Practice

Created: 2026-11-10 Updated: 2026-11-10

Management Summary: Traditional Linux permissions governed by Discretionary Access Control (DAC) fail the moment a service operating with superuser authority is compromised: by architectural design, root can inspect, modify, or delete every file across the operating system. Mandatory Access Control (MAC) introduces an inviolable second enforcement boundary directly within the Linux kernel via the Linux Security Module (LSM) interface. Even if an adversary achieves full root authority (UID 0) inside a daemon or web application, the security module confines execution strictly within an isolated sandbox. This enterprise engineering guide deconstructs both leading implementations: AppArmor (path-based, default on Debian and Ubuntu) and SELinux (label-based, default on RHEL and Fedora): From LSM kernel architecture and profile lifecycle management (aa-genprof, aa-logprof) to SELinux Type Enforcement, booleans, and port labels, culminating in systematic AVC denial forensics using ausearch and audit2why.

  • DAC vs. MAC Architecture


    Understand the fundamental distinction between user-controlled discretionary permissions (DAC) and kernel-enforced LSM security policies (MAC).

  • AppArmor Profile Lifecycle


    From complain monitoring mode to strict enforce lockdown: define, test, and atomically activate daemon security profiles.

  • AppArmor CLI Toolchain


    Efficiently generate and maintain profiles using aa-genprof, aa-logprof, aa-complain, aa-enforce, and the aa-status dashboard.

  • SELinux Type Enforcement


    Labels, extended attributes (security.selinux), security contexts (user:role:type:level), and domain transitions in the Linux kernel.

  • SELinux Booleans & Ports


    Dynamically toggle daemon capabilities using setsebool -P and assign custom network ports via semanage port.

  • AVC Denials & Audit Forensics


    Systematically isolate access denials with ausearch -m avc, translate errors via audit2why, and compile hardened policies with audit2allow.


1. DAC vs. MAC: Why Discretionary Access Control Is Insufficient

Under traditional Discretionary Access Control (DAC), the file owner governs read, write, and execute permissions (chmod, chown). When a daemon (such as Nginx, Apache, or Bind) executes as root or an adversary weaponizes a local privilege escalation flaw, the attacker instantly gains unconstrained authority over the entire filesystem.

Mandatory Access Control (MAC) hooks into the Linux Security Module (LSM) subsystem, acting as a mandatory second checkpoint built directly into the kernel execution path. Every system call must first pass DAC evaluation and subsequently satisfy the active MAC policy:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    Process["Process<br/>(e.g., Web Server UID 0)"] ==> SysCall["System Call<br/>(openat, execve, socket)"]
    SysCall --> DAC_Check{"1. DAC Check<br/>(UID / GID / Mode)"}
    DAC_Check -->|"Denied"| Access_Denied_DAC["Access Blocked<br/>(EACCES)"]
    DAC_Check -->|"Allowed"| LSM_Check{"2. LSM / MAC Check<br/>(AppArmor / SELinux)"}
    LSM_Check -->|"Profile Authorizes"| Access_Granted["Kernel Grants Resource"]
    LSM_Check -->|"No Rule in Profile"| Access_Denied_MAC["Access Blocked & Audited<br/>(EPERM / AVC Denial)"]

The Decisive Security Benefit

Even if an attacker achieves full root code execution within the web server process, the active MAC profile forbids reading /etc/shadow, spawning interactive shells (/bin/bash), or binding unauthorized sockets. The compromise is contained within the sandbox.


2. AppArmor Deep Architecture & Profile Syntax (Debian/Ubuntu)

AppArmor binds security policies directly to the absolute binary path of an executable. This design makes profiles readable, declarative, and easy to maintain.

Profiles reside as plaintext files in /etc/apparmor.d/ (such as /etc/apparmor.d/usr.sbin.nginx).

Profile Modes: complain vs. enforce

  1. Complain Mode (complain): Policy violations are logged to the audit stream, but requests are permitted to execute (ideal for development, staging, and behavioral learning).
  2. Enforce Mode (enforce): Any access not explicitly granted is immediately rejected by the kernel with EPERM and recorded in the audit log.

AppArmor Permission Flags

Inside a profile, explicit permission flags govern path access:

Flag Meaning Security Enforcement
r Read Permits file read access.
w Write Permits creating, appending, and truncating files.
m Memory Map Executable Permits loading binary code into memory (shared libraries).
k Lock Permits acquiring advisory file locks (flock).
ix Inherit Execute Executes target binary within the current profile context.
px Profile Execute Transitions upon execution into a discrete, dedicated profile.
cx Child Execute Transitions into an embedded child sub-profile.

Complete Production Profile for an API Daemon

Here is a hardened AppArmor profile for a custom daemon (/usr/local/bin/api-daemon):

# /etc/apparmor.d/usr.local.bin.api-daemon
#include <tunables/global>

/usr/local/bin/api-daemon {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  # Restrict networking strictly to IPv4/IPv6 TCP sockets
  network inet stream,
  network inet6 stream,

  # Allow binding privileged ports (80/443)
  capability net_bind_service,
  capability setuid,
  capability setgid,

  # Self execution and shared library mappings
  /usr/local/bin/api-daemon mr,
  /lib{,32,64}/** mr,
  /usr/lib{,32,64}/** mr,

  # Configuration read-only access
  /etc/api-daemon/** r,
  /etc/ssl/certs/** r,

  # Application state: read, write, and lock
  /var/lib/api-daemon/** rwk,
  /var/log/api-daemon/** w,

  # Transient scratch files
  /tmp/api-daemon* rwk,

  # Explicit denials (Deny rules override Allow rules)
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /root/** rwx,
}

3. AppArmor Profile Lifecycle in Production

Managing profiles follows a structured pipeline using the AppArmor CLI suite:

# Install AppArmor administrative utilities
sudo apt-get install apparmor-utils apparmor-profiles

1. Inspecting Operational Status with aa-status

# Inspect all loaded profiles and execution modes
sudo aa-status

2. Interactive Profiling with aa-genprof

aa-genprof creates a baseline skeleton, sets complain mode, and listens for operational events:

# Initialize profiling session
sudo aa-genprof /usr/local/bin/api-daemon

In a second terminal, exercise all typical workflows of the service (workload tests, API calls, log rotation, service reloads).

3. Parsing Audit Logs with aa-logprof

Return to aa-genprof (or invoke aa-logprof independently). The tool parses access records from /var/log/audit/audit.log (or /var/log/syslog) and prompts interactively:

  • (A)llow: Grants persistent permission.
  • (D)eny: Explicitly forbids access.
  • (G)lob: Grants permission via wildcard across the path (/*).

4. Enforcing the Profile (enforce)

# Switch profile to strict enforcing mode
sudo aa-enforce /etc/apparmor.d/usr.local.bin.api-daemon

# Reload profile live in running kernel
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.api-daemon

4. SELinux Architecture & Type Enforcement (RHEL/Fedora)

Unlike AppArmor, SELinux attaches security labels directly to filesystem inodes via extended attributes (xattr). Renaming or moving a file preserves its SELinux label!

The 4-Part Security Context

Every file, directory, and running process carries a structured label:

user:role:type:level
system_u:object_r:httpd_sys_content_t:s0
  1. User (system_u): SELinux user identity (independent of Linux UIDs).
  2. Role (object_r): Role used in Role-Based Access Control (RBAC). For files, this is universally object_r.
  3. Type (httpd_sys_content_t): The core mechanism of SELinux—Type Enforcement. Domains and file types must have an explicit allow rule in policy to interact.
  4. Level (s0): Multi-Level / Multi-Category Security (MLS/MCS) isolation.

Inspecting Security Contexts

# View SELinux contexts on files (-Z flag)
ls -laZ /var/www/html/

# View SELinux contexts of executing processes
ps -eZ | grep httpd

5. SELinux Booleans, Port Labels & File Contexts

SELinux provides rich baseline policies for hundreds of system daemons. Rather than writing custom modules, administrators configure behavior using booleans and port labels.

1. Controlling Booleans with getsebool and setsebool

A boolean is a kernel toggle enabling or disabling specific privileges:

# Check if web server is permitted to initiate outbound network sockets
getsebool httpd_can_network_connect

# Persistently enable outbound connections across reboots (-P flag mandatory!)
sudo setsebool -P httpd_can_network_connect 1

# Permit web daemon to dispatch emails
sudo setsebool -P httpd_can_sendmail 1

2. Assigning Custom Network Ports

If a daemon listens on a non-standard port (such as TCP 8080 instead of 80), SELinux terminates the bind() call. Assign the port to the appropriate label:

# List all ports authorized for HTTP traffic
sudo semanage port -l | grep http_port_t

# Add port 8080/TCP to the authorized HTTP port set
sudo semanage port -a -t http_port_t -p tcp 8080

3. Repairing File Contexts: restorecon vs. chcon

Production Ban on chcon

Never use chcon in production! chcon applies transient labels directly to inodes. During system relabeling (touch /.autorelabel) or maintenance, all chcon labels are permanently overwritten.

Always define persistent rules via semanage fcontext and enforce them using restorecon:

# Define persistent labeling rule for a custom webroot
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webdata(/.*)?"

# Apply the persistent label recursively across the filesystem
sudo restorecon -Rv /srv/webdata

6. AVC Denial Forensics & Troubleshooting Runbook

When SELinux blocks an operation, the kernel writes an AVC (Access Vector Cache) Denial to /var/log/audit/audit.log.

1. Tracking Denials with ausearch

# Query all AVC denials from the past 10 minutes
sudo ausearch -m avc -ts recent

# Explain denial reasons with actionable guidance via audit2why
sudo ausearch -m avc -ts recent | audit2why

audit2why translates cryptic kernel hexadecimal logs into human-readable recommendations (e.g., “Missing boolean httpd_can_network_connect”).

2. Generating Custom Policy Modules with audit2allow

When a proprietary daemon requires legitimate permissions not covered by standard policies, generate a tailored Type Enforcement module:

# Generate readable TE policy source and compiled PP module from recent denials
sudo ausearch -m avc -ts recent | audit2allow -M custom_app_policy

# Inspect the generated source carefully before activating!
cat custom_app_policy.te

# Install compiled policy module into the running kernel
sudo semodule -i custom_app_policy.pp

Caution Against Indiscriminate audit2allow

Never blindly execute audit2allow -a -M ...! If an attacker just attempted to read /etc/shadow, running audit2allow generates a rule that explicitly authorizes reading shadow hashes in the future!

Enterprise Troubleshooting Table

Issue / Symptom Root Cause Remediation Workflow
AppArmor blocks file access Path missing from profile Run sudo aa-logprof or add explicit r/w path.
AppArmor daemon fails to start Profile syntax error Execute sudo apparmor_parser -r /etc/apparmor.d/<profile> to see line error.
SELinux blocks web directory Incorrect file context Execute sudo restorecon -Rv /path.
SELinux blocks custom port Port not in authorized type set Run sudo semanage port -a -t <type> -p tcp <port>.
Emergency Triage Unidentified production outage Execute sudo setenforce 0 (temporarily switches to Permissive mode for testing).

Mandatory Access Control provides the most resilient in-kernel isolation barrier available in Linux: processes adhere strictly to least-privilege boundaries, ensuring that a compromised daemon cannot achieve host takeover.