// SPDX-License-Identifier: GPL-3.0-or-later // Package provenance answers "is this file shipped by an installed package?". // Ownership raises confidence that a binary is legitimate; it never proves // safety, so callers use it to suppress or rank alerts, not to delete evidence. package provenance import ( "context" "os/exec" "strings" "sync" "time" ) var ( toolOnce sync.Once tool string cacheMu sync.Mutex cache = map[string]string{} seen = map[string]bool{} ) func packageTool() string { toolOnce.Do(func() { for _, name := range []string{"pacman", "dpkg", "rpm"} { if _, err := exec.LookPath(name); err == nil { tool = name break } } }) return tool } func queryOwner(name, path string) string { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() var output []byte var err error switch name { case "pacman": output, err = exec.CommandContext(ctx, "pacman", "-Qo", path).Output() case "dpkg": output, err = exec.CommandContext(ctx, "dpkg", "-S", path).Output() case "rpm": output, err = exec.CommandContext(ctx, "rpm", "-qf", path).Output() } if err != nil { return "" } return parseOwner(name, string(output)) } func parseOwner(name, output string) string { output = strings.TrimSpace(output) switch name { case "pacman": _, owner, found := strings.Cut(output, " is owned by ") if found { return strings.TrimSpace(owner) } case "dpkg": owner, _, found := strings.Cut(output, ":") if found { return strings.TrimSpace(owner) } case "rpm": if output != "" && !strings.Contains(strings.ToLower(output), "not owned") { return output } } return "" } // PackageOwner returns the owning installed-package description, or an empty // string. The command is bounded and runs without holding the cache lock. func PackageOwner(path string) string { path = strings.ReplaceAll(path, " (deleted)", "") if path == "" || path == "(deleted)" { return "" } cacheMu.Lock() if seen[path] { owner := cache[path] cacheMu.Unlock() return owner } cacheMu.Unlock() owner := "" if name := packageTool(); name != "" { owner = queryOwner(name, path) } cacheMu.Lock() cache[path], seen[path] = owner, true cacheMu.Unlock() return owner } // IsPackageOwned reports whether path belongs to an installed package. // Results are cached because package ownership is stable within a sweep run. func IsPackageOwned(path string) bool { return PackageOwner(path) != "" }