master
go 85 lines 2.48 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build unix
4
5 package pathvalidate
6
7 import (
8 "fmt"
9 "os"
10 "path/filepath"
11 "syscall"
12 )
13
14 // ValidateBinaryPath checks if a binary path is secure for execution.
15 // It verifies ownership, permissions, and directory security.
16 func ValidateBinaryPath(path string) (string, error) {
17 // Step 1: Resolve full symlink path
18 resolvedPath, err := filepath.EvalSymlinks(path)
19 if err != nil {
20 return "", fmt.Errorf("failed to resolve symlink for %s: %w", path, err)
21 }
22
23 // Step 2: Resolve to absolute path
24 absPath, err := filepath.Abs(resolvedPath)
25 if err != nil {
26 return "", fmt.Errorf("failed to resolve absolute path for %s: %w", resolvedPath, err)
27 }
28
29 // Step 3: Stat the resolved file
30 fileInfo, err := os.Stat(absPath)
31 if err != nil {
32 return "", fmt.Errorf("binary stat error for %s: %w", absPath, err)
33 }
34
35 // Step 4: Check that it is a regular file
36 if !fileInfo.Mode().IsRegular() {
37 return "", fmt.Errorf("binary at %s must be a regular file, not %s", absPath, fileInfo.Mode().String())
38 }
39
40 // Step 5: Check file ownership and permissions
41 fileStat, ok := fileInfo.Sys().(*syscall.Stat_t)
42 if !ok {
43 return "", fmt.Errorf("unable to get file stat information for %s", absPath)
44 }
45 if fileStat.Uid != 0 {
46 return "", fmt.Errorf("binary at %s must be owned by root (current uid: %d)", absPath, fileStat.Uid)
47 }
48
49 if perm := fileInfo.Mode().Perm(); perm&0022 != 0 {
50 return "", fmt.Errorf("binary at %s must not be writable by group/others (current permissions: %s / %04o)",
51 absPath, fileInfo.Mode().String(), perm)
52 }
53
54 // Step 6: Check executable bit
55 if fileInfo.Mode().Perm()&0111 == 0 {
56 return "", fmt.Errorf("binary at %s must be executable", absPath)
57 }
58
59 // Step 7: Check all ancestor directories up to and including root
60 for dir := filepath.Dir(absPath); ; dir = filepath.Dir(dir) {
61 dirInfo, err := os.Stat(dir)
62 if err != nil {
63 return "", fmt.Errorf("directory stat error for %s: %w", dir, err)
64 }
65
66 dirStat, ok := dirInfo.Sys().(*syscall.Stat_t)
67 if !ok {
68 return "", fmt.Errorf("unable to get directory stat information for %s", dir)
69 }
70 if dirStat.Uid != 0 {
71 return "", fmt.Errorf("directory %s must be owned by root (current uid: %d)", dir, dirStat.Uid)
72 }
73
74 if perm := dirInfo.Mode().Perm(); perm&0022 != 0 {
75 return "", fmt.Errorf("directory %s must not be writable by group/others (current permissions: %s / %04o)",
76 dir, dirInfo.Mode().String(), perm)
77 }
78
79 if dir == filepath.Dir(dir) {
80 break
81 }
82 }
83
84 return absPath, nil
85 }