LinuxXXXI · Audit and Security LoggingAudit analysis
Audit record anatomy and attribution - answering who
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
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=SYSCALLis the spine: the call, its arguments, its result, and the full identity of the process.type=CWDis the working directory, needed to resolve any relative path in the PATH records.type=PATHappears once per path element the kernel touched.items=2in the SYSCALL record says how many to expect.nametype=says what happened to each:PARENTfor a directory that was traversed,NORMALfor a file that was accessed,CREATEfor one that was made,DELETEfor one removed.type=PROCTITLEis 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.
| Field | Meaning | What it proves |
|---|---|---|
uid | Real UID of the process | Which account the process is running as, now |
euid | Effective UID | Which account the kernel uses for permission checks |
suid | Saved UID | What it can switch back to |
fsuid | Filesystem UID | Which account is used for file access checks |
gid, egid, sgid, fsgid | The group equivalents | The same, for groups |
auid | Login UID | Which human logged in |
ses | Session ID | Which 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.
# ausearch -f /etc/sudoers -ts today -i | grep -E 'type=SYSCALL' | head -3type=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=identityIllustrative 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.
auidis only as trustworthy as the PAM stack that set it and the kernel policy that locks it. A host where root can changeauidhas 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 2immutable rule set and theCONFIG_CHANGErecords around it are for.
Knowledge check
Knowledge check · 5 questions
Q1. Three engineers all use `sudo -u deploy` to run releases. Which field identifies which of them ran a particular command?
Q2. What does `auid=4294967295` mean?
Q3. A single openat event produces several records. Which are part of the same event? Select all that apply.
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.
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.