Mandatory Access Control

Overview

Confining even root: SELinux and AppArmor.

Standard Linux file permissions are discretionary: the owner of a file decides who may access it, and root can override any of it. That flexibility is also the weakness. If a process runs as root, or if a user can be tricked into loosening permissions, discretionary access control (DAC) offers no further defence. Mandatory access control (MAC) adds a second, non-negotiable layer: a system-wide policy, set by the administrator and enforced by the kernel, that constrains what every process may do regardless of UID and regardless of file ownership. Under MAC, even root is confined. A compromised root-running web server can be stopped from reading /etc/shadow or writing outside its document root, because the policy simply does not permit it.

Why discretionary permissions are not enough

DAC answers “does this user own or have rights to this object.” It cannot answer “should the Apache process, specifically, ever be reading users’ home directories.” The gap between those two questions is where MAC lives.

PropertyDAC (file permissions)MAC (SELinux / AppArmor)
Who sets policyThe resource ownerThe system administrator
Does root bypass itYes, root overrides DACNo, root is subject to policy
GranularityPer file, per user/groupPer program, per action, per object type
Typical failure modeMisconfigured permissionsDenied action that policy did not anticipate
Enforcement pointVFS permission checkLinux Security Module (LSM) hook

MAC confines the program, not just the user. That containment is exactly what limits blast radius: a bug in one confined service cannot be leveraged into reading or corrupting things that service has no business touching, even if the exploit gains root.

Two implementations, one framework

Both major Linux MAC systems plug into the kernel through the Linux Security Module (LSM) framework, but they take different approaches, which the sibling pages cover in depth.

  • SELinux (SELinux page) is label-based. Every process and every object (files, ports, sockets) carries a security context, and policy is written in terms of allowing a subject type to perform an action on an object type. It is comprehensive and fine-grained but has a steep learning curve. It is the default on Red Hat Enterprise Linux, Fedora, and CentOS Stream.
  • AppArmor (AppArmor page) is path-based. Policy is a set of per-program profiles listing the files, capabilities, and network operations each executable may use, keyed by filesystem path. It is easier to read and write, at the cost of some precision (paths can be aliased). It is the default on Ubuntu and SUSE.

On the Ubuntu 24.04 lab VM, AppArmor is the system in force; SELinux is not installed or enabled by default. That shapes how you will spend your hands-on time: AppArmor is the one you will actually operate, while SELinux is essential to understand for Red Hat family systems and many hardened environments.

Hands-on: which MAC is active here

A quick check tells you which framework, if any, is enforcing on a given host.

# Ubuntu default: AppArmor is enabled and enforcing
sudo aa-status --summary
# SELinux status (expected to be absent on Ubuntu)
sestatus 2>/dev/null || echo "SELinux tools not installed"

Expected output:

apparmor module is loaded.
32 profiles are loaded.
28 profiles are in enforce mode.
4 profiles are in complain mode.
SELinux tools not installed

That is the canonical Ubuntu picture: AppArmor loaded and enforcing most of its profiles, SELinux not present. On a Red Hat box the second command would instead report SELinux status: enabled, Current mode: enforcing.

The recurring lesson: fix policy, do not disable it

The single biggest operational mistake with MAC is turning it off the moment it blocks something. A denial usually means either the policy is slightly too tight (fix it by refining the profile or setting a boolean) or that the action genuinely should not happen (the denial just caught a misconfiguration or an attack). Blanket-disabling MAC (setenforce 0, or unloading an AppArmor profile) removes a whole defensive layer to work around a five-minute fix. Both sibling pages emphasise reading the denial logs and adjusting policy properly, and the Kubernetes Security foundation carries the same theme where pod AppArmor and SELinux options appear in the workload spec.

Practical Guidance

  1. Keep MAC enabled and enforcing. Confining root is the entire point; a disabled MAC layer protects nothing.
  2. Know which system your distro uses: SELinux on the Red Hat family, AppArmor on Ubuntu and SUSE. The concepts transfer but the tooling does not.
  3. When something breaks, read the denial (audit log for SELinux, dmesg/journal for AppArmor) before changing anything. The log names the exact subject, action, and object.
  4. Refine policy rather than disabling it: adjust an AppArmor rule, set an SELinux boolean, or relabel a file. Reach for permissive/complain mode only as a temporary diagnostic.
  5. Treat MAC as one layer among many. It complements capabilities, seccomp, and namespaces; it does not replace them, and none of them replaces it.