"Permission Denied" — But Why?
When you see a "Permission Denied" error and the standard Unix permissions look correct, the LSM is almost certainly the cause. The kernel records every LSM denial through the Linux Audit subsystem, which writes structured log entries to /var/log/audit/audit.log on systems with auditd running, or to the kernel ring buffer (accessible via journalctl -k) on systems without it.
SELinux denials are called AVC denials — AVC stands for Access Vector Cache, the kernel data structure that caches policy decisions for performance. A raw AVC denial looks intimidating but follows a consistent structure:
type=AVC msg=audit(1700000000.123:456): avc: denied { read } for pid=1234 comm="httpd" name="index.html" dev="sda1" ino=98765 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:user_home_t:s0 tclass=file permissive=0
Breaking this down field by field:
| Field | Value | Meaning |
|---|---|---|
avc: denied |
— | This was blocked (not just logged) |
{ read } |
read | The specific permission that was denied |
comm="httpd" |
httpd | The process name that attempted the action |
pid=1234 |
1234 | The process ID |
name="index.html" |
index.html | The target object name |
scontext= |
httpd_t:s0 |
Source context — the process's label |
tcontext= |
user_home_t:s0 |
Target context — the file's label |
tclass=file |
file | The object class (file, dir, tcp_socket, etc.) |
permissive=0 |
0 | 0 = enforcing (blocked), 1 = permissive (logged only) |
Practical diagnostic tools: ausearch queries the audit log for AVC events — recent ones, or filtered down to a specific process — without you having to grep raw log lines by hand. The sealert tool (from the setroubleshoot-server package on RHEL systems) is particularly valuable — it translates raw AVC denials into plain English explanations and suggests specific restorecon or semanage commands to fix them.
For AppArmor, denials appear in /var/log/syslog or journalctl with the prefix apparmor=:
kernel: [12345.678] audit: type=1400 audit(1700000000.123:456): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/etc/shadow" pid=5678 comm="nginx" requested_mask="r" denied_mask="r" fsuid=33 ouid=0
The fields are analogous: profile is the confining profile, name is the target path, requested_mask is what was attempted, denied_mask is what was blocked.