131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package ebpfsource
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/cilium/ebpf/link"
|
|
"github.com/cilium/ebpf/perf"
|
|
"github.com/cilium/ebpf/rlimit"
|
|
|
|
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/events"
|
|
)
|
|
|
|
// ExecSource owns the Go-native exec tracepoint and perf reader. Load errors
|
|
// are returned to the caller so startup can log and fall back to polling.
|
|
type ExecSource struct {
|
|
objects execObjects
|
|
link link.Link
|
|
reader *perf.Reader
|
|
}
|
|
|
|
// execEventRaw mirrors struct exec_event in bpf/exec.bpf.c. Keeping the wire
|
|
// layout explicit makes changes to the kernel/user-space ABI reviewable.
|
|
type execEventRaw struct {
|
|
PID uint32
|
|
PPID uint32
|
|
UID uint32
|
|
ParentComm [16]int8
|
|
Filename [128]int8
|
|
Arg1 [128]int8
|
|
Arg2 [128]int8
|
|
}
|
|
|
|
// LostSamplesError reports perf-buffer pressure without making it impossible
|
|
// for the caller to continue reading later events.
|
|
type LostSamplesError struct {
|
|
Count uint64
|
|
}
|
|
|
|
func (e LostSamplesError) Error() string {
|
|
return fmt.Sprintf("exec perf buffer lost %d samples", e.Count)
|
|
}
|
|
|
|
func OpenExecSource() (*ExecSource, error) {
|
|
// Required by older kernels. On newer memcg-accounted kernels this may fail
|
|
// in an otherwise capable service sandbox, so attempt the load regardless
|
|
// and preserve both errors only when loading also fails.
|
|
memlockErr := rlimit.RemoveMemlock()
|
|
var objects execObjects
|
|
if err := loadExecObjects(&objects, nil); err != nil {
|
|
if memlockErr != nil {
|
|
return nil, fmt.Errorf("load exec BPF objects: %w (memlock adjustment: %v)", err, memlockErr)
|
|
}
|
|
return nil, fmt.Errorf("load exec BPF objects: %w", err)
|
|
}
|
|
attached, err := link.Tracepoint("syscalls", "sys_enter_execve", objects.TraceExecve, nil)
|
|
if err != nil {
|
|
objects.Close()
|
|
return nil, fmt.Errorf("attach exec tracepoint: %w", err)
|
|
}
|
|
reader, err := perf.NewReader(objects.Events, 64*4096)
|
|
if err != nil {
|
|
attached.Close()
|
|
objects.Close()
|
|
return nil, fmt.Errorf("open exec perf reader: %w", err)
|
|
}
|
|
return &ExecSource{objects: objects, link: attached, reader: reader}, nil
|
|
}
|
|
|
|
func (s *ExecSource) Read() (events.ExecEvent, error) {
|
|
record, err := s.reader.Read()
|
|
if err != nil {
|
|
return events.ExecEvent{}, err
|
|
}
|
|
if record.LostSamples != 0 {
|
|
return events.ExecEvent{}, LostSamplesError{Count: record.LostSamples}
|
|
}
|
|
return decodeExecSample(record.RawSample)
|
|
}
|
|
|
|
func decodeExecSample(sample []byte) (events.ExecEvent, error) {
|
|
var raw execEventRaw
|
|
if len(sample) != binary.Size(raw) {
|
|
return events.ExecEvent{}, fmt.Errorf("exec event size %d, want %d", len(sample), binary.Size(raw))
|
|
}
|
|
if err := binary.Read(bytes.NewReader(sample), binary.NativeEndian, &raw); err != nil {
|
|
return events.ExecEvent{}, fmt.Errorf("decode exec event: %w", err)
|
|
}
|
|
argv := make([]string, 0, 2)
|
|
if value := cString(raw.Arg1[:]); value != "" {
|
|
argv = append(argv, value)
|
|
}
|
|
if value := cString(raw.Arg2[:]); value != "" {
|
|
argv = append(argv, value)
|
|
}
|
|
return events.ExecEvent{
|
|
PID: int(raw.PID), PPID: int(raw.PPID), UID: int(raw.UID),
|
|
ParentComm: cString(raw.ParentComm[:]), Filename: cString(raw.Filename[:]),
|
|
Argv: argv,
|
|
}, nil
|
|
}
|
|
|
|
func (s *ExecSource) Close() error {
|
|
var failures []error
|
|
if s.reader != nil {
|
|
failures = append(failures, s.reader.Close())
|
|
}
|
|
if s.link != nil {
|
|
failures = append(failures, s.link.Close())
|
|
}
|
|
failures = append(failures, s.objects.Close())
|
|
return errors.Join(failures...)
|
|
}
|
|
|
|
func cString(raw []int8) string {
|
|
value := make([]byte, 0, len(raw))
|
|
for _, char := range raw {
|
|
if char == 0 {
|
|
break
|
|
}
|
|
value = append(value, byte(char))
|
|
}
|
|
return string(value)
|
|
}
|
|
|
|
var _ io.Closer = (*ExecSource)(nil)
|