-
Notifications
You must be signed in to change notification settings - Fork 18.7k
Description
Go version
go version go1.25rc1 darwin/arm64
go version go1.24.4 darwin/arm64
go version go1.24.4 linux/amd64
go version go1.24.4 windows/amd64
...
What did you do?
The os/exec.LookPath function incorrectly resolves the given path without raising an error, when both of the following circumstances are matched:
- the
PATHenvironment variable contains one element that is a path to an executable file that is not a directory - the path given to
exec.LookPath, when joined to the path above usingfilepath.Join(), gives a path to the executable file inPATH. Such values include""(the empty string) or"."(dot)
In that case, the returned error is nil(no error) and the resolved path is the path to the executable file in PATH.
On Windows the incorrect resolution of "" and "." can also be triggered when a malicious executable file exists in the parent directory of an element of PATH with the name of that PATH element and a PATHEXT extension. For example, exec.LookPath("") resolves to "C:\utils\bin.cmd" if "C:\utils\bin" is in PATH and "C:\utils\bin.cmd" exists. In that case the control of the value of the PATH environment variable is not necessary for the exploitation.
As the output of a successful call to exec.LookPath is usually given to exec.Command, this bug could be used to trigger the execution of the malicious PATH element as the command, while that is not obvious when looking at the calling code. Cmd.Start contains protections against running an empty command, but here we are in a case where LookPath transforms an empty path into a non-empty path pointing to a real executable file, and so allows to bypass that protection.
Proof of concept
The full code of the program below is available on the Go Playground: https://go.dev/play/p/scC3h8jGPuz
func check(s string) {
fmt.Println("PATH:", os.Getenv("PATH"))
fmt.Printf("exec.LookPath(%q)\n", s)
p, err := exec.LookPath(s)
fmt.Printf("-> err: %v\n-> p: %q\n", err, p)
}
func main() {
check("")
check(".")
// Let's add an executable file in PATH
os.Setenv("PATH", os.Getenv("PATH")+string(filepath.ListSeparator)+os.Args[0])
check("")
check(".")
}Output:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
exec.LookPath("")
-> err: exec: "": executable file not found in $PATH
-> p: ""
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
exec.LookPath(".")
-> err: exec: ".": executable file not found in $PATH
-> p: ""
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tmpfs/play
exec.LookPath("")
-> err: <nil>
-> p: "/tmpfs/play"
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tmpfs/play
exec.LookPath(".")
-> err: <nil>
-> p: "/tmpfs/play"
Here is some example of exploiting the issue to make exec.Cmd.Run execute the program injected in $PATH instead of failing because the LookPath argument is empty.
That program runs on the Go Playground: https://go.dev/play/p/yivKGIeemiB
func main() {
// Binary injected in PATH. This could happen outside the program
os.Setenv("PATH", "/bin/date")
// Vulnerable code that executes /bin/date instead of failing because to the empty command
p, err := exec.LookPath("")
if err != nil {
log.Fatal(err)
}
cmd := exec.Command(p)
cmd.Stdout = os.Stdout
cmd.Run()
}Output:
Mon Jun 23 21:21:10 UTC 2025
Severity
So far I'm not considering this bug to be a real risk in the general case as any real exploit requires the control of both PATH and the argument to exec.LookPath, or on Windows a writable directory parent to a PATH element. An attacker who has just control of PATH already has a wide range of attacks possible, so this bug doesn't increase the vulnerability of the system.
However, this bug could still be used to hide the launch of an external command to a code reviewer or to static analysis tools. The path to a malicious binary could be added to PATH in some place and the path given to os/exec.LookPath could be just left uninitialized in some obscure path or from user input, and the execution of the binary specified in PATH would be triggered.
Also, as a Go developer, I was trusting exec.LookPath to sanitize user input before calling exec.Command. This bug breaks this expectation (a command name that would be rejected by Cmd.Start like "" may still lead to an execution).
Notes
Following the Go Security Policy this bug has been privately reported to the Go security team on 2025-06-24 who qualified it as a PUBLIC track issue.