package monitor import ( "execguard/core/alert" "execguard/core/configure" "execguard/core/sys_database" "database/sql" "golang.org/x/sys/unix" "path/filepath" "encoding/binary" "sync" "fmt" "log" "os" "bytes" "time" "unsafe" ) const sizeofFanotifyEventMetadata = int(unsafe.Sizeof(unix.FanotifyEventMetadata{})) var ( config configure.Config initMode bool initFile string updateFile string migrateMode bool dbMutex sync.Mutex alertCache sync.Map ) func SetModes(mode bool, file string, update string, migrate bool) { initMode = mode initFile = file updateFile = update migrateMode = migrate } func SetGlobalConfig(c configure.Config) { config = c } func MonitorExecutions(db *sql.DB, log log.Logger, mailPath string) error { fd, err := unix.FanotifyInit(unix.FAN_CLOEXEC|unix.FAN_CLASS_CONTENT, unix.O_RDONLY|unix.O_LARGEFILE) if err != nil { return fmt.Errorf("fanotify init failed: %w", err) } defer unix.Close(fd) success := false for _, dir := range config.ProtectedDirs { if err := unix.FanotifyMark(fd, unix.FAN_MARK_ADD|unix.FAN_MARK_MOUNT, unix.FAN_OPEN_EXEC_PERM, unix.AT_FDCWD, dir); err != nil { log.Printf("Failed to mark %s: %v", dir, err) } else { success = true } } if !success { return fmt.Errorf("failed to mark any protected directories") } buf := make([]byte, 4096) defer func() { if r := recover(); r != nil { log.Printf("Recovered from panic in monitorExecutions: %v", r) } }() for { n, err := unix.Read(fd, buf) if err != nil { return fmt.Errorf("fanotify read failed: %w", err) } for offset := 0; offset < n; { if n-offset < sizeofFanotifyEventMetadata { break } meta := (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[offset])) if meta.Event_len == 0 { break } resp := unix.FanotifyResponse{Fd: meta.Fd, Response: unix.FAN_ALLOW} shouldClose := true if meta.Mask&unix.FAN_OPEN_EXEC_PERM != 0 { fdpath := fmt.Sprintf("/proc/self/fd/%d", meta.Fd) path, err := os.Readlink(fdpath) if err == nil { absPath, _ := filepath.Abs(path) absPath, _ = filepath.EvalSymlinks(absPath) if info, statErr := os.Stat(absPath); statErr == nil && info.Mode().IsRegular() && (info.Mode().Perm()&0111 != 0) { if initMode { go func(p string, fd int) { sys_database.AddToAllowed(db, log, p) unix.Close(fd) }(absPath, int(meta.Fd)) shouldClose = false } else { if !sys_database.IsAllowed(db, log, absPath) { log.Printf("Blocked execution attempt: %s", absPath) if _, seen := alertCache.LoadOrStore(absPath, struct{}{}); !seen { go alert.SendAlert(fmt.Sprintf("Unauthorized execution attempt blocked: %s", absPath), db, log) time.AfterFunc(10*time.Minute, func() { alertCache.Delete(absPath) }) } resp.Response = unix.FAN_DENY } } } } } var respBuf bytes.Buffer if err := binary.Write(&respBuf, binary.LittleEndian, resp); err != nil { log.Printf("Failed to encode fanotify response: %v", err) } else if _, err := unix.Write(fd, respBuf.Bytes()); err != nil { log.Printf("Fanotify response write error: %v", err) } if shouldClose { unix.Close(int(meta.Fd)) } offset += int(meta.Event_len) } } }