Skip to main content
RunBook Academy

LinuxXXXI · Audit and Security LoggingAudit analysis

Audit record anatomy and attribution - answering who

Advanced⏱ ~16 minauditdausearch

What you'll learn

  • Decompose a single audit event into its constituent records
  • Distinguish uid, euid, fsuid and auid and say what each proves
  • Attribute an action taken through a shared account to a person
  • Recognise and diagnose an unset loginuid
  • Reconstruct a session from a file change back to a login source

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11

Not yet marked complete on this device.

The previous lessons in this part set up auditd, wrote rules, and queried the log. This one is about the moment the query returns a result and somebody asks the only question that matters: who did this?

The answer is in the record, and it is not in the field most people read.

One action, several records

An audit event is not a line. It is a group of records sharing one event identifier, emitted together because they describe different aspects of the same syscall.

type=SYSCALL msg=audit(1786224851.412:4521): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=560c1f2a4b30 a2=241 a3=1b6 items=2 ppid=18422 pid=18455 auid=1001 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts2 ses=37 comm="vim" exe="/usr/bin/vim" key="identity"
type=CWD msg=audit(1786224851.412:4521): cwd="/root"
type=PATH msg=audit(1786224851.412:4521): item=0 name="/etc/" inode=131074 dev=fd:00 mode=040755 ouid=0 ogid=0 nametype=PARENT
type=PATH msg=audit(1786224851.412:4521): item=1 name="/etc/sudoers.tmp" inode=133891 dev=fd:00 mode=0100600 ouid=0 ogid=0 nametype=CREATE
type=PROCTITLE msg=audit(1786224851.412:4521): proctitle=76696D002F6574632F7375646F657273

Reading it:

  • msg=audit(1786224851.412:4521) is the event identifier: epoch seconds, milliseconds, then a serial number that is unique for this boot. Every record with the same identifier belongs to one event.
  • type=SYSCALL is the spine: the call, its arguments, its result, and the full identity of the process.
  • type=CWD is the working directory, needed to resolve any relative path in the PATH records.
  • type=PATH appears once per path element the kernel touched. items=2 in the SYSCALL record says how many to expect. nametype= says what happened to each: PARENT for a directory that was traversed, NORMAL for a file that was accessed, CREATE for one that was made, DELETE for one removed.
  • type=PROCTITLE is the command line, hex-encoded with NUL separators.

Retrieve the whole event by identifier and let ausearch decode it:

sudo ausearch -a 4521 -i

-i is not optional in practice. It resolves UIDs to names, syscall=257 to openat, arch=c000003e to x86_64, and the PROCTITLE hex to vim /etc/sudoers. Without it you are reading a memory dump.

The identity fields

The SYSCALL record carries seven identity numbers and they do not mean the same thing.

FieldMeaningWhat it proves
uidReal UID of the processWhich account the process is running as, now
euidEffective UIDWhich account the kernel uses for permission checks
suidSaved UIDWhat it can switch back to
fsuidFilesystem UIDWhich account is used for file access checks
gid, egid, sgid, fsgidThe group equivalentsThe same, for groups
auidLogin UIDWhich human logged in
sesSession IDWhich login session this belongs to

In the record above, uid=0 and auid=1001. The process is running as root. A person whose UID is 1001 logged in and got there. Those are separate facts and only the second one is an accountability claim.

auid is the accountability field

auid - the loginuid - is set once, by pam_loginuid in the PAM stack, when a session is created. From that moment it is inherited by every child process and, on a properly configured host, the kernel refuses to change it.

That immutability is the entire point. su, sudo, setuid, a shell inside a shell, a background job that outlives the session - none of them alter auid. It follows the process tree from the login that started it.

# Your own login uid and session, right now
cat /proc/self/loginuid
cat /proc/self/sessionid

# Everything one person did today, regardless of which
# accounts they became
sudo ausearch -ul 1001 -ts today -i

This is what makes shared accounts auditable. Three engineers who all sudo -u deploy to run a release produce records with uid=deploy for all three, which is useless. The auid names the individual.

Read-only / Safeuid=root says nothing; auid=jmartin is the answer
# ausearch -f /etc/sudoers -ts today -i | grep -E 'type=SYSCALL' | head -3
type=SYSCALL msg=audit(11/08/26 09:14:11.412:4521) : arch=x86_64 syscall=openat success=yes exit=3 ppid=18422 pid=18455 auid=jmartin uid=root gid=root euid=root suid=root fsuid=root tty=pts2 ses=37 comm=vim exe=/usr/bin/vim key=identity

Illustrative output

Reconstructing a session

The investigation shape is: a change was found, work outwards from it to a person and then to a source address.

1. Find the change.

sudo ausearch -f /etc/sudoers -ts 09:00 -te 10:00 -i

2. Pull the whole event, not the matching record.

sudo ausearch -a 4521 -i

The SYSCALL record gives you auid, ses, pid, comm and exe. The PATH records give you what was touched. PROCTITLE gives you the command as typed.

3. Widen to the session. Every record from that login carries the same ses=, which makes the session the natural unit of investigation:

sudo grep -h 'ses=37' /var/log/audit/audit.log | wc -l
sudo ausearch -ul 1001 -ts today -i | grep -E 'type=(USER_CMD|EXECVE|USER_LOGIN)'

4. Find the login that opened the session. The USER_LOGIN and USER_START records carry the source address:

sudo ausearch -m USER_LOGIN -ts today -i | grep -E 'ses=37|addr='
type=USER_LOGIN msg=audit(11/08/26 08:52:03.117:4402) : pid=18400 uid=root
  auid=jmartin ses=37 msg='op=login id=jmartin exe=/usr/sbin/sshd
  hostname=198.51.100.24 addr=198.51.100.24 terminal=/dev/pts/2 res=success'

5. Corroborate outside auditd. The authentication lesson in this part covers journalctl -u ssh and /var/log/auth.log. Two independent sources agreeing is a materially stronger claim than one, and if they disagree that is itself the finding.

What a record does not prove

Be precise about the limits when you write this up:

  • It proves a syscall was made by a process with those credentials. It does not prove intent, and it does not prove the person was at the keyboard.
  • auid is only as trustworthy as the PAM stack that set it and the kernel policy that locks it. A host where root can change auid has an audit trail root can forge.
  • Anyone who can write to /var/log/audit/ can alter history. This is why the central logging lesson exists: the copy that is evidence is the one already off the host.
  • A rule that was not loaded records nothing. The absence of an event is not evidence the action did not happen unless you can show the rule was active at the time - which is what the -e 2 immutable rule set and the CONFIG_CHANGE records around it are for.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Three engineers all use `sudo -u deploy` to run releases. Which field identifies which of them ran a particular command?

  2. Q2. What does `auid=4294967295` mean?

  3. Q3. A single openat event produces several records. Which are part of the same event? Select all that apply.

  4. Q4. The absence of an audit event for an action is only evidence the action did not happen if you can show the relevant rule was loaded at the time.

  5. Q5. Why quote the full event identifier in an incident report rather than paraphrasing the finding?

Passing score: 75%. Answers are checked in this browser.