Managing SELinux on Linux: Modes, Policies, Contexts and Booleans

SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system built into the Linux kernel. It provides a security layer on top of the standard Unix discretionary access controls (DAC). Understanding SELinux deeply is critical for RHCA — it is one of the most commonly misunderstood and incorrectly disabled security features in Linux, yet it protects against entire classes of attacks that file permissions cannot prevent.

DAC vs MAC — Why SELinux Exists

Discretionary Access Control (Standard Linux Permissions)

In standard Linux, the file owner decides who can access the file (chmod, chown). This is called DAC because access control is at the discretion of the data owner. The problem:

  • If a web server process is compromised, the attacker can access everything that the apache user can access
  • If root is compromised (e.g., a kernel exploit), all security is lost
  • No way to limit what a process can do beyond file ownership

Mandatory Access Control (SELinux)

SELinux enforces policies set by a security administrator — not the file owner. Even if a process is compromised, it can only access resources specifically allowed by the SELinux policy for its type. Key properties:

  • Type Enforcement (TE): Every process has a type (domain), every file has a type. Policy defines which domains can access which types.
  • Role-Based Access Control (RBAC): Users are assigned roles, roles are assigned domains. Limits which types of processes users can run.
  • Multi-Level Security (MLS): Military-grade classification (unclassified, secret, top secret). Not used in most enterprise environments.

SELinux Labels (Security Context)

Every object (file, process, socket, device) has a security context (also called a label). The format is:

user:role:type:sensitivity_level

# Examples:
# system_u:system_r:httpd_t:s0        - Apache process context
# system_u:object_r:httpd_sys_content_t:s0  - web file context
# unconfined_u:unconfined_r:unconfined_t:s0 - normal user process (unconfined)

# The TYPE field is most important for access decisions

# View contexts:
# ls -Z /var/www/html/               # file context
# ps -efZ | grep httpd               # process context
# ls -ldZ /var/www/                  # directory context
# id -Z                              # your current context
# stat -c "%C" /etc/passwd           # file context via stat

SELinux Modes — When and Why to Use Each

ModePolicy EnforcedLogs DenialsWhen to Use
EnforcingYes — blocks violationsYesProduction (always)
PermissiveNo — allows everythingYes (logs only)Debugging, testing new policy, troubleshooting
DisabledNoNoNever (security risk, relabeling required to re-enable)

Why never disable SELinux permanently?

  • Disabling SELinux removes all its protection permanently until re-enabled
  • Re-enabling requires a full filesystem relabel at boot (can take hours)
  • If you have a problem, switch to Permissive to debug, then fix and switch back to Enforcing
# Check current mode:
# getenforce                         # Enforcing, Permissive, or Disabled
# sestatus                           # detailed status including policy name and version

# Temporary mode change (no reboot needed):
# setenforce 0                       # switch to Permissive
# setenforce 1                       # switch back to Enforcing

# Permanent mode change (requires reboot):
# vim /etc/selinux/config
SELINUX=enforcing                    # or permissive or disabled
# /etc/sysconfig/selinux is a symlink to /etc/selinux/config

# Grub kernel parameter for single-boot change:
# Add selinux=0 to kernel parameters (in GRUB) for one-time boot without SELinux
# Add enforcing=0 to boot in Permissive (kernel loads SELinux but doesn't enforce)

Type Enforcement — The Core of SELinux

Access decisions are based on the type of the process (domain) accessing the type of the object (file type). The policy specifies allowed combinations.

# Example: httpd_t (Apache daemon type) can:
# - Read files of type httpd_sys_content_t (web content)
# - Write to httpd_sys_rw_content_t (writable web content)
# - Read/write to httpd_log_t (log files)
# - Listen on http_port_t (ports 80, 443)

# httpd_t CANNOT (by default):
# - Read /etc/shadow (shadow_t)
# - Write to /home/ (user_home_dir_t)
# - Execute binaries in /tmp/ (tmp_t)
# - Connect to arbitrary network ports

# So even if Apache is completely compromised, the attacker is trapped
# in the httpd_t domain with very limited capabilities

Managing File Contexts

# View file context:
# ls -Z /etc/passwd
# -rw-r--r--. root root system_u:object_r:passwd_file_t:s0 /etc/passwd

# Change context temporarily (resets after restorecon or relabel):
# chcon -t httpd_sys_content_t /data/website/index.html
# chcon -R -t httpd_sys_content_t /data/website/     # recursive

# Change user and role too:
# chcon -u system_u -r object_r -t httpd_sys_content_t /data/file

# Restore default context (from SELinux policy):
# restorecon -v /data/website/index.html              # one file
# restorecon -Rv /data/website/                       # recursive, verbose

