Diagnosing SELinux denials: from AVC records to file-label repair

Diagnosing SELinux denials: from AVC records to file-label repair

Diagnosing SELinux denials: from AVC records to file-label repair

After website files are moved from their original directory to /srv, httpd returns 403—even though the files are readable and the service is running. At this point, do not simply broaden permissions or disable SELinux. Standard file permissions, service authorization configuration, and mandatory access control are three separate layers of checks. Using an existing httpd test virtual host on AlmaLinux 9, this article correlates a failed request with AVC records, repairs file labels persistently, and then verifies both service operation and access boundaries.

1. Establish the test scope and baseline

CloudDDR dedicated servers support AlmaLinux 9. After payment, retrieve the credentials from the console and connect over SSH; if multiple addresses are listed, use the first one. The following operations require administrator privileges. Run them only in an isolated test environment, without overwriting existing production directories or changing listening addresses or firewall settings.

The prerequisites are an installed httpd service, a configured test virtual host, and an existing URL for accessing it. First, save the virtual host configuration and record its original DocumentRoot. Install the appropriate package if the diagnostic tools are missing, then check the enforcement mode and audit service:

sudo dnf install policycoreutils-python-utils
getenforce
sestatus
sudo systemctl status auditd --no-pager
sudo semanage fcontext -l -C
sudo semanage permissive -l

The expected global mode is Enforcing, and the domain in which httpd runs must not have been set individually to permissive. If the audit service is not operating correctly, restore evidence collection first. The absence of denial records in the logs does not prove that access is unrestricted. Save the list of local file-context mappings so you can later distinguish existing rules from those added during this test.

setenforce 0 is not a repair. A successful request after enforcement has been disabled is only a diagnostic clue; it does not prove that the service will still work once enforcing mode is restored.

2. Rule out standard permission and configuration errors first

Create a dedicated test directory containing only a single-line verification marker. Directories must allow traversal, and the file must allow reading. These permissions are appropriate only for public test content—not for keys, backups, or production data.

sudo install -d -m 0755 /srv/selinux-lab
printf 'selinux-lab-ok\n' | sudo tee /srv/selinux-lab/index.html
sudo chmod 0644 /srv/selinux-lab/index.html
namei -l /srv/selinux-lab/index.html
ls -ldZ /srv/selinux-lab
ls -lZ /srv/selinux-lab/index.html

In the existing test virtual host, change the document root and the corresponding directory authorization to:

DocumentRoot "/srv/selinux-lab"

<Directory "/srv/selinux-lab">
    AllowOverride None
    Options -Indexes
    Require all granted
</Directory>

Before making changes, check how configuration files are included to ensure that another directory section does not override the authorization settings. Use httpd -t to check syntax and httpd -S to confirm which virtual host handles the request. Reload only after these checks pass.

Set the shell variable CHECK_URL to the actual test URL, with the path pointing to /index.html. The URL must route to this virtual host; do not put credentials in it. Check the response body rather than merely confirming that a connection succeeds:

sudo httpd -t
sudo httpd -S
sudo systemctl reload httpd
curl --fail --show-error "$CHECK_URL"

If the request fails, read the httpd error log first. Missing execute permission on a parent directory, a request reaching the wrong virtual host, or a denial by authorization rules can all produce similar symptoms. Recursively broadening permissions at this stage will neither fix a labeling problem nor preserve the existing exposure boundary.

3. Use AVC records to identify the subject, object, and action

Record the time of the request, reproduce the failed operation only once, and then query denial records from the same time window:

sudo ausearch -m AVC,USER_AVC -ts recent -i
ps -eZ | grep '[h]ttpd'
matchpathcon -V /srv/selinux-lab/index.html

In the AVC records, focus on scontext, tcontext, tclass, and the denied permission. scontext identifies the domain of the process attempting access, while tcontext identifies the target object's security context. Reading a file and searching a directory are different actions; the process name alone is not enough to determine what happened.

Correlate the path, process, and timestamp in the records with the failed request. If directory search is denied, inspect every directory component in the path as well. Fixing only the final file may have no effect. If a log record contains no path information, use the audit event identifier to examine other records belonging to the same event.

The absence of an AVC record does not establish that SELinux is unrelated. First check the time window, audit status, and the service's actual process domain. If there is still no relevant evidence, return to application configuration and standard permissions instead of blindly adding allow rules. Do not feed all historical denial records into a policy-generation tool either; they may belong to other workloads.

4. Repair web content labels persistently

For ordinary static content that httpd needs to read, httpd_sys_content_t is the purpose-specific type provided by the existing policy. Add a narrowly scoped mapping only when the evidence shows that the test path's labels do not match its intended use:

sudo semanage fcontext -a -t httpd_sys_content_t \
    '/srv/selinux-lab(/.*)?'
sudo restorecon -Rv /srv/selinux-lab
matchpathcon -V /srv/selinux-lab/index.html
ls -lZ /srv/selinux-lab/index.html

If the exact same expression already exists, inspect the existing rule and use -m to modify it if necessary, rather than adding more rules. The expression covers only the test directory; do not relabel all of /srv as web content. semanage stores the mapping between paths and types, while restorecon applies the expected contexts to existing files.

chcon changes the current label directly but does not create a persistent mapping, so it cannot replace this step. A later relabeling operation may undo temporary changes.

The service does not need write access to a static-content directory. Do not switch all content to a writable type just to eliminate a single denial, and do not enable unrelated booleans along the way. If uploads, caches, or other features genuinely require writes, allocate a separate directory and design its permissions and labels around the actual operations required.

5. Verify the repair and preserve a rollback path

Keep SELinux in enforcing mode and access the same URL again. Confirm that the response body contains selinux-lab-ok, and check for any new AVC records associated with that request. Then run restorecon again, restart the test service, and repeat the request to verify that the repair does not depend on temporary labels or the state of an old process.

getenforce
sudo restorecon -Rv /srv/selinux-lab
sudo systemctl restart httpd
curl --fail --show-error "$CHECK_URL"
sudo ausearch -m AVC,USER_AVC -ts recent -i

Acceptance checks must also include negative tests: a request for a nonexistent file should fail; the test directory should not allow directory indexing; the service should not have any new, unexpected listeners; and local label changes should cover only the agreed path. These checks validate application behavior, configuration, and the scope of changes respectively. They cannot substitute for one another.

When ending the experiment, first restore the original virtual host configuration, check its syntax, and reload. Then remove the test directory. You may delete mappings that were genuinely added during this test; if an existing mapping was modified, restore the type recorded beforehand. After cleanup, run restorecon on any retained paths.

Keep a final record of the failed request, the associated AVC records, labels before and after the change, the mapping expression, and the response after the restart. A single successful request demonstrates only functional recovery. Complete verification of the repair also requires an unchanged enforcement mode, reproducible mappings, and no expansion of the access boundary.

Frequently asked questions

Does an HTTP 403 prove that SELinux blocked the request?

No. Check directory traversal permissions, file read permissions and httpd authorization rules. Match the failed request to an AVC record from the same period before attributing it to SELinux.

Why not use chcon or generate an allow policy immediately?

restorecon can undo labels assigned with chcon. For ordinary web content, persist the mapping with semanage fcontext and apply it with restorecon. Rule out incorrect labels, paths and existing policy booleans before generating an allow policy.

Dedicated AMD EPYC 7543P server, from $447/month.

Dedicated Server Configurations: Start with Memory

AMD EPYC 7543P, with 128 GB or 256 GB DDR4-3200 ECC.