86 lines
2.1 KiB
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
|
|
}
|