# Set PERMANENT custom context (survives relabels):
# semanage fcontext -a -t httpd_sys_content_t "/data/website(/.*)?"
# restorecon -Rv /data/website/       # apply the new policy

# List all fcontext rules:
# semanage fcontext -l
# semanage fcontext -l | grep httpd   # filter for httpd-related

# Common content types:
# httpd_sys_content_t     - read-only web content
# httpd_sys_rw_content_t  - read-write web content (uploads)
# samba_share_t           - Samba shares
# public_content_t        - publicly readable content
# nfs_t                   - NFS content

SELinux Booleans

Booleans are on/off switches that modify SELinux policy without writing custom modules. They allow administrators to enable common use cases without full policy knowledge.

# List all booleans:
# getsebool -a
# semanage boolean -l                # with description and current state

# Check specific boolean:
# getsebool httpd_can_network_connect

# Set boolean (temporary, resets on reboot):
# setsebool httpd_can_network_connect on
# setsebool httpd_can_network_connect 1

# Set boolean permanently:
# setsebool -P httpd_can_network_connect on
# setsebool -P httpd_can_network_connect 1

# Common booleans by service:
# httpd_can_network_connect         - Apache can connect to network (for proxying)
# httpd_can_network_connect_db      - Apache can connect to databases
# httpd_can_sendmail                - Apache can send email
# httpd_enable_cgi                  - Apache can execute CGI scripts
# httpd_use_nfs                     - Apache can serve files from NFS
# ftp_home_dir                      - FTP users can access home directories
# ftp_anon_write                    - FTP anonymous write access
# samba_export_all_rw               - Samba can share any directory read-write
# samba_enable_home_dirs            - Samba can share home directories
# nfs_export_all_rw                 - NFS can export any directory read-write
# allow_ftpd_anon_write             - FTP daemon anonymous write
# allow_user_exec_content           - Users can execute content in home dirs

SELinux Ports

Services can only listen on ports allowed by SELinux policy for their type.

# List allowed ports for httpd:
# semanage port -l | grep http

# Example output:
# http_port_t  tcp  80, 443, 488, 8008, 8009, 8443

# Add non-standard port for Apache (e.g., 8888):
# semanage port -a -t http_port_t -p tcp 8888

# Modify existing port:
# semanage port -m -t http_port_t -p tcp 8888

# Delete port:
# semanage port -d -t http_port_t -p tcp 8888

# List all port definitions:
# semanage port -l

Diagnosing and Fixing SELinux Denials

# All SELinux denials are logged to:
# /var/log/audit/audit.log

# AVC = Access Vector Cache — where denials are logged

# Read audit log:
# tail -f /var/log/audit/audit.log
# ausearch -m avc -ts today          # today's denials

# Get human-readable explanation with fix suggestions:
# sealert -a /var/log/audit/audit.log

# Example audit.log denial:
# type=AVC msg=audit(1686000000.123:456): avc: denied { write }
# for pid=12345 comm="httpd" name="uploads"
# scontext=system_u:system_r:httpd_t:s0
# tcontext=system_u:object_r:httpd_sys_content_t:s0
# tclass=dir

# Reading the denial:
# "denied { write }" = tried to write
# comm="httpd" = the Apache process
# name="uploads" = the directory it tried to write
# scontext=httpd_t = the process context
# tcontext=httpd_sys_content_t = the file context
# FIX: change directory context to httpd_sys_rw_content_t

# Generate custom policy from denials:
# ausearch -m avc -ts today | audit2allow    # shows allow rules
# ausearch -m avc -ts today | audit2allow -M mymodule  # creates policy module
# semodule -i mymodule.pp                              # install module

# Full workflow for persistent issues:
# 1. Switch to Permissive: setenforce 0
# 2. Reproduce the issue
# 3. Check audit log for denials
# 4. Fix: add boolean, change context, or write custom policy
# 5. Switch back to Enforcing: setenforce 1
# 6. Test again

Relabeling the Filesystem

# Needed when:
# - Re-enabling SELinux after it was disabled
# - Files were created while SELinux was disabled
# - Bulk copying files without preserving context

# Relabel on next boot:
# touch /.autorelabel                # create flag file
# reboot                             # relabeling happens during boot

# Manual relabel (takes time):
# fixfiles -F relabel
# restorecon -Rv /                   # relabel everything (slow)

# Relabel specific directory:
# restorecon -Rv /var/www/

# Check what context a file SHOULD have:
# matchpathcon /var/www/html/index.html
# Expected: system_u:object_r:httpd_sys_content_t:s0