SELinux vs. AppArmor: Mandatory Access Control on Linux
Compare SELinux label policy and AppArmor profiles through enforcement, logging, safe policy changes, containers and a denial-first audit workflow.
Discretionary permissions answer whether the current credentials may access an object. Linux Security Module policy can impose an additional decision even when DAC permits it. SELinux and AppArmor are mature policy systems with different object-identification and policy languages, but neither replaces ownership, capabilities, seccomp, namespaces, patching, or service hardening.
Administrators with sufficient authority can change policy or enforcement, so “even root can never override MAC” is too absolute. The security value is that a compromised root-running service does not automatically possess the administrative path needed to rewrite the active policy.
Begin with the distribution’s supported model
Fedora/RHEL-family systems normally integrate SELinux policy, labels, package scripts, containers, and support expectations. Ubuntu and SUSE families commonly integrate AppArmor. Defaults evolve, and Debian can support AppArmor without every installation enabling it.
Do not replace the distribution’s primary MAC system during an application incident. Check current state:
# SELinux hosts
getenforce
sestatus
# AppArmor hosts
aa-enabled
aa-status
Record kernel command line, LSM list, policy package versions, enforcing modes, and affected process label/profile before changing anything.
SELinux decisions use security contexts
SELinux associates subjects and objects with contexts commonly shown as user, role, type, and level. Type enforcement is central on general-purpose distributions: a web-server process domain such as httpd_t receives rules for object types such as httpd_sys_content_t.
ps -eZ | grep '[h]ttpd'
ls -Zd /var/www/html /srv/web
id -Z
The precise types and policy modules come from the installed distribution policy. Never copy a type name from another release without checking semanage/policy documentation.
SELinux labels survive path changes differently
File contexts are usually stored in security.selinux extended attributes. Moving a file within the same filesystem preserves its current label; creating/copying/restoring can select or preserve labels differently depending on tool and policy. The persistent file-context database maps path regexes to expected labels.
Preview correction first:
restorecon -nRv /srv/web
If evidence shows the default mapping is correct, apply without -n. For a nonstandard tree, define a persistent mapping with semanage fcontext, then restorecon; do not use chcon as the only long-term fix because relabeling can replace that ad hoc context.
SELinux denials need full AVC context
An AVC record contains source/target contexts, object class, permission, process, path where available, and enforcement result. Correlate by time and service:
ausearch -m AVC,USER_AVC -ts recent
journalctl -t setroubleshoot --since '30 minutes ago'
audit2why can explain known constraints. audit2allow mechanically turns observed denials into candidate allows; it cannot decide whether the behavior is legitimate, whether a boolean/type transition already exists, or whether input was malicious. Never pipe production denials directly into an installed policy module.
SELinux offers several safer fixes
Diagnose in this order: wrong DAC/ownership, wrong default label, unsupported path, documented boolean, service configuration, then minimal custom policy. List boolean meaning before changing one:
getsebool -a
semanage boolean -l
Persistent setsebool -P changes should be reviewed because a boolean can widen access for an entire domain, not just one process. Preserve before/after policy evidence and a rollback command.
SELinux permissive modes are scoped diagnostic tools
Global permissive logs denials without enforcing most SELinux decisions; it expands attack surface system-wide. Prefer a disposable reproduction or a per-domain permissive rule where supported, for the shortest interval, with monitoring.
Never disable SELinux in the bootloader to solve one denial. Re-enabling after disabled operation can require a correct relabel, and the unconfined interval loses enforcement evidence.
AppArmor attaches named profiles to programs
AppArmor profiles describe allowed file paths, capabilities, network operations, signals, mounts, DBus and other classes depending on parser/kernel features. Profile attachment and pathname mediation make policy readable around the application’s deployed layout, while variables, abstractions, aliases, hats, and child profiles support reuse and transitions.
aa-status
cat /proc/"$pid"/attr/current
journalctl -k --since '30 minutes ago' | grep -i apparmor
A process can be unconfined, complain-mode, enforce-mode, or under a named profile. A profile file existing on disk does not prove the running task is confined by it.
Path-based does not mean “simple glob firewall”
AppArmor resolves access through kernel mediation and its pathname model. Bind mounts, mount namespaces, deleted files, hard links, aliases, chroot/container layouts, and executable transitions require explicit analysis. Do not assume a symlink automatically bypasses policy, or that one visible host path names an object identically inside every mount namespace.
Use the parser and official policy-language reference for the installed AppArmor version. Unsupported rule classes or syntax can fail profile loading or leave intended mediation absent.
AppArmor denial review is interactive policy engineering
aa-logprof groups logged events and proposes rules; aa-genprof can assist with a new profile. Training sees only exercised behavior, so it easily misses failure paths and can learn attacker-driven accesses.
Use a clean test workload, known inputs, and complain mode only for the target profile. Review every proposal for path breadth, write/create/delete distinctions, execute transition mode, capabilities, and owner qualifiers. Reload, return to enforce, and run negative tests.
Execution transitions define confinement continuity
Both systems need deliberate behavior when a confined process executes another binary. SELinux domain transitions depend on policy types/rules and entrypoint labeling. AppArmor execute permissions can inherit, switch to a named profile, create a child profile relationship, or run unconfined depending on rule mode.
An allow to execute is not enough: audit the child label/profile, capabilities, environment, open descriptors, and writable executable/interpreter paths. Shells, language runtimes, plugins, and helper programs often expand the effective attack surface.
Containers add label/profile plumbing
Container runtimes can assign SELinux process/file MCS labels or AppArmor profiles. A bind mount with the wrong SELinux labeling option can fail or dangerously relabel shared host content; use runtime/distribution documentation and understand shared/private label semantics before applying :z/:Z-style options.
For AppArmor, verify the OCI/runtime profile actually attached to the container process. “Default profile” names and generated rules vary. Privileged modes, host mounts, capabilities, devices, and unconfined settings can defeat much of the intended containment.
MAC denial is only one possible EACCES
DAC/ACLs, read-only/nosuid/noexec mounts, capabilities, seccomp, Landlock, immutable attributes, namespace ownership, systemd sandboxing, and application logic can deny the same operation. Confirm an AVC/AppArmor denial with matching timestamp and request before editing policy.
Conversely, absence of logs may reflect audit rate limiting, disabled auditing, dontaudit rules, wrong observation namespace, or a denial occurring in another layer—not proof MAC permitted it.
Policy updates are code changes
Version custom SELinux modules or AppArmor profiles with source, rationale, test cases, target distribution/policy version, owner, review, and rollback. Compile/parse in CI or a matching VM. Avoid replacing vendor policy files; use supported local override/module locations so package upgrades remain reviewable.
After an OS/policy update, retest both allowed and forbidden operations. New kernel hooks, application paths, helpers, and default policy can change behavior without changing your local file.
Build a denial-first acceptance matrix
For the service, test required reads/writes/binds/execs plus forbidden secret reads, arbitrary writes, unexpected network families, privileged capabilities, and execution of writable code. Capture SELinux context or AppArmor profile for parent/children and correlate audit records.
The policy succeeds when normal behavior works under enforcement, representative attacks remain denied, no broad permissive/unconfined state persists, changes survive reboot/package update, and each allow rule has an owner and evidence-backed reason.
Related:
- Diagnosing a Linux Service That Fails Silently from a Permission Denial
- Fixing SELinux Denials Blocking a Legitimate Service
Sources: