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
PATH
environment 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.