@cryptotaxi247 / netdata-1 / commits / b97b0d418

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 +}