Skip to content

auditd: fix V0 dispatch truncating last 16 bytes of events - #544

Merged
stevegrubb merged 1 commit into
linux-audit:masterfrom
Cropi:fix-dispatch-v1-nlmsg-truncation
Aug 13, 2026
Merged

auditd: fix V0 dispatch truncating last 16 bytes of events#544
stevegrubb merged 1 commit into
linux-audit:masterfrom
Cropi:fix-dispatch-v1-nlmsg-truncation

Conversation

@Cropi

@Cropi Cropi commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

When log_format=RAW and any audisp plugin (e.g. audisp-syslog) is active,
the last 16 bytes of every kernel audit event are silently dropped before
reaching the plugin pipe. A USER record ending in terminal=? res=success'
shows up in syslog as termina with the rest gone.

Root cause

Commit ba8ba4f introduced a branch for embedded netlink replies
(rep->nlh == &rep->msg.nlh) that subtracted NLMSG_HDRLEN (16) from
nlmsg_len before copying the payload, assuming the kernel follows the
standard netlink convention where nlmsg_len = header + payload.

The kernel audit subsystem doesn't follow that convention.
In kernel/audit.c, __audit_log_end() sets:

nlh->nlmsg_len = skb->len - NLMSG_HDRLEN;

So nlmsg_len is already the payload size. Subtracting NLMSG_HDRLEN again
cuts 16 real bytes off every kernel audit event.

The kernel documents this explicitly in kauditd_send_multicast_skb():

"non-standard mods are made to the skb by the original kaudit unicast
socket send routine. The existing auditd daemon assumes this breakage.
Fixing this would require co-ordinating a change in the established
protocol between the kaudit kernel subsystem and the auditd userspace
code."

The multicast path fixes nlmsg_len back to skb->len for non-auditd
readers, but the unicast path that auditd reads doesn't.

Fix

Remove the rep->nlh == &rep->msg.nlh branch distinction. The kernel always
sets nlmsg_len to the payload size, so use it directly with no subtraction.
Keep the bounds check against the buffer size.

Tests

  • test_netlink_payload_length: updated to use payload-only nlmsg_len,
    matching how the kernel actually sets it
  • test_invalid_netlink_length: removed the now-impossible low-bound case
    (< NLMSG_HDRLEN)
  • test_realistic_user_event: new -- full USER record payload round-trip
    through V0 dispatch
  • test_v2_protocol_uses_rep_len: new -- VER2 path uses rep->len, not
    nlmsg_len; confirms no regression there

How to reproduce

  1. Set log_format=RAW and enable audisp-syslog
  2. Generate events: auditctl -m "test message"
  3. Compare audit.log vs syslog output -- syslog entries will be exactly
    16 bytes shorter every time

When log_format=RAW and audisp-syslog is active, the last 16 characters
of every kernel audit event were missing from syslog output. For example
a USER event ending in "terminal=? res=success'" would appear as
"termina" with the rest cut off.

The problem was found by comparing the audit.log output (which was
complete) with the syslog output (which was truncated). Tracing with
strace showed that auditd's write to the plugin pipe was 16 bytes
shorter than the write to the log file.

Commit ba8ba4f subtracted NLMSG_HDRLEN (16) from nlmsg_len before
copying the payload to the dispatcher, assuming nlmsg_len follows the
standard netlink convention where it includes the 16-byte header.

The kernel audit subsystem does not follow that convention. In
audit_log_end() (kernel/audit.c) the kernel sets:

    nlh->nlmsg_len = skb->len - NLMSG_HDRLEN;

This means nlmsg_len is already the payload size, not the total message
size. Subtracting NLMSG_HDRLEN again removes 16 bytes of real event
data.

The kernel documents this as a non-standard choice. The multicast path
(kauditd_send_multicast_skb) makes a copy and restores the standard
nlmsg_len = skb->len for non-auditd listeners.

A netlink probe confirmed this on kernel 6.12: for every event,
recvfrom returns nlmsg_len + 16 bytes, meaning nlmsg_len equals the
payload size and the extra 16 bytes are the header.

Fix: use nlmsg_len directly as the payload size for both kernel and
synthetic events, and keep a bounds check against the buffer size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Cropi <alakatos@redhat.com>

@sergio-correia sergio-correia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! The kernel audit subsystem's non-standard nlmsg_len behavior makes this code deceptively tricky, as it is easy to assume standard netlink conventions apply, but
__audit_log_end() deliberately sets nlmsg_len = skb->len - NLMSG_HDRLEN on the unicast path. The old subtraction was a double-subtract bug.

@stevegrubb

Copy link
Copy Markdown
Contributor

Thanks.

@stevegrubb
stevegrubb merged commit 2c6409d into linux-audit:master Aug 13, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants