70 lines
1.7 KiB
Go
70 lines
1.7 KiB
Go
// 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 (
|
|
mu sync.Mutex
|
|
tool *string
|
|
cache = map[string]bool{}
|
|
)
|
|
|
|
func packageTool() string {
|
|
if tool != nil {
|
|
return *tool
|
|
}
|
|
found := ""
|
|
for _, name := range []string{"pacman", "dpkg", "rpm"} {
|
|
if _, err := exec.LookPath(name); err == nil {
|
|
found = name
|
|
break
|
|
}
|
|
}
|
|
tool = &found
|
|
return found
|
|
}
|
|
|
|
func query(name, path string) bool {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
switch name {
|
|
case "pacman":
|
|
output, err := exec.CommandContext(ctx, "pacman", "-Qo", path).Output()
|
|
return err == nil && strings.Contains(string(output), "owned by")
|
|
case "dpkg":
|
|
output, err := exec.CommandContext(ctx, "dpkg", "-S", path).Output()
|
|
return err == nil && strings.Contains(string(output), ":")
|
|
case "rpm":
|
|
output, err := exec.CommandContext(ctx, "rpm", "-qf", path).Output()
|
|
return err == nil && !strings.Contains(string(output), "not owned")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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 {
|
|
if path == "" || path == "(deleted)" {
|
|
return false
|
|
}
|
|
path = strings.ReplaceAll(path, " (deleted)", "")
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if owned, seen := cache[path]; seen {
|
|
return owned
|
|
}
|
|
name := packageTool()
|
|
owned := name != "" && query(name, path)
|
|
cache[path] = owned
|
|
return owned
|
|
}
|