enodia-sentinal/go-agent/internal/ebpfsource/syscall_test.go

256 lines
8.2 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package ebpfsource
import (
"bytes"
"encoding/binary"
"os"
"path/filepath"
"slices"
"testing"
"golang.org/x/sys/unix"
)
func TestDecodeSyscallSample(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: syscallMprotect,
Args: [6]uint64{0x1000, 0x2000, 0x6},
}
copyCString(raw.Comm[:], "dropper")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
event, err := decodeSyscallSample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if event.PID != 42 || event.PPID != 7 || event.UID != 1000 ||
event.Comm != "dropper" || event.Syscall != "mprotect" || event.Args[2] != 0x6 {
t.Fatalf("unexpected event: %#v", event)
}
}
func TestDecodeSyscallSampleRejectsUnknownIDAndSize(t *testing.T) {
if _, err := decodeSyscallSample([]byte{1}); err == nil {
t.Fatal("expected size error")
}
raw := syscallEventRaw{SyscallID: 99}
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
if _, err := decodeSyscallSample(sample.Bytes()); err == nil {
t.Fatal("expected unknown ID error")
}
}
func TestDecodeSecuritySampleProducesTypedHostTransition(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostSetuid,
Args: [6]uint64{0},
}
copyCString(raw.Comm[:], "python3")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Syscall != nil || securityEvent.Host == nil ||
securityEvent.Host.Event != "setuid" || securityEvent.Host.TargetUID != 0 ||
securityEvent.Host.TargetGID != -1 || securityEvent.Host.Comm != "python3" {
t.Fatalf("unexpected security event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesPermissionChange(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostFchownat,
Args: [6]uint64{0, 0},
}
copyCString(raw.Comm[:], "python3")
copyCString(raw.Text[:], "/etc/systemd/system/backdoor.service")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Host == nil || securityEvent.Host.Event != "chown" ||
securityEvent.Host.Path != "/etc/systemd/system/backdoor.service" ||
securityEvent.Host.TargetUID != 0 || securityEvent.Host.TargetGID != 0 {
t.Fatalf("unexpected security event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesCapabilities(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostCapset,
Args: [6]uint64{(1 << 2) | (1 << 21)},
}
copyCString(raw.Comm[:], "python3")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
want := []string{"CAP_DAC_READ_SEARCH", "CAP_SYS_ADMIN"}
if securityEvent.Host == nil || securityEvent.Host.Event != "capset" ||
!slices.Equal(securityEvent.Host.Capabilities, want) {
t.Fatalf("unexpected security event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesModuleLoad(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 0, SyscallID: hostFinitModule,
Args: [6]uint64{5},
}
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Host == nil || securityEvent.Host.Event != "module_load" || securityEvent.pathDirFD != 5 {
t.Fatalf("unexpected security event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesFileWrite(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostFileWrite,
Args: [6]uint64{5, 128},
}
copyCString(raw.Comm[:], "python3")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Host == nil || securityEvent.Host.Event != "file_write" ||
securityEvent.Host.Comm != "python3" || securityEvent.pathDirFD != 5 {
t.Fatalf("unexpected security event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesNetworkEvents(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostTCPConnect,
Args: [6]uint64{unix.AF_INET, 4444, 0, 5},
}
copyCString(raw.Comm[:], "python3")
address := []byte{8, 8, 8, 8}
raw.Args[2] = uint64(binary.NativeEndian.Uint32(address))
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Host == nil || securityEvent.Host.Event != "tcp_connect" ||
securityEvent.Host.PeerIP != "8.8.8.8" || securityEvent.Host.PeerPort != 4444 ||
securityEvent.pathDirFD != 5 {
t.Fatalf("unexpected network event: %#v", securityEvent)
}
}
func TestDecodeSecuritySampleProducesAccept(t *testing.T) {
raw := syscallEventRaw{
PID: 42, PPID: 7, UID: 1000, SyscallID: hostAccept4,
Args: [6]uint64{0, 0, 0, 6},
}
copyCString(raw.Comm[:], "python3")
var sample bytes.Buffer
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
t.Fatal(err)
}
securityEvent, err := decodeSecuritySample(sample.Bytes())
if err != nil {
t.Fatal(err)
}
if securityEvent.Host == nil || securityEvent.Host.Event != "accept" || securityEvent.pathDirFD != 6 {
t.Fatalf("unexpected accept event: %#v", securityEvent)
}
}
func TestMonitoredSyscallsUsesPlatformConstants(t *testing.T) {
numbers := monitoredSyscalls()
if len(numbers) != 26+len(legacyPermissionSyscalls())+len(legacyNetworkSyscalls()) ||
numbers[unix.SYS_MEMFD_CREATE] != syscallMemfdCreate ||
numbers[unix.SYS_MPROTECT] != syscallMprotect || numbers[unix.SYS_SETUID] != hostSetuid {
t.Fatalf("unexpected syscall map: %#v", numbers)
}
for _, number := range []uint64{
unix.SYS_WRITE, unix.SYS_WRITEV, unix.SYS_PWRITE64,
unix.SYS_PWRITEV, unix.SYS_PWRITEV2,
} {
if numbers[number] != hostFileWrite {
t.Fatalf("write syscall %d maps to %d", number, numbers[number])
}
}
}
func TestResolveProcessPath(t *testing.T) {
procRoot := t.TempDir()
processDir := filepath.Join(procRoot, "42")
if err := os.Mkdir(processDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink("/etc/systemd", filepath.Join(processDir, "cwd")); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(processDir, "fd"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink("/etc", filepath.Join(processDir, "fd", "5")); err != nil {
t.Fatal(err)
}
if err := os.Symlink("socket:[12345]", filepath.Join(processDir, "fd", "6")); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(procRoot, "net"), 0o755); err != nil {
t.Fatal(err)
}
tcpTable := "sl local remote st tx rx tr tm retr uid timeout inode\n0: 0100007F:1F90 08080808:115C 01 0 0 0 0 0 12345\n"
if err := os.WriteFile(filepath.Join(procRoot, "net", "tcp"), []byte(tcpTable), 0o600); err != nil {
t.Fatal(err)
}
if got := resolveProcessPath(procRoot, 42, -100, "system/agent.service"); got != "/etc/systemd/system/agent.service" {
t.Fatalf("resolved path=%q", got)
}
if got := resolveProcessPath(procRoot, 42, 5, "cron.d/agent"); got != "/etc/cron.d/agent" {
t.Fatalf("dirfd-resolved path=%q", got)
}
if got := resolveProcessFD(procRoot, 42, 5); got != "/etc" {
t.Fatalf("fd-resolved path=%q", got)
}
if !isTCPSocket(procRoot, 42, 6) || isTCPSocket(procRoot, 42, 5) {
t.Fatal("TCP socket classification mismatch")
}
local, peer, ok := lookupTCPSocket(procRoot, 42, 6)
if !ok || local.IP != "127.0.0.1" || local.Port != 8080 || peer.IP != "8.8.8.8" || peer.Port != 4444 {
t.Fatalf("unexpected endpoints: local=%#v peer=%#v ok=%v", local, peer, ok)
}
if got := resolveProcessPath(procRoot, 99, -100, "relative"); got != "relative" {
t.Fatalf("unresolved path=%q", got)
}
}