forked from elastic/go-libaudit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
audit.go
662 lines (567 loc) · 19.1 KB
/
audit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// +build linux
package libaudit
import (
"bytes"
"encoding/binary"
"io"
"os"
"sync"
"syscall"
"time"
"unsafe"
"github.com/pkg/errors"
"github.com/elastic/go-libaudit/v2/auparse"
"github.com/elastic/go-libaudit/v2/sys"
)
var (
byteOrder = sys.GetEndian()
)
const (
// AuditMessageMaxLength is the maximum length of an audit message (data
// portion of a NetlinkMessage).
// https://github.com/linux-audit/audit-userspace/blob/990aa27ccd02f9743c4f4049887ab89678ab362a/lib/libaudit.h#L435
AuditMessageMaxLength = 8970
)
// Audit command and control message types.
const (
AuditGet uint16 = iota + 1000
AuditSet
)
// Netlink groups.
const (
NetlinkGroupNone = iota // Group 0 not used
NetlinkGroupReadLog // "best effort" read only socket
)
// WaitMode is a flag to control the behavior of methods that abstract
// asynchronous communication for the caller.
type WaitMode uint8
const (
// WaitForReply mode causes a call to wait for a reply message.
WaitForReply WaitMode = iota + 1
// NoWait mode causes a call to return without waiting for a reply message.
NoWait
)
// FailureMode defines the kernel's behavior on critical errors.
type FailureMode uint32
const (
// SilentOnFailure ignores errors.
SilentOnFailure FailureMode = 0
// LogOnFailure logs errors using printk.
LogOnFailure
// PanicOnFailure causes a kernel panic on error.
PanicOnFailure
)
// AuditClient is a client for communicating with the Linux kernels audit
// interface over netlink.
type AuditClient struct {
Netlink NetlinkSendReceiver
pendingAcks []uint32
clearPIDOnClose bool
closeOnce sync.Once
}
// NewMulticastAuditClient creates a new AuditClient that binds to the multicast
// socket subscribes to the audit group. The process should have the
// CAP_AUDIT_READ capability to use this. This audit client should not be used
// for command and control. The resp parameter is optional. If provided resp
// will receive a copy of all data read from the netlink socket. This is useful
// for debugging purposes.
func NewMulticastAuditClient(resp io.Writer) (*AuditClient, error) {
return newAuditClient(NetlinkGroupReadLog, resp)
}
// NewAuditClient creates a new AuditClient. The resp parameter is optional. If
// provided resp will receive a copy of all data read from the netlink socket.
// This is useful for debugging purposes.
func NewAuditClient(resp io.Writer) (*AuditClient, error) {
return newAuditClient(NetlinkGroupNone, resp)
}
func newAuditClient(netlinkGroups uint32, resp io.Writer) (*AuditClient, error) {
buf := make([]byte, syscall.NLMSG_HDRLEN+AuditMessageMaxLength)
netlink, err := NewNetlinkClient(syscall.NETLINK_AUDIT, netlinkGroups, buf, resp)
if err != nil {
switch err {
case syscall.EINVAL, syscall.EPROTONOSUPPORT, syscall.EAFNOSUPPORT:
return nil, errors.Wrap(err, "audit not supported by kernel")
default:
return nil, errors.Wrap(err, "failed to open audit netlink socket")
}
}
return &AuditClient{Netlink: netlink}, nil
}
// GetStatus returns the current status of the kernel's audit subsystem.
func (c *AuditClient) GetStatus() (*AuditStatus, error) {
// Send AUDIT_GET message to the kernel.
seq, err := c.GetStatusAsync(true)
if err != nil {
return nil, errors.Wrap(err, "failed sending request")
}
// Get the ack message which is a NLMSG_ERROR type whose error code is SUCCESS.
ack, err := c.getReply(seq)
if err != nil {
return nil, errors.Wrap(err, "failed to get audit status ack")
}
if ack.Header.Type != syscall.NLMSG_ERROR {
return nil, errors.Errorf("unexpected ACK to GET, got type=%d", ack.Header.Type)
}
if err = ParseNetlinkError(ack.Data); err != nil {
return nil, err
}
// Get the audit_status reply message. It has the same sequence number as
// our original request.
reply, err := c.getReply(seq)
if err != nil {
return nil, errors.Wrap(err, "failed to get audit status reply")
}
if reply.Header.Type != AuditGet {
return nil, errors.Errorf("unexpected reply to GET, got type=%d", reply.Header.Type)
}
replyStatus := &AuditStatus{}
if err := replyStatus.FromWireFormat(reply.Data); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal reply")
}
return replyStatus, nil
}
// GetStatusAsync sends a request for the status of the kernel's audit subsystem
// and returns without waiting for a response.
func (c *AuditClient) GetStatusAsync(requireACK bool) (seq uint32, err error) {
var flags uint16 = syscall.NLM_F_REQUEST
if requireACK {
flags |= syscall.NLM_F_ACK
}
msg := syscall.NetlinkMessage{
Header: syscall.NlMsghdr{
Type: AuditGet,
Flags: flags,
},
Data: nil,
}
// Send AUDIT_GET message to the kernel.
return c.Netlink.Send(msg)
}
// GetRules returns a list of audit rules (in binary format).
func (c *AuditClient) GetRules() ([][]byte, error) {
msg := syscall.NetlinkMessage{
Header: syscall.NlMsghdr{
Type: uint16(auparse.AUDIT_LIST_RULES),
Flags: syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
},
Data: nil,
}
// Send AUDIT_LIST_RULES message to the kernel.
seq, err := c.Netlink.Send(msg)
if err != nil {
return nil, errors.Wrap(err, "failed sending request")
}
// Get the ack message which is a NLMSG_ERROR type whose error code is SUCCESS.
ack, err := c.getReply(seq)
if err != nil {
return nil, errors.Wrap(err, "failed to get audit ACK")
}
if ack.Header.Type != syscall.NLMSG_ERROR {
return nil, errors.Errorf("unexpected ACK to LIST_RULES, got type=%d", ack.Header.Type)
}
if err = ParseNetlinkError(ack.Data); err != nil {
return nil, err
}
var rules [][]byte
for {
reply, err := c.getReply(seq)
if err != nil {
return nil, errors.Wrap(err, "failed receive rule data")
}
if reply.Header.Type == syscall.NLMSG_DONE {
break
}
if reply.Header.Type != uint16(auparse.AUDIT_LIST_RULES) {
return nil, errors.Errorf("unexpected message type %d while receiving rules", reply.Header.Type)
}
rule := make([]byte, len(reply.Data))
copy(rule, reply.Data)
rules = append(rules, rule)
}
return rules, nil
}
// DeleteRules deletes all rules.
func (c *AuditClient) DeleteRules() (int, error) {
rules, err := c.GetRules()
if err != nil {
return 0, err
}
for i, rule := range rules {
if err := c.DeleteRule(rule); err != nil {
return 0, errors.Wrapf(err, "failed to delete rule %v of %v", i, len(rules))
}
}
return len(rules), nil
}
// DeleteRule deletes the given rule (specified in binary format).
func (c *AuditClient) DeleteRule(rule []byte) error {
msg := syscall.NetlinkMessage{
Header: syscall.NlMsghdr{
Type: uint16(auparse.AUDIT_DEL_RULE),
Flags: syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
},
Data: rule,
}
// Send AUDIT_DEL_RULE message to the kernel.
seq, err := c.Netlink.Send(msg)
if err != nil {
return errors.Wrapf(err, "failed sending delete request")
}
_, err = c.getReply(seq)
if err != nil {
return errors.Wrap(err, "failed to get ACK to rule delete request")
}
return nil
}
// AddRule adds the given rule to the kernel's audit rule list.
func (c *AuditClient) AddRule(rule []byte) error {
msg := syscall.NetlinkMessage{
Header: syscall.NlMsghdr{
Type: uint16(auparse.AUDIT_ADD_RULE),
Flags: syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
},
Data: rule,
}
// Send AUDIT_ADD_RULE message to the kernel.
seq, err := c.Netlink.Send(msg)
if err != nil {
return errors.Wrapf(err, "failed sending delete request")
}
ack, err := c.getReply(seq)
if err != nil {
return errors.Wrap(err, "failed to get ACK to rule delete request")
}
if ack.Header.Type != syscall.NLMSG_ERROR {
return errors.Errorf("unexpected ACK to AUDIT_ADD_RULE, got type=%d", ack.Header.Type)
}
if err = ParseNetlinkError(ack.Data); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EEXIST {
return errors.New("rule exists")
}
return errors.Wrap(err, "error adding audit rule")
}
return nil
}
// SetPID sends a netlink message to the kernel telling it the PID of the
// client that should receive audit messages.
// https://github.com/linux-audit/audit-userspace/blob/990aa27ccd02f9743c4f4049887ab89678ab362a/lib/libaudit.c#L432-L464
func (c *AuditClient) SetPID(wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusPID,
PID: uint32(os.Getpid()),
}
c.clearPIDOnClose = true
return c.set(status, wm)
}
// SetRateLimit will set the maximum number of messages that the kernel will
// send per second. This can be used to throttle the rate if systems become
// unresponsive. Of course the trade off is that events will be dropped.
// The default value is 0, meaning no limit.
func (c *AuditClient) SetRateLimit(perSecondLimit uint32, wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusRateLimit,
RateLimit: perSecondLimit,
}
return c.set(status, wm)
}
// SetBacklogLimit sets the queue length for audit events awaiting transfer to
// the audit daemon. The default value is 64 which can potentially be overrun by
// bursts of activity. When the backlog limit is reached, the kernel consults
// the failure_flag to see what action to take.
func (c *AuditClient) SetBacklogLimit(limit uint32, wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusBacklogLimit,
BacklogLimit: limit,
}
return c.set(status, wm)
}
// SetEnabled is used to control whether or not the audit system is
// active. When the audit system is enabled (enabled set to 1), every syscall
// will pass through the audit system to collect information and potentially
// trigger an event.
func (c *AuditClient) SetEnabled(enabled bool, wm WaitMode) error {
var e uint32
if enabled {
e = 1
}
status := AuditStatus{
Mask: AuditStatusEnabled,
Enabled: e,
}
return c.set(status, wm)
}
// SetImmutable is used to lock the audit configuration so that it can't be
// changed. Locking the configuration is intended to be the last command you
// issue. Any attempt to change the configuration in this mode will be
// audited and denied. The configuration can only be changed by rebooting the
// machine.
func (c *AuditClient) SetImmutable(wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusEnabled,
Enabled: 2,
}
return c.set(status, wm)
}
// SetFailure sets the action that the kernel will perform when the backlog
// limit is reached or when it encounters an error and cannot proceed.
func (c *AuditClient) SetFailure(fm FailureMode, wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusFailure,
Failure: uint32(fm),
}
return c.set(status, wm)
}
// SetBacklogWaitTime sets the time that the kernel will wait for a buffer in
// the backlog queue to become available before dropping the event. This has
// the side-effect of blocking the thread that was invoking the syscall being
// audited.
// waitTime is measured in jiffies, default in kernel is 60*HZ (60 seconds).
// A value of 0 disables the wait time completely, causing the failure mode
// to be invoked immediately when the backlog queue is full.
// Attempting to set a negative value or a value 10x larger than the default
// will fail with EINVAL.
func (c *AuditClient) SetBacklogWaitTime(waitTime int32, wm WaitMode) error {
status := AuditStatus{
Mask: AuditStatusBacklogWaitTime,
BacklogWaitTime: uint32(waitTime),
}
return c.set(status, wm)
}
// RawAuditMessage is a raw audit message received from the kernel.
type RawAuditMessage struct {
Type auparse.AuditMessageType
Data []byte // RawData is backed by the read buffer so make a copy.
}
// Receive reads an audit message from the netlink socket. If you are going to
// use the returned message then you should make a copy of the raw data before
// calling receive again because the raw data is backed by the read buffer.
func (c *AuditClient) Receive(nonBlocking bool) (*RawAuditMessage, error) {
msgs, err := c.Netlink.Receive(nonBlocking, parseNetlinkAuditMessage)
if err != nil {
return nil, err
}
// ParseNetlinkAuditMessage always return a slice with 1 item.
return &RawAuditMessage{
Type: auparse.AuditMessageType(msgs[0].Header.Type),
Data: msgs[0].Data,
}, nil
}
// Close closes the AuditClient and frees any associated resources. If the audit
// PID was set it will be cleared (set 0). Any invocations beyond the first
// become no-ops.
func (c *AuditClient) Close() error {
var err error
// Only unregister and close the socket once.
c.closeOnce.Do(func() {
if c.clearPIDOnClose {
// Unregister from the kernel for a clean exit.
status := AuditStatus{
Mask: AuditStatusPID,
PID: 0,
}
c.set(status, NoWait)
}
err = c.Netlink.Close()
})
return err
}
// WaitForPendingACKs waits for acknowledgements messages for operations
// executed with a WaitMode of NoWait. Such ACK messages are expected in the
// same order as the operations have been performed. If it receives an error,
// it is returned and no further ACKs are processed.
func (c *AuditClient) WaitForPendingACKs() error {
for _, reqId := range c.pendingAcks {
ack, err := c.getReply(reqId)
if err != nil {
return err
}
if ack.Header.Type != syscall.NLMSG_ERROR {
return errors.Errorf("unexpected ACK to SET, type=%d", ack.Header.Type)
}
if err := ParseNetlinkError(ack.Data); err != nil {
return err
}
}
return nil
}
// getReply reads from the netlink socket and find the message with the given
// sequence number. The caller should inspect the returned message's type,
// flags, and error code.
func (c *AuditClient) getReply(seq uint32) (*syscall.NetlinkMessage, error) {
var msg syscall.NetlinkMessage
var msgs []syscall.NetlinkMessage
var err error
for receiveMore := true; receiveMore; {
// Retry the non-blocking read multiple times until a response is received.
for i := 0; i < 10; i++ {
msgs, err = c.Netlink.Receive(true, parseNetlinkAuditMessage)
if err != nil {
switch err {
case syscall.EINTR:
continue
case syscall.EAGAIN:
time.Sleep(50 * time.Millisecond)
continue
default:
return nil, errors.Wrap(err, "error receiving audit reply")
}
}
break
}
if len(msgs) == 0 {
return nil, errors.New("no reply received")
}
msg = msgs[0]
// Skip audit event that sneak between the request/response
receiveMore = msg.Header.Seq == 0 && seq != 0
}
if msg.Header.Seq != seq {
return nil, errors.Errorf("unexpected sequence number for reply (expected %v but got %v)",
seq, msg.Header.Seq)
}
return &msg, nil
}
func (c *AuditClient) set(status AuditStatus, mode WaitMode) error {
msg := syscall.NetlinkMessage{
Header: syscall.NlMsghdr{
Type: AuditSet,
Flags: syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
},
Data: status.toWireFormat(),
}
seq, err := c.Netlink.Send(msg)
if err != nil {
return errors.Wrap(err, "failed sending request")
}
if mode == NoWait {
c.storePendingAck(seq)
return nil
}
ack, err := c.getReply(seq)
if err != nil {
return err
}
if ack.Header.Type != syscall.NLMSG_ERROR {
return errors.Errorf("unexpected ACK to SET, type=%d", ack.Header.Type)
}
if err := ParseNetlinkError(ack.Data); err != nil {
return err
}
return nil
}
// parseNetlinkAuditMessage parses an audit message received from the kernel.
// Audit messages differ significantly from typical netlink messages in that
// a single message is sent and the length in the header should be ignored.
// This is why syscall.ParseNetlinkMessage is not used.
func parseNetlinkAuditMessage(buf []byte) ([]syscall.NetlinkMessage, error) {
if len(buf) < syscall.NLMSG_HDRLEN {
return nil, syscall.EINVAL
}
r := bytes.NewReader(buf)
m := syscall.NetlinkMessage{}
if err := binary.Read(r, byteOrder, &m.Header); err != nil {
return nil, err
}
m.Data = buf[syscall.NLMSG_HDRLEN:]
return []syscall.NetlinkMessage{m}, nil
}
// audit_status message
// AuditStatusMask is a bitmask used to convey the fields used in AuditStatus.
// https://github.com/linux-audit/audit-kernel/blob/v4.7/include/uapi/linux/audit.h#L318-L325
type AuditStatusMask uint32
// Mask types for AuditStatus.
const (
AuditStatusEnabled AuditStatusMask = 1 << iota
AuditStatusFailure
AuditStatusPID
AuditStatusRateLimit
AuditStatusBacklogLimit
AuditStatusBacklogWaitTime
AuditStatusLost
)
// AuditFeatureBitmap is a mask used to indicate which features are currently
// supported by the audit subsystem.
type AuditFeatureBitmap uint32
const (
AuditFeatureBitmapBacklogLimit = 1 << iota
AuditFeatureBitmapBacklogWaitTime
AuditFeatureBitmapExecutablePath
AuditFeatureBitmapExcludeExtend
AuditFeatureBitmapSessionIDFilter
AuditFeatureBitmapLostReset
)
var sizeofAuditStatus = int(unsafe.Sizeof(AuditStatus{}))
// AuditStatus is a status message and command and control message exchanged
// between the kernel and user-space.
// https://github.com/linux-audit/audit-kernel/blob/v4.7/include/uapi/linux/audit.h#L413-L427
type AuditStatus struct {
Mask AuditStatusMask // Bit mask for valid entries.
Enabled uint32 // 1 = enabled, 0 = disabled, 2 = immutable
Failure uint32 // Failure-to-log action.
PID uint32 // PID of auditd process.
RateLimit uint32 // Messages rate limit (per second).
BacklogLimit uint32 // Waiting messages limit.
Lost uint32 // Messages lost.
Backlog uint32 // Messages waiting in queue.
FeatureBitmap uint32 // Bitmap of kernel audit features (previously to 3.19 it was the audit api version number).
BacklogWaitTime uint32 // Message queue wait timeout.
}
func (s AuditStatus) toWireFormat() []byte {
buf := bytes.NewBuffer(make([]byte, sizeofAuditStatus))
buf.Reset()
if err := binary.Write(buf, byteOrder, s); err != nil {
// This never returns an error.
panic(err)
}
return buf.Bytes()
}
// FromWireFormat unmarshals the given buffer to an AuditStatus object. Due to
// changes in the audit_status struct in the kernel source this method does
// not return an error if the buffer is smaller than the sizeof our AuditStatus
// struct.
func (s *AuditStatus) FromWireFormat(buf []byte) error {
fields := []interface{}{
&s.Mask,
&s.Enabled,
&s.Failure,
&s.PID,
&s.RateLimit,
&s.BacklogLimit,
&s.Lost,
&s.Backlog,
&s.FeatureBitmap,
&s.BacklogWaitTime,
}
if len(buf) == 0 {
return io.EOF
}
r := bytes.NewReader(buf)
for _, f := range fields {
if r.Len() == 0 {
return nil
}
if err := binary.Read(r, byteOrder, f); err != nil {
return err
}
}
return nil
}
func (c *AuditClient) storePendingAck(requestID uint32) {
c.pendingAcks = append(c.pendingAcks, requestID)
}