enodia-sentinal/go-agent/internal/system/suid.go

86 lines
2.1 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package system
import (
"os"
"path/filepath"
"sort"
"syscall"
)
// ScanSUIDBinaries mirrors Python's deliberately conservative SUID/SGID walk.
// The primary root never crosses a filesystem boundary, while explicitly
// configured writable mounts are walked separately because tmpfs-backed paths
// are exactly where an attacker is likely to drop a privileged binary.
//
// Individual unreadable entries are ignored. Host filesystems are inherently
// racy and permission-sensitive, so one denied subtree must not discard the
// useful portion of a scan.
func ScanSUIDBinaries(root string, extraDirs []string) []string {
if root == "" {
root = "/"
}
result := make(map[string]bool)
rootInfo, err := os.Lstat(root)
if err == nil {
device, ok := deviceID(rootInfo)
if ok {
walkSUID(root, &device, result)
}
}
for _, directory := range extraDirs {
info, err := os.Lstat(directory)
if err != nil || !info.IsDir() {
continue
}
walkSUID(directory, nil, result)
}
paths := make([]string, 0, len(result))
for path := range result {
paths = append(paths, path)
}
sort.Strings(paths)
return paths
}
func walkSUID(root string, requiredDevice *uint64, result map[string]bool) {
stack := []string{root}
for len(stack) > 0 {
directory := stack[len(stack)-1]
stack = stack[:len(stack)-1]
entries, err := os.ReadDir(directory)
if err != nil {
continue
}
for _, entry := range entries {
path := filepath.Join(directory, entry.Name())
info, err := os.Lstat(path)
if err != nil {
continue
}
if info.IsDir() {
if requiredDevice != nil {
device, ok := deviceID(info)
if !ok || device != *requiredDevice {
continue
}
}
stack = append(stack, path)
continue
}
mode := info.Mode()
if mode.IsRegular() && (mode&os.ModeSetuid != 0 || mode&os.ModeSetgid != 0) {
result[path] = true
}
}
}
}
func deviceID(info os.FileInfo) (uint64, bool) {
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return 0, false
}
return uint64(stat.Dev), true
}