SELinux and AppArmor: Mandatory Access Control

Standard Linux permissions (rwx for owner/group/other) are discretionary — the file owner decides who else can read or write. That’s fine for most things. But when you run a process that handles untrusted input — a web server, a parser, a daemon facing the internet — you want a SECOND layer of defense that the process itself can’t change. That’s Mandatory Access Control (MAC). Linux has two: SELinux (Red Hat family) and AppArmor (Debian/Ubuntu/SUSE).

The mental model

Both work the same way: a policy says “process X can read file Y, but cannot write file Z, cannot bind to port 8000, cannot exec /bin/sh.” Even if the process is exploited and tries to do those things, the kernel blocks it.

This is why a vulnerable nginx with SELinux enforcing will fail to read /etc/shadow even if exploited — it doesn’t have the SELinux label that would allow it.

SELinux (RHEL, Fedora, Rocky, Alma)

Modes

# Show current mode
getenforce
# Enforcing | Permissive | Disabled

# Status with detail
sestatus

# Switch modes
sudo setenforce 1            # enforcing (real)
sudo setenforce 0            # permissive (logs only, doesn't block)

# Permanent change: edit /etc/selinux/config
SELINUX=enforcing

SELinux contexts

Every file and process has a security context. View with -Z:

ls -Z                            # show file contexts
ps -eZ | head                    # show process contexts
id -Z                            # your context

# Sample
# system_u:object_r:httpd_sys_content_t:s0  /var/www/html/index.html
# system_u:system_r:httpd_t:s0              nginx process

The third field (the “type”) is what matters most. httpd_sys_content_t means “content nginx can read.”

Common operations

# Set a file's context
sudo chcon -t httpd_sys_content_t /var/www/myapp/index.html

# Restore default context based on policy
sudo restorecon -Rv /var/www/

# Set file context permanently in policy
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www

# Allow nginx to bind to a non-standard port
sudo semanage port -a -t http_port_t -p tcp 8080

# Allow a boolean (common toggles)
sudo setsebool -P httpd_can_network_connect 1
sudo getsebool -a | grep httpd

Diagnose denials

# Watch for denials in real time
sudo journalctl -f | grep -i avc

# Show recent denials with explanations
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log

# Generate a custom policy from a denial
sudo audit2allow -a -M mypolicy
sudo semodule -i mypolicy.pp

AppArmor (Debian, Ubuntu, SUSE)

Same idea as SELinux, simpler configuration — profiles are file paths, not labels.

Status

sudo aa-status                   # what's loaded and what mode

# Output sections:
# - profiles in enforce mode
# - profiles in complain mode
# - processes that have a profile

Profile modes

  • enforce — block actions not in the profile.
  • complain — log violations but allow them (good for testing).
  • disabled — profile not active.
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
sudo aa-disable /etc/apparmor.d/usr.sbin.nginx
sudo systemctl reload apparmor

Generate a profile from observed behavior

sudo aa-genprof /usr/local/bin/myapp
# In another terminal: run the app, exercise its features
# Then come back and answer aa-genprof's questions

Diagnose denials

sudo journalctl -k | grep -i apparmor
sudo dmesg | grep -i apparmor

Should you use them?

  • Servers facing the internet: yes. The cost (occasional troubleshooting) is worth the security gain.
  • Containers / cloud workloads: usually already enabled by your image (RHEL UBI ships SELinux on, Ubuntu ships AppArmor on).
  • Personal desktop: AppArmor’s defaults rarely cause issues; SELinux can be more aggressive. Most users leave whatever the distro shipped.

Don’t disable on the first denial

The instinct when something doesn’t work is to disable SELinux/AppArmor. Resist. Instead:

  1. Switch the specific profile to permissive/complain.
  2. Reproduce the issue, collect the denial.
  3. Add an exception in policy.
  4. Re-enforce.

Disabling completely turns off a real security layer. Almost every “I disabled SELinux” forum thread ends with a real issue.

Other hardening to consider

  • Updatesunattended-upgrades on Debian/Ubuntu, dnf-automatic on Fedora.
  • Auditd — kernel-level audit logging.
  • OSSEC / Wazuh — host intrusion detection.
  • CIS Benchmarks — comprehensive hardening checklists.

Common mistakes

  • Disabling instead of debugging the first denial.
  • Using chcon when you should use semanage fcontext + restorecon — chcon doesn’t survive a relabel.
  • Trying to copy an SELinux policy from one machine to another with different versions.

What to learn next

The last section of this roadmap: containers — the kernel features (namespaces, cgroups) that make Docker possible, and Docker itself.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *