Skip to content

LogsExecutionAnti-forensics

AppArmor and SELinux Denials: Linux MAC Audit Logs

SELinux AVC and AppArmor DENIED records on Linux: where they are logged, how to read them, and how they expose web shells, exploits and disabled enforcement.

Location
/var/log/audit/audit.log (with auditd); otherwise kern.log, messages or the journal
Proves
Which confined process tried to open, write or execute what and was blocked (or would have been in permissive/complain mode), and when enforcement was switched off
Timestamps
msg=audit(epoch.msec:serial) in UTC; kernel log copies carry syslog or journal time
Access
root (audit.log mode 0600; kernel log readable by adm or root depending on distro)
Retention
Follows auditd rotation (upstream 8 MiB x 5) or syslog/journal retention
Collection
UAC, Velociraptor, cp -a, ausearch --raw
  • ausearchCLI · built into Linux
  • aureportCLI · built into Linux
  • audit2whyCLI · built into Linux
  • sealertCLI · built into Linux
  • sestatusCLI · built into Linux
  • semoduleCLI · built into Linux
  • aa-statusCLI · built into Linux
  • journalctlCLI · built into Linux
  • grep / zgrepCLI · built into Linux

What it is

SELinux and AppArmor are the two mandatory access control (MAC) systems shipped by mainstream distributions. Both confine services to a policy and both report violations through the kernel audit subsystem. For an investigator, the denial records are a free intrusion detection log: a web server process trying to execute a file from its upload directory, a database writing to /tmp, or cupsd reading /etc/shadow are all recorded even when nobody configured auditd rules.

Which one you will find depends on the family: SELinux is enabled and enforcing by default on RHEL, Rocky, Alma and Fedora. AppArmor is enabled by default on Ubuntu, Debian (since 10) and SUSE releases up to SLES 15 and older openSUSE installs; newer openSUSE Tumbleweed installs and SLES 16 default to SELinux. Arch enables neither by default.

Where it lives

