mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
[release-branch.go1.25] 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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
(cherry picked from commit e0b07dc22e
)
Reviewed-on: https://go-review.googlesource.com/c/go/+/691775
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
parent
84fb1b8253
commit
ebee011a54
5 changed files with 70 additions and 0 deletions
|
@ -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(".."))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
|
||||||
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
|
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
|
||||||
// to test whether a returned error err is due to this condition.
|
// to test whether a returned error err is due to this condition.
|
||||||
var ErrDot = errors.New("cannot run executable found relative to current directory")
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
// 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.
|
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||||
func LookPath(file string) (string, error) {
|
func LookPath(file string) (string, error) {
|
||||||
|
if err := validateLookPath(file); err != nil {
|
||||||
|
return "", &Error{file, err}
|
||||||
|
}
|
||||||
|
|
||||||
// skip the path lookup for these prefixes
|
// skip the path lookup for these prefixes
|
||||||
skip := []string{"/", "#", "./", "../"}
|
skip := []string{"/", "#", "./", "../"}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
|
||||||
// (only bypass the path if file begins with / or ./ or ../)
|
// (only bypass the path if file begins with / or ./ or ../)
|
||||||
// but that would not match all the Unix shells.
|
// but that would not match all the Unix shells.
|
||||||
|
|
||||||
|
if err := validateLookPath(file); err != nil {
|
||||||
|
return "", &Error{file, err}
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(file, "/") {
|
if strings.Contains(file, "/") {
|
||||||
err := findExecutable(file)
|
err := findExecutable(file)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -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
|
// 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.
|
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
|
||||||
func LookPath(file string) (string, error) {
|
func LookPath(file string) (string, error) {
|
||||||
|
if err := validateLookPath(file); err != nil {
|
||||||
|
return "", &Error{file, err}
|
||||||
|
}
|
||||||
|
|
||||||
return lookPath(file, pathExt())
|
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
|
// "C:\foo\example.com" would be returned as-is even if the
|
||||||
// program is actually "C:\foo\example.com.exe".
|
// program is actually "C:\foo\example.com.exe".
|
||||||
func lookExtensions(path, dir string) (string, error) {
|
func lookExtensions(path, dir string) (string, error) {
|
||||||
|
if err := validateLookPath(path); err != nil {
|
||||||
|
return "", &Error{path, err}
|
||||||
|
}
|
||||||
|
|
||||||
if filepath.Base(path) == path {
|
if filepath.Base(path) == path {
|
||||||
path = "." + string(filepath.Separator) + path
|
path = "." + string(filepath.Separator) + path
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue