Skip to content

Linux Security Modules

The standard Linux permission model is Discretionary Access Control (DAC): the file owner controls access by setting rwx permissions plus group membership. It’s called “discretionary” because the owner has discretion over who can access their files.

DAC has a fundamental weakness: a compromised process running as root has zero restrictions. If nginx is exploited and runs as root, it can read /etc/shadow, modify system files, or install backdoors.

Mandatory Access Control (MAC) adds an additional policy layer that the kernel enforces regardless of DAC settings - even root is bound by MAC policy. Applications can only do what the policy explicitly allows.


LSM is a kernel framework (merged in 2003) that allows different MAC implementations to hook into the kernel’s access control decisions. The framework is lightweight and doesn’t favor any particular security model.

An LSM intercepts security-sensitive operations and calls registered security hooks before allowing them to proceed. If any hook returns denial, the operation is blocked.

LSMDefault onFocus
SELinuxRHEL, Fedora, CentOSFine-grained label/policy; NSA-developed
AppArmorUbuntu, SUSEPath-based profiles; simpler to configure
SmackEmbedded systems (Tizen)Simplified mandatory access
TomoyoJapan-originated distrosPathname-based; good learning mode
YamaMany distros (minor LSM)Restrict ptrace scope; can stack with others

SELinux was developed by the NSA and contributed to the Linux kernel in 2000. It has been the default MAC system on RHEL-family distros ever since.

SELinux works with three building blocks:

ConceptDescriptionExample
Context (label)Security label on files, processes, and portssystem_u:system_r:httpd_t:s0
RuleA statement defining allowed access between two contextsallow httpd_t var_log_t : file write
PolicyA compiled set of rules loaded into the kerneltargeted, minimum, MLS

The default policy is deny: if no rule explicitly allows an operation, SELinux blocks it.

Context format: user:role:type:level

  • user - SELinux user (not the same as Linux username)
  • role - Role (RBAC component)
  • type - The most important field; type enforcement rules use this. Convention: ends in _t
  • level - MLS/MCS security level (only used in MLS/MCS policies)
ModeBehavior
EnforcingPolicy violations are blocked AND logged. This is the normal production state.
PermissivePolicy violations are only logged (not blocked). Use for debugging or gradual policy rollout.
DisabledSELinux is completely off. Requires reboot to re-enable; triggers full filesystem relabel.
Terminal window
sestatus # show current mode, policy, and MLS status
getenforce # print just the mode (Enforcing/Permissive/Disabled)
sudo setenforce 1 # switch to Enforcing (live; no reboot needed)
sudo setenforce 0 # switch to Permissive (live; no reboot needed)

To permanently change the mode, edit /etc/selinux/config:

Terminal window
sudo vim /etc/selinux/config
# SELINUX=enforcing # or permissive, or disabled
# SELINUXTYPE=targeted # policy type
PolicyDescription
targeted (default)Only network-facing services are confined. User processes and init are unconfined. Best balance of security vs. usability.
minimumTargeted policy with most processes unconfined; a subset of targeted.
MLSMulti-Level Security: all processes confined; Bell-LaPadula model; used in classified environments. Very restrictive.
Terminal window
# View contexts with -Z flag
ls -Z /var/www/html/
# -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
ps axZ
# LABEL PID TTY STAT COMMAND
# system_u:system_r:httpd_t:s0 7490 ? Ss /usr/sbin/httpd
id -Z # SELinux context of current session

chcon changes the context of a file (temporary - reverted by restorecon or autorelabel):

Terminal window
chcon -t httpd_sys_content_t /mydir/index.html
chcon --reference /var/www/html/index.html /mydir/index.html # copy context from reference

Persistent Context with semanage + restorecon

Section titled “Persistent Context with semanage + restorecon”

For permanent context changes that survive a relabel:

Terminal window
# 1. Define the rule (maps path to context type)
sudo semanage fcontext -a -t httpd_sys_content_t '/virtualHosts(/.*)?'
# 2. Apply the rule to existing files
sudo restorecon -RFv /virtualHosts
# restorecon reset /virtualHosts context default_t->httpd_sys_content_t
# View all custom fcontext rules
sudo semanage fcontext -l | grep virtualHosts

The two-step pattern is essential:

  • semanage fcontext = writes the rule to the policy database
  • restorecon = applies the rule to the filesystem right now

