fix(go.d): validate custom binary path (#20761)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Ilya Mashchenko committed
Aug 5, 2025 at 15:47 UTC
b97b0d418175e56f60480bc139b6caea56b39721
6 files changed
+115
src/go/plugin/go.d/collector/ap/init.go
+6
@@ -9,6 +9,8 @@ import (
9
"os"
10
"os/exec"
11
"strings"
12
+
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/pathvalidate"
14
)
15
16
func (c *Collector) validateConfig() error {
@@ -33,6 +35,10 @@ func (c *Collector) initIwExec() (iwBinary, error) {
35
return nil, err
36
}
37
38
+ if err := pathvalidate.ValidateBinaryPath(binPath); err != nil {
39
+ return nil, err
40
+ }
41
+
42
iw := newIwExec(binPath, c.Timeout.Duration())
43
44
return iw, nil
src/go/plugin/go.d/collector/nvidia_smi/init.go
+6
@@ -6,6 +6,8 @@ import (
6
"fmt"
7
"os"
8
"os/exec"
9
+
10
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/pathvalidate"
11
)
12
13
func (c *Collector) initNvidiaSmiExec() (nvidiaSmiBinary, error) {
@@ -18,5 +20,9 @@ func (c *Collector) initNvidiaSmiExec() (nvidiaSmiBinary, error) {
20
binPath = path
21
}
22
23
+ if err := pathvalidate.ValidateBinaryPath(binPath); err != nil {
24
+ return nil, err
25
+ }
26
+
27
return newNvidiaSmiBinary(binPath, c.Config, c.Logger)
28
}
src/go/plugin/go.d/collector/postfix/init.go
+6
@@ -7,6 +7,8 @@ import (
7
"os"
8
"os/exec"
9
"strings"
10
+
11
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/pathvalidate"
12
)
13
14
func (c *Collector) validateConfig() error {
@@ -31,6 +33,10 @@ func (c *Collector) initPostqueueExec() (postqueueBinary, error) {
33
return nil, err
34
}
35
36
+ if err := pathvalidate.ValidateBinaryPath(binPath); err != nil {
37
+ return nil, err
38
+ }
39
+
40
pq := newPostqueueExec(binPath, c.Timeout.Duration())
41
pq.Logger = c.Logger
42
src/go/plugin/go.d/collector/zfspool/init.go
+6
@@ -9,6 +9,8 @@ import (
9
"os"
10
"os/exec"
11
"strings"
12
+
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/pathvalidate"
14
)
15
16
func (c *Collector) validateConfig() error {
@@ -33,6 +35,10 @@ func (c *Collector) initZPoolCLIExec() (zpoolCli, error) {
35
return nil, err
36
}
37
38
+ if err := pathvalidate.ValidateBinaryPath(binPath); err != nil {
39
+ return nil, err
40
+ }
41
+
42
zpoolExec := newZpoolCLIExec(binPath, c.Timeout.Duration())
43
zpoolExec.Logger = c.Logger
44
src/go/plugin/go.d/pkg/pathvalidate/validate_stub.go
new
+11
@@ -0,0 +1,11 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+//go:build !unix
4
+
5
+package pathvalidate
6
+
7
+// ValidateBinaryPath checks if a binary path is secure for execution.
8
+// This is a stub implementation for non-Unix platforms.
9
+func ValidateBinaryPath(path string) error {
10
+ return nil
11
+}
src/go/plugin/go.d/pkg/pathvalidate/validate_unix.go
new
+80
@@ -0,0 +1,80 @@
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) 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 parent directory
60
+ dir := filepath.Dir(absPath)
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
+ return nil
80
+}