Skip to content

Commit 82bf332

Browse files
committed
seclog: fix kernel null termination and improve alignment and seq number
1 parent 94d87b6 commit 82bf332

3 files changed

Lines changed: 52 additions & 13 deletions

File tree

seclog/audit_linux.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,23 @@ func (aw *AuditWriter) Close() error {
101101
// The header layout follows struct nlmsghdr from
102102
// https://github.com/torvalds/linux/blob/254f49634ee16a731174d2ae34bc50bd5f45e731/include/uapi/linux/netlink.h#L45
103103
func (aw *AuditWriter) buildMessage(payload []byte) []byte {
104-
totalLen := syscall.SizeofNlMsghdr + uint32(len(payload))
105-
buf := make([]byte, nlmsgAlign(totalLen))
104+
// The kernel forcibly null-terminates the payload data. We include an extra
105+
// byte to avoid overwriting message data.
106+
totalLen := nlmsgAlign(syscall.SizeofNlMsghdr + uint32(len(payload)) + 1)
107+
buf := make([]byte, totalLen)
106108

107109
// Write header in native byte order (netlink uses host endianness).
110+
// [0:4] uint32 Length of message including header
111+
// [4:6] uint16 Message content type
112+
// [6:8] uint16 Flags
113+
// [8:12] uint32 Sequence number
114+
// [12:16] uint32 Sending process port ID
108115
// TODO: Upgrade from fire-and-forget to use NLM_F_ACK and handle
109116
// acknowledgments.
110117
arch.Endian().PutUint32(buf[0:4], totalLen)
111118
arch.Endian().PutUint16(buf[4:6], auditTrustedApp)
112-
arch.Endian().PutUint16(buf[6:8], syscall.NLM_F_REQUEST) // fire-and-forget, no ACK
113-
arch.Endian().PutUint32(buf[8:12], aw.seq.Add(1))
119+
arch.Endian().PutUint16(buf[6:8], syscall.NLM_F_REQUEST)
120+
arch.Endian().PutUint32(buf[8:12], aw.nextSeq())
114121
arch.Endian().PutUint32(buf[12:16], 0)
115122

116123
// Write payload.
@@ -122,3 +129,13 @@ func (aw *AuditWriter) buildMessage(payload []byte) []byte {
122129
func nlmsgAlign(size uint32) uint32 {
123130
return (size + 3) &^ 3
124131
}
132+
133+
// nextSeq returns the next non-zero sequence number, skipping zero on
134+
// wrap to allow unambiguous ACK matching (mirrors audit-userspace).
135+
func (aw *AuditWriter) nextSeq() uint32 {
136+
s := aw.seq.Add(1)
137+
if s == 0 {
138+
s = aw.seq.Add(1)
139+
}
140+
return s
141+
}

seclog/audit_linux_test.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ func (s *AuditSuite) TestBuildMessageHeaderLayout(c *C) {
137137
payload := []byte("hello")
138138
msg := seclog.AuditWriterBuildMessage(aw, payload)
139139

140-
// Total length: 16 (header) + 5 (payload) = 21, aligned to 24.
140+
// Total length: NLMSG_SPACE(5 + 1) = align(22) = 24.
141141
c.Assert(len(msg), Equals, 24)
142142

143143
// nlmsghdr fields in native byte order.
144144
totalLen := arch.Endian().Uint32(msg[0:4])
145-
c.Check(totalLen, Equals, uint32(21))
145+
c.Check(totalLen, Equals, uint32(24))
146146

147147
msgType := arch.Endian().Uint16(msg[4:6])
148148
c.Check(msgType, Equals, uint16(seclog.AuditTrustedApp))
@@ -159,8 +159,9 @@ func (s *AuditSuite) TestBuildMessageHeaderLayout(c *C) {
159159
// Payload follows header.
160160
c.Check(string(msg[syscall.SizeofNlMsghdr:syscall.SizeofNlMsghdr+5]), Equals, "hello")
161161

162-
// Padding bytes after payload should be zero.
162+
// NUL byte for kernel null-termination.
163163
c.Check(msg[21], Equals, byte(0))
164+
// Padding bytes should be zero.
164165
c.Check(msg[22], Equals, byte(0))
165166
c.Check(msg[23], Equals, byte(0))
166167
}
@@ -181,27 +182,44 @@ func (s *AuditSuite) TestBuildMessageSequenceIncrements(c *C) {
181182
c.Check(seq3, Equals, uint32(3))
182183
}
183184

185+
func (s *AuditSuite) TestBuildMessageSequenceSkipsZero(c *C) {
186+
aw := &seclog.AuditWriter{}
187+
188+
// Set sequence just before wraparound.
189+
seclog.AuditWriterSetSeq(aw, ^uint32(0)) // math.MaxUint32
190+
191+
msg1 := seclog.AuditWriterBuildMessage(aw, []byte("x"))
192+
msg2 := seclog.AuditWriterBuildMessage(aw, []byte("y"))
193+
194+
seq1 := arch.Endian().Uint32(msg1[8:12])
195+
seq2 := arch.Endian().Uint32(msg2[8:12])
196+
197+
// Should skip 0 and go to 1, then 2.
198+
c.Check(seq1, Equals, uint32(1))
199+
c.Check(seq2, Equals, uint32(2))
200+
}
201+
184202
func (s *AuditSuite) TestBuildMessageAlignedPayload(c *C) {
185203
aw := &seclog.AuditWriter{}
186204

187-
// Payload of exactly 4 bytes: total = 20 which is already aligned.
205+
// Payload of exactly 4 bytes: NLMSG_SPACE(4 + 1) = align(21) = 24.
188206
msg := seclog.AuditWriterBuildMessage(aw, []byte("abcd"))
189-
c.Check(len(msg), Equals, 20)
207+
c.Check(len(msg), Equals, 24)
190208

191209
totalLen := arch.Endian().Uint32(msg[0:4])
192-
c.Check(totalLen, Equals, uint32(20))
210+
c.Check(totalLen, Equals, uint32(24))
193211
}
194212

195213
func (s *AuditSuite) TestBuildMessageEmptyPayload(c *C) {
196214
aw := &seclog.AuditWriter{}
197215

198216
msg := seclog.AuditWriterBuildMessage(aw, []byte{})
199217

200-
// 16-byte header, already aligned.
201-
c.Check(len(msg), Equals, 16)
218+
// NLMSG_SPACE(0 + 1) = align(17) = 20.
219+
c.Check(len(msg), Equals, 20)
202220

203221
totalLen := arch.Endian().Uint32(msg[0:4])
204-
c.Check(totalLen, Equals, uint32(16))
222+
c.Check(totalLen, Equals, uint32(20))
205223
}
206224

207225
func (s *AuditSuite) TestNlmsgAlignAlreadyAligned(c *C) {

seclog/export_audit_linux_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func AuditWriterBuildMessage(aw *AuditWriter, payload []byte) []byte {
3333
return aw.buildMessage(payload)
3434
}
3535

36+
func AuditWriterSetSeq(aw *AuditWriter, val uint32) {
37+
aw.seq.Store(val)
38+
}
39+
3640
func MockSyscallOps(ops syscallOps) (restore func()) {
3741
return testutil.Mock(&sys, ops)
3842
}

0 commit comments

Comments
 (0)