aboutsummaryrefslogtreecommitdiff
path: root/src/os/exec
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/exec')
-rw-r--r--src/os/exec/dot_test.go44
-rw-r--r--src/os/exec/exec.go10
-rw-r--r--src/os/exec/lp_plan9.go4
-rw-r--r--src/os/exec/lp_unix.go4
-rw-r--r--src/os/exec/lp_windows.go8
5 files changed, 70 insertions, 0 deletions
diff --git a/src/os/exec/dot_test.go b/src/os/exec/dot_test.go
index 1bf0d9c760..b95639e6c8 100644
--- a/src/os/exec/dot_test.go
+++ b/src/os/exec/dot_test.go
@@ -177,4 +177,48 @@ func TestLookPath(t *testing.T) {
}
}
})
+
+ checker := func(test string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+ t.Logf("PATH=%s", os.Getenv("PATH"))
+ p, err := LookPath(test)
+ if err == nil {
+ t.Errorf("%q: error expected, got nil", test)
+ }
+ if p != "" {
+ t.Errorf("%q: path returned should be \"\". Got %q", test, p)
+ }
+ }
+ }
+
+ // Reference behavior for the next test
+ t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
+ t.Run("empty", checker(""))
+ t.Run("dot", checker("."))
+ t.Run("dotdot1", checker("abc/.."))
+ t.Run("dotdot2", checker(".."))
+ })
+
+ // Test the behavior when PATH contains an executable file which is not a directory
+ t.Run(pathVar+"=exe", func(t *testing.T) {
+ // Inject an executable file (not a directory) in PATH.
+ // Use our own binary os.Args[0].
+ t.Setenv(pathVar, testenv.Executable(t))
+ t.Run("empty", checker(""))
+ t.Run("dot", checker("."))
+ t.Run("dotdot1", checker("abc/.."))
+ t.Run("dotdot2", checker(".."))
+ })
+
+ // Test the behavior when PATH contains an executable file which is not a directory
+ t.Run(pathVar+"=exe/xx", func(t *testing.T) {
+ // Inject an executable file (not a directory) in PATH.
+ // Use our own binary os.Args[0].
+ t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
+ t.Run("empty", checker(""))
+ t.Run("dot", checker("."))
+ t.Run("dotdot1", checker("abc/.."))
+ t.Run("dotdot2", checker(".."))
+ })
}
diff --git a/src/os/exec/exec.go b/src/os/exec/exec.go
index 91a6831b04..38354a5244 100644
--- a/src/os/exec/exec.go
+++ b/src/os/exec/exec.go
@@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
// to test whether a returned error err is due to this condition.
var ErrDot = errors.New("cannot run executable found relative to current directory")
+
+// validateLookPath excludes paths that can't be valid
+// executable names. See issue #74466 and CVE-2025-47906.
+func validateLookPath(s string) error {
+ switch s {
+ case "", ".", "..":
+ return ErrNotFound
+ }
+ return nil
+}
diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go
index 87359b3551..0430af9eef 100644
--- a/src/os/exec/lp_plan9.go
+++ b/src/os/exec/lp_plan9.go
@@ -36,6 +36,10 @@ func findExecutable(file string) error {
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
func LookPath(file string) (string, error) {
+ if err := validateLookPath(file); err != nil {
+ return "", &Error{file, err}
+ }
+
// skip the path lookup for these prefixes
skip := []string{"/", "#", "./", "../"}
diff --git a/src/os/exec/lp_unix.go b/src/os/exec/lp_unix.go
index 8617d45e98..e5fddbafe2 100644
--- a/src/os/exec/lp_unix.go
+++ b/src/os/exec/lp_unix.go
@@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
// (only bypass the path if file begins with / or ./ or ../)
// but that would not match all the Unix shells.
+ if err := validateLookPath(file); err != nil {
+ return "", &Error{file, err}
+ }
+
if strings.Contains(file, "/") {
err := findExecutable(file)
if err == nil {
diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go
index 1225674306..e01e7bbbba 100644
--- a/src/os/exec/lp_windows.go
+++ b/src/os/exec/lp_windows.go
@@ -67,6 +67,10 @@ func findExecutable(file string, exts []string) (string, error) {
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
func LookPath(file string) (string, error) {
+ if err := validateLookPath(file); err != nil {
+ return "", &Error{file, err}
+ }
+
return lookPath(file, pathExt())
}
@@ -80,6 +84,10 @@ func LookPath(file string) (string, error) {
// "C:\foo\example.com" would be returned as-is even if the
// program is actually "C:\foo\example.com.exe".
func lookExtensions(path, dir string) (string, error) {
+ if err := validateLookPath(path); err != nil {
+ return "", &Error{path, err}
+ }
+
if filepath.Base(path) == path {
path = "." + string(filepath.Separator) + path
}