Booleans allow runtime adjustment of policy behavior without writing new rules:

Terminal window
getsebool -a # list all booleans and current values
getsebool httpd_can_network_connect # check specific boolean
sudo setsebool httpd_can_network_connect on # enable (not persistent across reboot)
sudo setsebool -P httpd_can_network_connect on # enable AND persist (-P flag)
semanage boolean -l | grep httpd # view persistent settings

Common booleans:

BooleanEffect
httpd_can_network_connectAllow Apache to initiate network connections
httpd_can_network_connect_dbAllow Apache to connect to databases
httpd_enable_homedirsAllow Apache to serve user home directories
allow_ftpd_anon_writeAllow vsftpd anonymous uploads
samba_enable_home_dirsAllow Samba to share home directories

Install setroubleshoot-server for human-readable denial messages:

Terminal window
sudo dnf install setroubleshoot-server # RHEL/Fedora
# After installation, restart auditd
sudo systemctl restart auditd

AVC (Access Vector Cache) denials are logged to /var/log/audit/audit.log. setroubleshoot also pushes summaries to /var/log/messages:

Terminal window
# Check for recent denials
sudo ausearch -m AVC -ts recent
sudo grep "SELinux is preventing" /var/log/messages
# Get detailed explanation and suggested fix
sudo sealert -l <uuid-from-ausearch>
# Generate a custom policy module from denials
grep httpd /var/log/audit/audit.log | audit2allow -M mypol
sudo semodule -i mypol.pp

Typical debugging workflow:

  1. Try the operation that fails
  2. Check /var/log/audit/audit.log for AVC entries (ausearch -m AVC -ts recent)
  3. Read sealert output for the root cause and suggested fix
  4. Fix with: boolean change, semanage fcontext + restorecon, or audit2allow (last resort)

AppArmor is a path-based MAC system, simpler to configure than SELinux. It’s the default on Ubuntu and SUSE.

FeatureSELinuxAppArmor
Access control basisLabels (contexts)File paths
Policy complexityHighLower
Learning modeNoYes (complain mode)
File relabelingRequiredNot required
Default distrosRHEL, FedoraUbuntu, SUSE
Terminal window
sudo apparmor_status # full status: loaded profiles, modes
sudo systemctl status apparmor
sudo aa-status # abbreviated status
ModeBehaviorSet with
EnforceViolations are blocked and logged. Default for active profiles.sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
ComplainViolations are only logged (not blocked). “Learning mode” - use to build new profiles.sudo aa-complain /etc/apparmor.d/usr.sbin.nginx

Profiles are stored in /etc/apparmor.d/. Each profile restricts what a specific executable can do (file access, capabilities, network, etc.):

Terminal window
ls /etc/apparmor.d/
# bin.ping usr.sbin.cups-browsed usr.sbin.avahi-daemon ...
# View a profile
cat /etc/apparmor.d/usr.sbin.cups-browsed
# Reload a modified profile
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
# Disable a profile
sudo aa-disable /etc/apparmor.d/usr.sbin.nginx
CommandPurpose
apparmor_status / aa-statusShow all profiles and their modes
aa-enforce <profile>Switch profile to enforce mode
aa-complain <profile>Switch profile to complain (learning) mode
aa-disable <profile>Unload profile permanently
aa-logprofReview log, suggest profile additions, apply them interactively
aa-genprof <binary>Interactive profile generator for a new program
apparmor_parser -r <profile>Reload a modified profile

The recommended workflow for profiling a new application:

Terminal window
# 1. Start profile generation (runs app in complain mode)
sudo aa-genprof /usr/sbin/myapp
# 2. In another terminal: exercise the application (all normal use cases)
myapp --do-typical-things
# 3. Back in aa-genprof: press 'S' to scan logs and approve/deny suggested rules
# 4. Press 'F' to finish - profile is saved to /etc/apparmor.d/
# 5. Switch to enforce mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.myapp

ConsiderationChoose SELinuxChoose AppArmor
Your distroRHEL/Fedora/CentOSUbuntu/SUSE/Debian
Security strictnessMaximum (label-based, everything labeled)High but more practical
Admin learning curveSteeperMore approachable
Policy fine-tuningVery granularProfile-based but simpler
Filesystem independenceYes (labels follow files)No (path-based; symlinks matter)

In practice, use whatever your distro ships with. Both provide effective MAC when properly configured. Switching from one to the other is complex.