ItemPathNotes
Denials with auditd/var/log/audit/audit.logtype=AVC for both SELinux and AppArmor, type=USER_AVC from user-space managers such as D-Bus or systemd
Denials without auditd/var/log/kern.log or /var/log/syslog (Debian/Ubuntu), /var/log/messages (RHEL, SUSE), journal (_TRANSPORT=audit or kernel)Shown as audit: type=1400 audit(...)
SELinux mode/etc/selinux/config (`SELINUX=enforcingpermissive
SELinux custom modules/var/lib/selinux/<policy>/active/modules/<priority>/ (priority 400 for semodule -i)Locally loaded policy
setroubleshootjournal entries "SELinux is preventing ..."; database under /var/lib/setroubleshoot/RHEL/Fedora desktops and some servers
AppArmor profiles/etc/apparmor.d/, disabled profiles linked in /etc/apparmor.d/disable/, cache in /var/cache/apparmor/Profile flags complain or enforce

What it proves

  • A specific process (comm, exe, pid) attempted a specific operation on a specific object and was denied, or allowed only because the domain or profile was permissive/complain.
  • The security context or profile involved, which tells you which service was compromised: httpd_t, postgresql_t, profile /usr/sbin/nginx.
  • Enforcement changes: setenforce 0 (MAC_STATUS), policy loads (MAC_POLICY_LOAD), boolean changes (MAC_CONFIG_CHANGE), AppArmor profile replacement or removal (apparmor="STATUS").
  • It does not show allowed actions in enforcing mode, and dontaudit rules suppress many expected denials.

Key fields

SELinux AVC from a web shell trying to run a dropped binary:

type=AVC msg=audit(1789874047.512:8812): avc:  denied  { execute } for  pid=4302 comm="sh" name="x" dev="dm-0" ino=393219 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:httpd_sys_rw_content_t:s0 tclass=file permissive=0

AppArmor denial for the same kind of event:

type=AVC msg=audit(1789874047.512:231): apparmor="DENIED" operation="exec" class="file" profile="/usr/sbin/nginx" name="/var/www/html/uploads/x" pid=4302 comm="sh" requested_mask="x" denied_mask="x" fsuid=33 ouid=33
FieldMeaning
denied { ... } / apparmor="DENIED"Operation was refused; apparmor="ALLOWED" means complain mode logged but allowed it
permissive=1SELinux domain or system was permissive: the action succeeded
scontext / profileConfined subject: the compromised service
tcontext, tclass / name, classTarget object and its type
comm, exe, pidProcess name, executable, PID
name, ino, devFile name (last component for SELinux) and inode; resolve the full path with the inode on the image
requested_mask, denied_maskAppArmor permissions (r, w, x, m, c, d)

Timestamps

The msg=audit(sec.msec:serial) stamp is Unix epoch UTC, as for all audit records. When records reach the kernel log instead, the same stamp is embedded in the message and the syslog line adds its own local time; prefer the embedded epoch.

Retention

In audit.log, denials share the space with every other audit event and roll over with auditd's num_logs and max_log_file. In the kernel log or journal they follow syslog rotation or journal size limits. Noisy denial loops (a misconfigured service retrying every second) can push older evidence out quickly; the kernel rate-limits audit messages with printk and audit backlog limits, so check for "audit: rate limit exceeded" or "callbacks suppressed" lines.

Collection

# Dead box
tar -C /mnt/evidence -cpf /cases/2026-017/mac.tar var/log/audit var/log/kern.log* var/log/syslog* \
  var/log/messages* etc/selinux etc/apparmor.d var/lib/selinux var/lib/setroubleshoot 2>/dev/null

# Live
sestatus; getenforce; semodule -lfull | awk '$1!=100' > custom_modules.txt   # SELinux: non-default priorities
aa-status > aa_status.txt                                                    # AppArmor: profiles and modes

UAC collects /etc and /var/log, which covers the configuration and the logs. Velociraptor can read /var/log/audit/audit.log with its audit parsing artifacts or grep the kernel log.

Parsing

A=/cases/2026-017/var/log/audit
ausearch -if "$A" -m AVC,USER_AVC -i                                # all denials, interpreted
ausearch -if "$A" -m AVC -c httpd -i                                 # only those from comm=httpd
ausearch -if "$A" -m MAC_STATUS,MAC_POLICY_LOAD,MAC_CONFIG_CHANGE -i # enforcement tampering
aureport -if "$A" --avc --summary
ausearch -if "$A" -m AVC --raw | audit2why                          # why SELinux denied it

# AppArmor without auditd (Debian/Ubuntu)
zgrep -h 'apparmor="DENIED"\|apparmor="STATUS"' /cases/2026-017/var/log/kern.log* | sort
journalctl -D /cases/2026-017/var/log/journal -g 'apparmor=|avc:' -o short-iso-precise   # all boots

sealert -a audit.log from setroubleshoot produces human-readable explanations but needs the policy of the original host.

Investigator tips

  • Denials from httpd_t, nginx, php-fpm, postgresql_t or mysqld_t against execute, execute_no_trans, name_connect or writes to /tmp are classic post-exploitation signals. Pivot to the matching time in web server logs and the web root.
  • permissive=1 on a production RHEL host, or a profile moved to complain mode, means the control that would have stopped the attacker was off. Find out when: look for MAC_STATUS records, edits to /etc/selinux/config, or new links in /etc/apparmor.d/disable/.
  • A custom SELinux module at priority 400 that nobody on the admin team recognizes can grant an attacker's process rights silently. Extract it with semodule -E <name> on a live host or read the CIL from /var/lib/selinux/.
  • semodule -DB disables dontaudit rules and reveals hidden denials during live monitoring; remember to re-enable with semodule -B.
  • Absence of any AVC on a host where SELinux is supposedly enforcing is suspicious in itself: check that auditd and the kernel log were running.

See also