Portal | Level: L2: Operations | Topics: SELinux & AppArmor, Linux Hardening, Linux Fundamentals | Domain: Security
SELinux & AppArmor - Primer¶
Why This Matters¶
Standard Linux permissions (DAC — Discretionary Access Control) answer one question: "Does this user/group have read/write/execute on this file?" That model breaks the moment a compromised process runs as root, because root bypasses DAC entirely. A hacked Apache running as root owns your entire filesystem.
Mandatory Access Control (MAC) adds a second, independent gate. Even if a process runs as root, the MAC policy can say "Apache only touches files labeled httpd_sys_content_t — everything else is denied." SELinux and AppArmor are the two MAC implementations that ship with mainstream distros. You will encounter one or the other on every production Linux box you touch.
If your instinct when something breaks is setenforce 0, this primer exists to give you a better instinct.
Name origin: SELinux stands for Security-Enhanced Linux. It was developed by the NSA (National Security Agency) and released as open source in 2000. The NSA contributed it to the Linux kernel in 2003 (kernel 2.6). AppArmor (originally "SubDomain") was created by Immunix Inc. and acquired by Novell in 2005 for SUSE Linux. The name is a portmanteau of "Application Armor."
DAC vs MAC — The Mental Model¶
┌──────────────────────────────────────────────┐
│ Access Request │
│ (process tries to read /var/www/index.html) │
└──────────────┬───────────────────────────────┘
│
┌───────▼────────┐
│ DAC Check │ ← traditional rwx / owner / group
│ (permission │
│ bits, ACLs) │
└───────┬────────┘
│ PASS
┌───────▼────────┐
│ MAC Check │ ← SELinux or AppArmor policy
│ (label/profile │
│ based) │
└───────┬────────┘
│ PASS
┌───────▼────────┐
│ Access Granted │
└────────────────┘
Both gates must pass. MAC never loosens what DAC already denied.
| Aspect | DAC | MAC |
|---|---|---|
| Who decides | File owner | System policy (admin) |
| Root bypasses? | Yes | No |
| Granularity | User/group/other | Process type + resource label |
| Default stance | Allow unless denied | Deny unless explicitly allowed |
| Compromise risk | Root = game over | Root process still confined |
SELinux — Deep Dive¶
The Three Modes¶
| Mode | Enforcing? | Logging? | Use Case |
|---|---|---|---|
| Enforcing | Yes | Yes | Production — the only real mode |
| Permissive | No | Yes | Debugging — logs but doesn't block |
| Disabled | No | No | Never in production |
Check current mode:
Switch temporarily (does not survive reboot):
Permanent change lives in /etc/selinux/config:
Changing from
disabledtoenforcingrequires a full filesystem relabel and reboot. Plan for 10-60 minutes depending on disk size.
Security Contexts — The Four Fields¶
Every file, process, port, and user carries a security context:
Example:
ls -Z /var/www/html/index.html
# -rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
ps -eZ | grep httpd
# system_u:system_r:httpd_t:s0 1234 ? 00:00:01 httpd
Breakdown:
| Field | Example | What It Does |
|---|---|---|
| User | system_u |
SELinux user (not Linux user) |
| Role | system_r |
Role-based access (RBAC layer) |
| Type | httpd_t |
The one that matters 95% of the time |
| Level | s0 |
MLS/MCS sensitivity (ignore unless MLS policy) |
Type Enforcement is where almost all policy decisions happen. The rule looks like:
Translation: "A process of type httpd_t may read/getattr/open files labeled httpd_sys_content_t."
Changing File Contexts¶
When you move files (not copy), they keep their old label. This is the #1 source of SELinux breakage.
# See current context
ls -Z /srv/myapp/
# Set context using semanage (persistent rule) + restorecon (apply)
semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"
restorecon -Rv /srv/myapp/
# Quick temporary fix (does not survive restorecon/relabel)
chcon -t httpd_sys_content_t /srv/myapp/index.html
Always prefer semanage fcontext + restorecon over chcon. chcon is a bandaid.
Remember: Mnemonic: "chcon is a Ch-eat, semanage is the S-eal."
chconchanges labels temporarily (lost on relabel),semanagewrites to the policy store permanently. If you seechconin a runbook, replace it with the two-stepsemanage fcontext+restorecon.War story: The #1 SELinux-caused outage pattern: an admin moves a web root from
/var/www/html/to/srv/www/usingmv. The files keep their olddefault_tlabel. Apache gets AVC denials. The admin runssetenforce 0"temporarily" and forgets. Months later, a security audit discovers the server has been running without MAC the entire time.
Booleans — Policy Toggle Switches¶
Booleans let you enable/disable specific policy behaviors without writing custom policy:
# List all booleans
getsebool -a
# Check specific boolean
getsebool httpd_can_network_connect
# httpd_can_network_connect --> off
# Enable (persistent across reboots with -P)
setsebool -P httpd_can_network_connect on
Common booleans you'll use:
httpd_can_network_connect → Apache/Nginx can make outbound connections
httpd_can_network_connect_db → Apache can talk to databases
httpd_enable_homedirs → Apache can serve from ~/public_html
allow_ftpd_full_access → FTP can read/write outside its default dirs
container_manage_cgroup → Container runtimes can manage cgroups
Troubleshooting with audit2allow¶
When SELinux blocks something, it writes an AVC (Access Vector Cache) denial to the audit log:
# Find recent denials
ausearch -m AVC -ts recent
# Or use the audit log directly
grep "avc: denied" /var/log/audit/audit.log
A denial looks like:
type=AVC msg=audit(1678234567.123:456): avc: denied { read } for
pid=1234 comm="httpd" name="config.yml" dev="sda1" ino=789012
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0
Generate a policy module to allow the denied action:
# From audit log
ausearch -m AVC -ts recent | audit2allow -M myfix
# Creates myfix.te (human-readable) and myfix.pp (compiled)
# Review the .te file FIRST
cat myfix.te
# Then install if it makes sense
semodule -i myfix.pp
Never blindly pipe audit2allow output into policy. Always read the
.tefile. It might grant far more than you intended.Debug clue: When reading AVC denials, the key fields are
scontext(source -- the process),tcontext(target -- the resource), andtclass(type of object -- file, socket, port). If scontext showsunconfined_t, the process was not confined at all, which usually means it was started outside systemd or from an interactive shell.
Port Contexts¶
SELinux labels network ports too:
# See what ports httpd_t can bind to
semanage port -l | grep http
# http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
# Add a custom port
semanage port -a -t http_port_t -p tcp 8090
AppArmor — The Path-Based Alternative¶
AppArmor confines processes based on file paths rather than labels. It ships as the default MAC on Ubuntu and SUSE.
Profile Modes¶
| Mode | Behavior |
|---|---|
| Enforce | Blocks and logs violations |
| Complain | Logs violations but does not block |
| Disabled | Profile loaded but inactive (rare, use complain) |
# See all profiles and their modes
aa-status
# or
apparmor_status
# Set a profile to complain mode
aa-complain /etc/apparmor.d/usr.sbin.nginx
# Set back to enforce
aa-enforce /etc/apparmor.d/usr.sbin.nginx
Profile Anatomy¶
Profiles live in /etc/apparmor.d/ and are named after the binary path with dots replacing slashes:
# /etc/apparmor.d/usr.sbin.nginx
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
# Binary itself
/usr/sbin/nginx mr,
# Config
/etc/nginx/** r,
# Web root
/var/www/** r,
# Logs
/var/log/nginx/** rw,
# PID file
/run/nginx.pid rw,
# Temp files
/var/lib/nginx/tmp/** rw,
# Network
network inet stream,
network inet6 stream,
# Deny everything else (implicit)
}
Permission flags:
r — read
w — write
a — append
k — lock
l — link
m — mmap PROT_EXEC
x — execute (ix=inherit, px=profile, ux=unconfined, cx=child)
Generating Profiles¶
# Generate a profile skeleton
aa-genprof /usr/sbin/myapp
# Starts myapp, scans logs, asks you about each access — interactive
# Update profile from logs (after running in complain mode)
aa-logprof
Abstractions — Reusable Rule Sets¶
AppArmor ships with abstractions in /etc/apparmor.d/abstractions/:
base — basic system access every process needs
nameservice — DNS, NSS, LDAP lookups
authentication — PAM, shadow, login
apache2-common — standard Apache paths
mysql — MySQL client access
Include them with:
SELinux vs AppArmor — When You Get to Choose¶
| Criteria | SELinux | AppArmor |
|---|---|---|
| Shipped with | RHEL, CentOS, Fedora, Rocky | Ubuntu, SUSE, Debian |
| Approach | Label-based (survives mv/rename) | Path-based (simpler to reason) |
| Learning curve | Steep | Moderate |
| Multi-level security | Yes (MLS) | No |
| Default policy | Comprehensive (targeted) | Per-application profiles |
| Container support | Excellent (built-in labeling) | Good (Docker/LXC integration) |
In practice, you use whatever your distro ships. RHEL shops run SELinux. Ubuntu shops run AppArmor. Fighting the default is rarely worth it.
Container Interactions¶
Both MAC systems interact with containers:
SELinux + Containers:
# Containers auto-labeled with container_t / svirt_lxc_net_t
# Volume mounts need :Z (private) or :z (shared) suffix
podman run -v /data:/data:Z myimage
# Without :Z, the container can't read the volume — SELinux blocks it
AppArmor + Containers:
# Docker applies default docker-default profile
# Override with:
docker run --security-opt apparmor=my-custom-profile myimage
# Disable (not recommended):
docker run --security-opt apparmor=unconfined myimage
Key Takeaways¶
- MAC is your second firewall inside the OS. DAC alone is not enough.
- SELinux type enforcement handles 95% of real-world policy decisions.
semanage fcontext+restoreconis the correct way to fix file labels.- Booleans are your first stop before writing custom policy.
audit2allowis a diagnosis tool, not a fix-it button — read before you apply.- AppArmor is path-based and easier to learn, but less granular.
- Never disable MAC in production. Use permissive/complain mode to debug, then fix and re-enforce.
Wiki Navigation¶
Prerequisites¶
- SELinux & Linux Hardening (Topic Pack, L2)
Related Content¶
- Linux Users & Permissions (Topic Pack, L1) — Linux Fundamentals, Linux Hardening
- RHCE (EX294) Exam Preparation (Topic Pack, L2) — Linux Fundamentals, SELinux & AppArmor
- SSH Deep Dive (Topic Pack, L1) — Linux Fundamentals, Linux Hardening
- /proc Filesystem (Topic Pack, L2) — Linux Fundamentals
- Advanced Bash for Ops (Topic Pack, L1) — Linux Fundamentals
- Adversarial Interview Gauntlet (30 sequences) (Scenario, L2) — Linux Fundamentals
- Bash Exercises (Quest Ladder) (CLI) (Exercise Set, L0) — Linux Fundamentals
- Case Study: CI Pipeline Fails — Docker Layer Cache Corruption (Case Study, L2) — Linux Fundamentals
- Case Study: Container Vuln Scanner False Positive Blocks Deploy (Case Study, L2) — Linux Fundamentals
- Case Study: Disk Full Root Services Down (Case Study, L1) — Linux Fundamentals