Files
fim-ebpf/main.go
Harshavardhan Musanalli ad4f459de5 Push initial working version
2025-08-18 08:15:33 +02:00

206 lines
4.5 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
"os"
"os/signal"
"os/user"
"syscall"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/perf"
"github.com/cilium/ebpf/rlimit"
"gopkg.in/yaml.v3"
)
type Config struct {
MonitoredFiles []string `yaml:"monitored_files"`
IgnoreActions []string `yaml:"ignore_actions"`
IgnoreUsers []string `yaml:"ignore_users"`
}
type event struct {
Pid uint32
Uid uint32
Euid uint32
Loginuid uint32
FilenameHash uint32
Comm [16]byte
Filename [256]byte
Flags uint32
}
// simpleHash implements the same DJB2-like hash as the eBPF C code.
func simpleHash(filename string) uint32 {
hash := uint32(5381)
for _, c := range []byte(filename) {
hash = ((hash << 5) + hash) + uint32(c) // hash * 33 + c
}
return hash
}
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
return &cfg, nil
}
func parseUint32(s string) (uint32, error) {
var n uint32
_, err := fmt.Sscanf(s, "%d", &n)
return n, err
}
func isReadOperation(flags uint32) bool {
return (flags & (syscall.O_WRONLY | syscall.O_RDWR)) == 0
}
func main() {
cfg, err := loadConfig("config.yaml")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal(err)
}
objs := tracepointObjects{}
if err := loadTracepointObjects(&objs, nil); err != nil {
log.Fatalf("Loading objects: %v", err)
}
defer objs.Close()
// Populate filename hashes
for _, file := range cfg.MonitoredFiles {
hash := simpleHash(file)
val := uint32(1) // Just a marker
if err := objs.TargetHashesMap.Put(hash, val); err != nil {
log.Fatalf("Failed to add hash for %s: %v", file, err)
}
}
// Populate ignored UIDs
for _, username := range cfg.IgnoreUsers {
u, err := user.Lookup(username)
if err != nil {
log.Printf("Warning: user %s not found", username)
continue
}
uid, err := parseUint32(u.Uid)
if err != nil {
log.Fatalf("Failed to parse UID for %s: %v", u.Uid, err)
}
if err := objs.IgnoreUidsMap.Put(uid, uint32(1)); err != nil {
log.Printf("Failed to ignore UID %d: %v", uid, err)
}
}
tp, err := link.Tracepoint("syscalls", "sys_enter_openat", objs.TraceOpenat, nil)
if err != nil {
log.Fatal(err)
}
defer tp.Close()
rd, err := perf.NewReader(objs.Events, os.Getpagesize())
if err != nil {
log.Fatal(err)
}
defer rd.Close()
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
for {
record, err := rd.Read()
if err != nil {
if errors.Is(err, perf.ErrClosed) {
return
}
log.Printf("Reading event: %v", err)
continue
}
var e event
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &e); err != nil {
log.Printf("Decoding event: %v", err)
continue
}
// Final verification in userspace
matched := false
currentFile := string(bytes.TrimRight(e.Filename[:], "\x00"))
for _, file := range cfg.MonitoredFiles {
if currentFile == file {
matched = true
break
}
}
if !matched {
continue
}
// Skip ignored actions
if isReadOperation(e.Flags) && contains(cfg.IgnoreActions, "read") {
continue
}
// Get username from UID
currentUsername := "unknown"
if e.Uid == 0 {
currentUsername = "root"
} else if user, err := user.LookupId(fmt.Sprintf("%d", e.Uid)); err == nil {
currentUsername = user.Username
}
// With this more robust version:
userInfo := fmt.Sprintf("%d (%s)", e.Uid, currentUsername)
if e.Loginuid != 0 && e.Uid != e.Loginuid {
loginUser, err := user.LookupId(fmt.Sprintf("%d", e.Loginuid))
loginUsername := "unknown"
if err == nil {
loginUsername = loginUser.Username
}
userInfo = fmt.Sprintf("%d (%s) [Login: %d (%s)]",
e.Uid, currentUsername, e.Loginuid, loginUsername)
}
log.Printf("Event: PID=%d UID=%d (%s) CMD=%s FILE=%s FLAGS=%08x",
e.Pid,
e.Uid,
userInfo,
string(bytes.TrimRight(e.Comm[:], "\x00")),
string(bytes.TrimRight(e.Filename[:], "\x00")),
e.Flags,
)
}
}()
log.Println("Monitoring started. Ctrl+C to exit.")
<-sig
log.Println("Shutting down...")
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}