SELinux and AppArmor
Mandatory access control systems that confine what a process can do beyond standard Unix permissions - SELinux on RHEL/Fedora, AppArmor on Debian/Ubuntu - and how to tell a genuine permission problem from a policy denial.
File Permissions and Ownership covers discretionary access control (DAC): the file's owner decides who can read, write, or execute it. SELinux and AppArmor add a second, independent layer - mandatory access control (MAC) - where a system-wide policy, not any individual file owner, decides what a process is allowed to do, even if the file's own permissions would otherwise allow it.
Why a second layer
Standard permissions only limit users. A process running as an
unprivileged user but with a bug (a web server with a file-upload
vulnerability, say) can still do anything that user's permissions allow -
read other files that user owns, make outbound network connections,
whatever. MAC narrows this further: a policy can confine nginx
specifically to reading its own web root and writing only its own logs,
regardless of what the www-data user could otherwise touch - so a
compromised process is boxed in even beyond its own account's normal
permissions.
SELinux (RHEL, Fedora, CentOS)
Every process and file gets a security context (a label): user, role, type, and optionally a sensitivity level. Policy rules are written in terms of these labels, not filenames or usernames directly.
getenforce # Enforcing, Permissive, or Disabled
ls -Z /var/www/html/index.html
Enforcing
unconfined_u:object_r:httpd_sys_content_t:s0 index.html
Moving a file into a web root with mv (which preserves the source
directory's context) instead of cp is a classic SELinux gotcha: the
file keeps its old label, doesn't match what httpd_sys_content_t
policy expects, and gets silently denied even though the DAC
permissions look completely fine.
sudo restorecon -Rv /var/www/html/ # reset labels to policy defaults
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
sudo setenforce 0 # Permissive: log denials, but don't block them (diagnosis only)
Diagnosing a denial:
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log # human-readable explanation, if setroubleshoot is installed
audit2allow can generate a custom policy module from observed denials
when a legitimate use case genuinely isn't covered by the stock policy -
review what it generates before applying it, rather than granting it
blindly.
AppArmor (Debian, Ubuntu, openSUSE)
AppArmor takes a simpler, path-based approach instead of labeling every file: a profile lists the exact file paths and capabilities one program is allowed to use.
sudo aa-status
apparmor module is loaded.
15 profiles are loaded.
13 profiles are in enforce mode.
2 profiles are in complain mode.
Profiles live in /etc/apparmor.d/, one file per confined program:
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx # complain mode: log only, don't block
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx # enforce mode: actually block violations
journalctl -k | grep -i apparmor # denials show up in the kernel log
aa-genprof walks through generating a new profile interactively: run
it, exercise the program's normal behavior, and it proposes rules for
each file/capability access it observes, which you then approve or deny.
Diagnosing "permission denied" that isn't
If standard permissions and ownership look correct
(ls -l matches what you'd expect) but access still fails, MAC is the
next thing to check - the symptoms look identical to a DAC issue from
the application's point of view, but the fix is completely different
(fixing a label or profile, not chmod/chown).
Don't just disable it
Turning SELinux or AppArmor off entirely to make an error go away is a common shortcut - and removes a real layer of defense along with it. In almost every case, the correct fix is adjusting the policy or label to match a legitimate need (as above), not disabling enforcement system-wide. See Hardening Basics for where this fits into a broader security baseline.