Skip to content

Commit e0b07dc

Browse files
dolmengopherbot
authored andcommitted
os/exec: fix incorrect expansion of "", "." and ".." in LookPath
Fix incorrect expansion of "" and "." when $PATH contains an executable file or, on Windows, a parent directory of a %PATH% element contains an file with the same name as the %PATH% element but with one of the %PATHEXT% extension (ex: C:\utils\bin is in PATH, and C:\utils\bin.exe exists). Fix incorrect expansion of ".." when $PATH contains an element which is an the concatenation of the path to an executable file (or on Windows a path that can be expanded to an executable by appending a %PATHEXT% extension), a path separator and a name. "", "." and ".." are now rejected early with ErrNotFound. Fixes CVE-2025-47906 Fixes #74466 Change-Id: Ie50cc0a660fce8fbdc952a7f2e05c36062dcb50e Reviewed-on: https://go-review.googlesource.com/c/go/+/685755 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent 25816d4 commit e0b07dc

File tree

5 files changed

+70
-0
lines changed

5 files changed

+70
-0
lines changed

src/os/exec/dot_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,48 @@ func TestLookPath(t *testing.T) {
177177
}
178178
}
179179
})
180+
181+
checker := func(test string) func(t *testing.T) {
182+
return func(t *testing.T) {
183+
t.Helper()
184+
t.Logf("PATH=%s", os.Getenv("PATH"))
185+
p, err := LookPath(test)
186+
if err == nil {
187+
t.Errorf("%q: error expected, got nil", test)
188+
}
189+
if p != "" {
190+
t.Errorf("%q: path returned should be \"\". Got %q", test, p)
191+
}
192+
}
193+
}
194+
195+
// Reference behavior for the next test
196+
t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
197+
t.Run("empty", checker(""))
198+
t.Run("dot", checker("."))
199+
t.Run("dotdot1", checker("abc/.."))
200+
t.Run("dotdot2", checker(".."))
201+
})
202+
203+
// Test the behavior when PATH contains an executable file which is not a directory
204+
t.Run(pathVar+"=exe", func(t *testing.T) {
205+
// Inject an executable file (not a directory) in PATH.
206+
// Use our own binary os.Args[0].
207+
t.Setenv(pathVar, testenv.Executable(t))
208+
t.Run("empty", checker(""))
209+
t.Run("dot", checker("."))
210+
t.Run("dotdot1", checker("abc/.."))
211+
t.Run("dotdot2", checker(".."))
212+
})
213+
214+
// Test the behavior when PATH contains an executable file which is not a directory
215+
t.Run(pathVar+"=exe/xx", func(t *testing.T) {
216+
// Inject an executable file (not a directory) in PATH.
217+
// Use our own binary os.Args[0].
218+
t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
219+
t.Run("empty", checker(""))
220+
t.Run("dot", checker("."))
221+
t.Run("dotdot1", checker("abc/.."))
222+
t.Run("dotdot2", checker(".."))
223+
})
180224
}

src/os/exec/exec.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
13281328
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
13291329
// to test whether a returned error err is due to this condition.
13301330
var ErrDot = errors.New("cannot run executable found relative to current directory")
1331+
1332+
// validateLookPath excludes paths that can't be valid
1333+
// executable names. See issue #74466 and CVE-2025-47906.
1334+
func validateLookPath(s string) error {
1335+
switch s {
1336+
case "", ".", "..":
1337+
return ErrNotFound
1338+
}
1339+
return nil
1340+
}

src/os/exec/lp_plan9.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func findExecutable(file string) error {
3636
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
3737
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
3838
func LookPath(file string) (string, error) {
39+
if err := validateLookPath(file); err != nil {
40+
return "", &Error{file, err}
41+
}
42+
3943
// skip the path lookup for these prefixes
4044
skip := []string{"/", "#", "./", "../"}
4145

src/os/exec/lp_unix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
5454
// (only bypass the path if file begins with / or ./ or ../)
5555
// but that would not match all the Unix shells.
5656

57+
if err := validateLookPath(file); err != nil {
58+
return "", &Error{file, err}
59+
}
60+
5761
if strings.Contains(file, "/") {
5862
err := findExecutable(file)
5963
if err == nil {

src/os/exec/lp_windows.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func findExecutable(file string, exts []string) (string, error) {
6767
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
6868
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
6969
func LookPath(file string) (string, error) {
70+
if err := validateLookPath(file); err != nil {
71+
return "", &Error{file, err}
72+
}
73+
7074
return lookPath(file, pathExt())
7175
}
7276

@@ -80,6 +84,10 @@ func LookPath(file string) (string, error) {
8084
// "C:\foo\example.com" would be returned as-is even if the
8185
// program is actually "C:\foo\example.com.exe".
8286
func lookExtensions(path, dir string) (string, error) {
87+
if err := validateLookPath(path); err != nil {
88+
return "", &Error{path, err}
89+
}
90+
8391
if filepath.Base(path) == path {
8492
path = "." + string(filepath.Separator) + path
8593
}

0 commit comments

Comments
 (0)