elastic / elastic/beats

[bug-hunter] auditd coalescing misattributes node on interleaved events

Open
#53,161 1 comment 0 reactions 0 assignees View on GitHub
needs_team
Dominant language
Go
Stars
12.7k
Forks
5k
Avg merge
2d 2h
Merged PRs (30d)
364

Description

## Impact
When audit logs from multiple nodes are interleaved (common with forwarded/aggregated streams), the coalescing parser can assign the wrong `auditd.data.node` to an event. This misattributes events to the wrong host, which can break host-level detection, triage, and auditing workflows.

## Reproduction Steps
1. Add this test to `libbeat/reader/auditd/coalesce_test.go` (new test function):

```go
func TestCoalescingInterleavedNodeIsolation(t *testing.T) {
lines := [][]byte{
[]byte(`node=host-a type=SYSCALL msg=audit(1626700000.000:610): arch=c000003e syscall=59 success=yes exit=0 a0=1 a1=2 a2=3 a3=4 items=0 ppid=100 pid=611 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="a" exe="/usr/bin/a" key=(null)`),
[]byte(`node=host-b type=SYSCALL msg=audit(1626700000.001:611): arch=c000003e syscall=59 success=yes exit=0 a0=1 a1=2 a2=3 a3=4 items=0 ppid=100 pid=612 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="b" exe="/usr/bin/b" key=(null)`),
[]byte(`type=EOE msg=audit(1626700000.000:610):`),
[]byte(`type=EOE msg=audit(1626700000.001:611):`),
}
r := &testReader{messages: lines}
p := NewParser(r, coalesceConfig(), logptest.NewTestingLogger(t, t.Name()))

got := map[uint32]string{}
for i := 0; i < 2; i++ {
msg, err := p.Next()
if err != nil {
t.Fatalf("Next() returned error: %v", err)
}

seqValue, _ := msg.Fields.GetValue("auditd.sequence")
seq, ok := seqValue.(uint32)
if !ok {
t.Fatalf("auditd.sequence = %v (%T); want uint32", seqValue, seqValue)
}

nodeValue, _ := msg.Fields.GetValue("auditd.data.node")
node, ok := nodeValue.(string)
if !ok {
t.Fatalf("auditd.data.node = %v (%T); want string", nodeValue, nodeValue)
}
got[seq] = node
}

if got[610] != "host-a" {
t.Errorf("sequence 610 node = %q; want %q", got[610], "host-a")
}
if got[611] != "host-b" {
t.Errorf("sequence 611 node = %q; want %q", got[611], "host-b")
}

_, err := p.Next()
if !errors.Is(err, io.EOF) {
t.Errorf("third Next() = %v; want io.EOF", err)
}
}
```

2. Run:

```bash
go test ./libbeat/reader/auditd -run TestCoalescingInterleavedNodeIsolation -count=1
```

## Expected vs Actual
**Expected:** Sequence `610` keeps `auditd.data.node="host-a"` and sequence `611` keeps `auditd.data.node="host-b"`.

**Actual:** Sequence `610` receives `"host-b"` (the most recently seen node), proving node leakage across in-flight sequences.

Command output:

```text
--- FAIL: TestCoalescingInterleavedNodeIsolation (0.00s)
coalesce_test.go:340: sequence 610 node = "host-b"; want "host-a"
FAIL
FAIL github.com/elastic/beats/v7/libbeat/reader/auditd 0.008s
FAIL
```

## Failing Test
The full failing test is included above in **Reproduction Steps**.

## Evidence
- Global parser state stores the most recently seen node, not per-sequence node:
- `libbeat/reader/auditd/coalesce.go:47` — `node string // most recently seen node= value`
- Node value is overwritten while reading lines, regardless of sequence:
- `libbeat/reader/auditd/coalesce.go:165-168`
- Emitted event uses global node field:
- `libbeat/reader/auditd/coalesce.go:230-236`
- `git blame` shows this logic was introduced in commit `f4497618bcf` (`libbeat/reader/auditd: add multi-record coalescing mode`).

---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/34592438640)

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Sep 18, 2026, 11:21 AM UTC

Contributor guide

Open the contributing guide

Research direction

Start with libbeat/reader/auditd/coalesce.go, especially the node state at line 47, node handling at lines 165-168, and event emission at lines 230-236. Add the supplied regression test to libbeat/reader/auditd/coalesce_test.go and run go test ./libbeat/reader/auditd -run TestCoalescingInterleavedNodeIsolation -count=1. Done means sequences 610 and 611 retain host-a and host-b respectively, and the following call returns io.EOF.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.