Skip to main content
RunBook Academy

← All labs in Linux

Lab · advanced · ~45 min

Lab: Diagnose and fix SELinux denials on a service

B · Nested virtualisationC · Simulation

Objectives

  • Trigger a SELinux AVC denial
  • Find and read the denial with ausearch
  • Decide the correct fix
  • Apply the fix and verify

Prerequisites

This lab triggers a SELinux AVC denial, diagnoses it, and applies the right fix. By the end you will have the discipline for every MAC denial investigation.

Objective

By the end of this lab, you can:

  • Trigger an AVC denial.
  • Read the denial with ausearch.
  • Decide the right fix.
  • Apply the fix and verify the service works.

Architecture

You need a RHEL-family host (RHEL 9, Rocky 9, Alma 9, Fedora, or CentOS Stream 9) with:

  • SELinux in enforcing mode.
  • The httpd package installed (or nginx).
  • A writable location for test content.

Tasks

Task 1: Verify SELinux is enforcing

sestatus
getenforce

Expected: enabled, enforcing. If not, fix it:

sudo setenforce 1
sudo sed -i 's/SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config

Task 2: Create a mislabeled test file

sudo mkdir -p /var/www/html/test
sudo cp /usr/share/nginx/html/index.html /var/www/html/test/ 2>/dev/null || \
    sudo cp /etc/hostname /var/www/html/test/index.html

# Mislabel: assign an arbitrary label
sudo chcon -t shadow_t /var/www/html/test/index.html
ls -lZ /var/www/html/test/

Task 3: Trigger the denial

If nginx is running:

sudo systemctl start nginx
curl http://localhost/test/

The curl fails with “Permission denied”. Check the AVC:

sudo ausearch -m avc -ts recent

Output should show a denial of nginx (or httpd) reading a file of type shadow_t.

Task 4: Diagnose

ls -lZ /var/www/html/test/index.html

The file has type shadow_t but should be httpd_sys_content_t. Diagnosis: mislabeled file.

Task 5: Fix with restorecon

sudo restorecon -v /var/www/html/test/index.html
ls -lZ /var/www/html/test/index.html

Now the file has httpd_sys_content_t.

Task 6: Verify

curl http://localhost/test/

Should succeed. Confirm no new AVCs:

sudo ausearch -m avc -ts recent

Task 7: A different scenario - a port outside the policy

Record the starting state before you change anything. The point of this task is that 8888 is not in http_port_t, and you want to have seen that for yourself:

sudo semanage port -l | grep '^http_port_t'
# http_port_t   tcp   80, 81, 443, 488, 8008, 8009, 8443, 9000

Now move nginx to 8888. Stop the running instance first — the systemd-managed nginx from Task 3 still holds :80, and starting a second one by hand produces a bind conflict that looks nothing like an SELinux denial:

sudo systemctl stop nginx
sudo sed -i 's/listen\( *\)80\b/listen\18888/' /etc/nginx/nginx.conf
sudo grep -n 'listen' /etc/nginx/nginx.conf     # confirm the edit
sudo nginx -t                                    # syntax only; SELinux is not consulted here
sudo systemctl start nginx                       # expect this to FAIL

The start fails. nginx -t passed because it never binds a socket — syntax checking and policy enforcement are different questions, and this is the gap that makes port denials confusing. Read the denial:

sudo systemctl status nginx --no-pager
sudo ausearch -m avc -ts recent | grep -E 'tclass=(tcp_socket|port)'

The AVC shows scontext=...:httpd_t denied name_bind on tcontext=...:unreserved_port_t — nginx runs as httpd_t, which is only permitted to bind ports labelled http_port_t.

Task 8: Fix the port

sudo semanage port -a -t http_port_t -p tcp 8888
sudo semanage port -l | grep '^http_port_t'   # 8888 is now listed
sudo systemctl start nginx
ss -ltnp | grep 8888

Task 9: A third scenario - generate a policy

The first two scenarios had an answer already in the policy: a label to restore, a port to add. This one does not — the content lives somewhere the shipped policy has no rule for, and you have to write the rule.

Put it under /srv, not /var/www. Anything you create under /var/www inherits httpd_sys_content_t from the existing /var/www(/.*)? fcontext rule, so nginx is allowed to read it and no denial ever happens — the task would appear to pass while teaching nothing:

