Cheat sheet — AppArmor & SELinux¶
Companion to Module 04 — Exploit Mitigation & Allowlisting · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
AppArmor — what's confined?¶
aa-status # profiles loaded, enforce vs complain, confined processes
aa-status --json | jq . # machine-readable — good for scripting an audit
aa-enabled # is AppArmor active in the kernel at all?
sudo aa-unconfined # network-facing procs running WITHOUT a profile — the gap
- The finding that matters: your critical app listed as unconfined. "Installed" ≠ "confining."
Loading and reloading profiles¶
ls /etc/apparmor.d/ # profiles live here (named after the binary path)
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.app # -r = reload (replace) a profile
sudo apparmor_parser -R /etc/apparmor.d/usr.bin.app # -R = remove/unload
sudo apparmor_parser -p /etc/apparmor.d/usr.bin.app # -p = preprocess only (syntax-check, no load)
sudo systemctl reload apparmor # reload all profiles from /etc/apparmor.d/
Complain vs enforce (the safe deployment path)¶
sudo aa-complain /etc/apparmor.d/usr.bin.app # log violations, DON'T block — the tuning mode
sudo aa-enforce /etc/apparmor.d/usr.bin.app # block AND log — production
sudo aa-disable /etc/apparmor.d/usr.bin.app # unload + symlink into disable/ so it stays off
- Pattern: complain → exercise the app → refine → enforce. Never enforce a fresh hand-written profile blind.
Authoring a profile from real behaviour¶
sudo aa-genprof /usr/bin/app # start a profile; run the app in another shell, then (S)can logs
sudo aa-logprof # replay new denials into an existing profile, interactively
# in genprof/logprof: (A)llow, (D)eny, (I)nherit, (C)hild, then (S)ave
aa-logprofonly sees what actually ran — exercise every code path or enforce breaks later.
Reading a denial¶
sudo dmesg | grep -i apparmor # kernel denials
sudo journalctl -k | grep -i apparmor # same via journald
# apparmor="DENIED" operation="open" profile="/usr/bin/app" name="/etc/shadow" requested_mask="r" denied_mask="r"
SELinux — mode and status¶
getenforce # Enforcing | Permissive | Disabled
sestatus # full status: current mode, policy, mode from config
sudo setenforce 0 # → Permissive (log only) — NOT persistent across reboot
sudo setenforce 1 # → Enforcing
sudoedit /etc/selinux/config # SELINUX=enforcing|permissive — the persistent setting
SELinux — contexts and labels¶
ls -Z /var/www/html # -Z shows security context on files
ps -eZ | grep httpd # context a process runs in
id -Z # your own context
sudo restorecon -Rv /var/www/html # relabel to policy default — fixes mislabeled files
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?" # add a persistent label rule
sudo restorecon -Rv /srv/web # then apply it
getsebool -a | grep httpd # list tunable booleans
sudo setsebool -P httpd_can_network_connect on # -P = persist the boolean
SELinux — troubleshooting a denial¶
sudo ausearch -m avc -ts recent # recent AVC (access vector cache) denials
sudo ausearch -m avc -ts recent | audit2allow -w # -w = plain-English "why was this denied"
sudo ausearch -m avc -ts recent | audit2allow -M myapp # generate a candidate policy module
# READ the .te it produced, THEN: sudo semodule -i myapp.pp
Gotchas worth remembering¶
- Complain mode logs, it does not block. It's for authoring a profile safely, not for
running one in production. A profile left in complain thinks it's protecting you and isn't —
check
aa-statusfor the mode, not just that the profile loaded. aa-logprof/aa-genprofonly capture accesses that actually happened. If you don't exercise every path (error handling, log rotation, that once-a-day cron job), enforce mode will deny a real access weeks later. Drive the app hard before you flip to enforce.setenforce 0is not persistent. It survives until reboot, then SELinux reads/etc/selinux/config. Edit the config file for a lasting change — and don't leave a host permissive "temporarily" and forget.- Never disable SELinux to "fix" a denial. A denial is usually SELinux working correctly.
Understand why with
audit2allow -w, then write a targeted policy module or set the right boolean — disabling it removes protection for the whole host to unblock one app. restoreconfixes the common case. Most SELinux "it broke" tickets are a mislabeled file (moved withmvinstead ofcp, so it kept the source context). Checkls -Zand relabel before you reach for policy edits.- AppArmor is path-based, SELinux is label-based. Rename or bind-mount a file and an AppArmor rule may no longer match; SELinux follows the label, not the path. Know which one you're running before you reason about a bypass.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).