// 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) } }