sudo mkdir -p /srv/myapp
echo ok | sudo tee /srv/myapp/index.html

# Label it as something httpd_t has no rule for
sudo semanage fcontext -a -t var_t '/srv/myapp(/.*)?'
sudo restorecon -Rv /srv/myapp
ls -lZ /srv/myapp/

Point nginx at it and reload:

sudo tee /etc/nginx/conf.d/myapp.conf >/dev/null <<'EOF'
server {
    listen 8889;
    location /myapp/ {
        alias /srv/myapp/;
    }
}
EOF
sudo semanage port -a -t http_port_t -p tcp 8889
sudo nginx -t && sudo systemctl reload nginx
curl -sI http://localhost:8889/myapp/

The request returns 403, not 404 — the file is there and nginx found it; the kernel refused the read. That distinction is the whole diagnostic: a 404 means you got the path wrong, a 403 with a matching AVC means policy stopped you.

sudo ausearch -m avc -ts recent
# scontext=...:httpd_t tcontext=...:var_t tclass=file denied { read open }

Generate a policy module:

sudo ausearch -m avc -ts recent | audit2allow -M nginx-myapp

# Review the .te file
cat nginx-myapp.te

# Install
sudo semodule -i nginx-myapp.pp

Verify:

curl -s http://localhost:8889/myapp/     # returns 'ok'
sudo ausearch -m avc -ts recent          # no new denials

Task 10: Document the lab

SELinux MAC Lab
===============

Scenario 1 (mislabel):
- Symptom: nginx returns 403
- AVC: scontext=httpd_t tcontext=shadow_t
- Fix: restorecon
- Verification: curl succeeds

Scenario 2 (port):
- Symptom: nginx cannot bind 8888
- AVC: scontext=httpd_t tclass=port
- Fix: semanage port -a -t http_port_t -p tcp 8888
- Verification: nginx listens on 8888

Scenario 3 (policy gap):
- Symptom: nginx returns 403 (not 404) for /srv/myapp
- AVC: scontext=httpd_t tcontext=var_t tclass=file denied { read }
- Fix: audit2allow + semodule (labelling would be the better fix)
- Verification: curl succeeds, no new AVCs

Validation

  • Scenario 1: the mislabeled file was fixed with restorecon, and ls -lZ shows httpd_sys_content_t again.
  • Scenario 2: nginx failed to start on 8888 with an AVC naming name_bind and unreserved_port_t, and started successfully after semanage port -a -t http_port_t -p tcp 8888.
  • Scenario 3: the 403 was accompanied by a matching AVC (a 403 with no AVC is an nginx permissions problem, not SELinux), and semodule -l | grep nginx-myapp lists the installed module.
  • All scenarios: getenforce reported Enforcing throughout. A scenario that only works after setenforce 0 is not a pass.

Cleanup

Revert every change, in reverse order. Note that the paths here are the ones the lab actually created — /var/www/html/test from Task 2 and /srv/myapp from Task 9:

sudo systemctl stop nginx

# Task 9: policy module, content, and its label rule
sudo semodule -r nginx-myapp 2>/dev/null
sudo rm -f /etc/nginx/conf.d/myapp.conf
sudo rm -rf /srv/myapp
sudo semanage fcontext -d '/srv/myapp(/.*)?' 2>/dev/null

# Tasks 7-8: the ports added to http_port_t
sudo semanage port -d -t http_port_t -p tcp 8888 2>/dev/null
sudo semanage port -d -t http_port_t -p tcp 8889 2>/dev/null

# Task 7: put nginx back on port 80
sudo sed -i 's/listen\( *\)8888\b/listen\180/' /etc/nginx/nginx.conf

# Task 2: the mislabelled test content
sudo rm -rf /var/www/html/test
sudo restorecon -R /var/www/

sudo nginx -t && sudo systemctl start nginx
ss -ltnp | grep ':80'

What you learned

  • SELinux denials follow a pattern: AVC log tells you what is denied.
  • The fix depends on the cause: mislabel, port, or policy gap.
  • restorecon is the first fix to try for mislabeled files.
  • audit2allow generates policies; review them carefully.

Deliverables

  • · An AVC denial log
  • · Diagnosis: mislabel vs policy gap
  • · Fix applied (restorecon, semanage, audit2allow)
  • · Verification that the service works in enforcing mode

Verification status

Last reviewed
2026-08-09
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.