[release-branch.go1.14-security] all: introduce and use internal/execabs

Introduces a wrapper around os/exec, internal/execabs, for use in
all commands. This wrapper prevents exec.LookPath and exec.Command from
running executables in the current directory.

All imports of os/exec in non-test files in cmd/ are replaced with
imports of internal/execabs.

This issue was reported by RyotaK.

Fixes CVE-2021-3115

Change-Id: I0423451a6e27ec1e1d6f3fe929ab1ef69145c08f
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/955304
Reviewed-by: Russ Cox <rsc@google.com>
Reviewed-by: Katie Hockman <katiehockman@google.com>
(cherry picked from commit 44f09a6990ccf4db601cbf8208c89ac4e888f884)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/955309
diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go
index b46b310..26eb328 100644
--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -16,11 +16,11 @@
 	"go/parser"
 	"go/token"
 	"go/types"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime"
diff --git a/src/cmd/api/run.go b/src/cmd/api/run.go
index a36f117..ecb1d0f 100644
--- a/src/cmd/api/run.go
+++ b/src/cmd/api/run.go
@@ -10,9 +10,9 @@
 
 import (
 	"fmt"
+	exec "internal/execabs"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"runtime"
 	"strings"
diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go
index d447bcb..698181c 100644
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -13,11 +13,11 @@
 	"go/ast"
 	"go/printer"
 	"go/token"
+	exec "internal/execabs"
 	"internal/xcoff"
 	"io"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"sort"
diff --git a/src/cmd/cgo/util.go b/src/cmd/cgo/util.go
index 779f7be..00d931b 100644
--- a/src/cmd/cgo/util.go
+++ b/src/cmd/cgo/util.go
@@ -8,9 +8,9 @@
 	"bytes"
 	"fmt"
 	"go/token"
+	exec "internal/execabs"
 	"io/ioutil"
 	"os"
-	"os/exec"
 )
 
 // run runs the command argv, feeding in stdin on standard input.
diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go
index 1e76a67..068c382 100644
--- a/src/cmd/compile/internal/ssa/html.go
+++ b/src/cmd/compile/internal/ssa/html.go
@@ -9,9 +9,9 @@
 	"cmd/internal/src"
 	"fmt"
 	"html"
+	exec "internal/execabs"
 	"io"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
diff --git a/src/cmd/cover/func.go b/src/cmd/cover/func.go
index 988c4ca..ce7c771 100644
--- a/src/cmd/cover/func.go
+++ b/src/cmd/cover/func.go
@@ -15,9 +15,9 @@
 	"go/ast"
 	"go/parser"
 	"go/token"
+	exec "internal/execabs"
 	"io"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"runtime"
diff --git a/src/cmd/cover/testdata/toolexec.go b/src/cmd/cover/testdata/toolexec.go
index 1769efe..386de79 100644
--- a/src/cmd/cover/testdata/toolexec.go
+++ b/src/cmd/cover/testdata/toolexec.go
@@ -16,7 +16,7 @@
 
 import (
 	"os"
-	"os/exec"
+	exec "internal/execabs"
 	"strings"
 )
 
diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go
index a07e64b..095ebbd 100644
--- a/src/cmd/dist/buildtool.go
+++ b/src/cmd/dist/buildtool.go
@@ -298,8 +298,10 @@
 			continue
 		}
 		if strings.HasPrefix(line, `import "`) || strings.HasPrefix(line, `import . "`) ||
-			inBlock && (strings.HasPrefix(line, "\t\"") || strings.HasPrefix(line, "\t. \"")) {
+			inBlock && (strings.HasPrefix(line, "\t\"") || strings.HasPrefix(line, "\t. \"") || strings.HasPrefix(line, "\texec \"")) {
 			line = strings.Replace(line, `"cmd/`, `"bootstrap/cmd/`, -1)
+			// During bootstrap, must use plain os/exec.
+			line = strings.Replace(line, `exec "internal/execabs"`, `"os/exec"`, -1)
 			for _, dir := range bootstrapDirs {
 				if strings.HasPrefix(dir, "cmd/") {
 					continue
diff --git a/src/cmd/doc/dirs.go b/src/cmd/doc/dirs.go
index 38cbe7f..661624c 100644
--- a/src/cmd/doc/dirs.go
+++ b/src/cmd/doc/dirs.go
@@ -7,9 +7,9 @@
 import (
 	"bytes"
 	"fmt"
+	exec "internal/execabs"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strings"
diff --git a/src/cmd/fix/typecheck.go b/src/cmd/fix/typecheck.go
index 66e0cdc..8390e44 100644
--- a/src/cmd/fix/typecheck.go
+++ b/src/cmd/fix/typecheck.go
@@ -9,9 +9,9 @@
 	"go/ast"
 	"go/parser"
 	"go/token"
+	exec "internal/execabs"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"reflect"
 	"runtime"
diff --git a/src/cmd/go/internal/base/base.go b/src/cmd/go/internal/base/base.go
index 272da55..15e6035 100644
--- a/src/cmd/go/internal/base/base.go
+++ b/src/cmd/go/internal/base/base.go
@@ -12,9 +12,9 @@
 	"flag"
 	"fmt"
 	"go/scanner"
+	exec "internal/execabs"
 	"log"
 	"os"
-	"os/exec"
 	"strings"
 	"sync"
 
diff --git a/src/cmd/go/internal/bug/bug.go b/src/cmd/go/internal/bug/bug.go
index fe71281..9434bc2 100644
--- a/src/cmd/go/internal/bug/bug.go
+++ b/src/cmd/go/internal/bug/bug.go
@@ -8,11 +8,11 @@
 import (
 	"bytes"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	urlpkg "net/url"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime"
diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go
index 315db69..758fa3b 100644
--- a/src/cmd/go/internal/generate/generate.go
+++ b/src/cmd/go/internal/generate/generate.go
@@ -9,10 +9,10 @@
 	"bufio"
 	"bytes"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strconv"
diff --git a/src/cmd/go/internal/modfetch/codehost/codehost.go b/src/cmd/go/internal/modfetch/codehost/codehost.go
index 5867288..383981d 100644
--- a/src/cmd/go/internal/modfetch/codehost/codehost.go
+++ b/src/cmd/go/internal/modfetch/codehost/codehost.go
@@ -10,10 +10,10 @@
 	"bytes"
 	"crypto/sha256"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 	"sync"
diff --git a/src/cmd/go/internal/modfetch/codehost/git.go b/src/cmd/go/internal/modfetch/codehost/git.go
index f08df51..aa696ec 100644
--- a/src/cmd/go/internal/modfetch/codehost/git.go
+++ b/src/cmd/go/internal/modfetch/codehost/git.go
@@ -8,11 +8,11 @@
 	"bytes"
 	"errors"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	"net/url"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"sort"
 	"strconv"
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 4ad142c..a23a1bb 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -10,10 +10,10 @@
 	"errors"
 	"fmt"
 	"go/build"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"regexp"
diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go
index 930eecb..f06c903 100644
--- a/src/cmd/go/internal/tool/tool.go
+++ b/src/cmd/go/internal/tool/tool.go
@@ -7,8 +7,8 @@
 
 import (
 	"fmt"
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"sort"
 	"strings"
 
diff --git a/src/cmd/go/internal/vet/vetflag.go b/src/cmd/go/internal/vet/vetflag.go
index e3de48b..052b8f1 100644
--- a/src/cmd/go/internal/vet/vetflag.go
+++ b/src/cmd/go/internal/vet/vetflag.go
@@ -9,9 +9,9 @@
 	"encoding/json"
 	"flag"
 	"fmt"
+	exec "internal/execabs"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index e3b25c9..c19e6f1 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -8,8 +8,8 @@
 	"errors"
 	"fmt"
 	"go/build"
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"runtime"
 	"strings"
diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go
index 7558a30..5fbdbb6 100644
--- a/src/cmd/go/internal/work/buildid.go
+++ b/src/cmd/go/internal/work/buildid.go
@@ -7,9 +7,9 @@
 import (
 	"bytes"
 	"fmt"
+	exec "internal/execabs"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"strings"
 
 	"cmd/go/internal/base"
diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go
index b65e2a0..d5c7506 100644
--- a/src/cmd/go/internal/work/exec.go
+++ b/src/cmd/go/internal/work/exec.go
@@ -16,13 +16,13 @@
 	"encoding/json"
 	"errors"
 	"fmt"
+	exec "internal/execabs"
 	"internal/lazyregexp"
 	"io"
 	"io/ioutil"
 	"log"
 	"math/rand"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime"
diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go
index 4c1f36d..2f5d5d6 100644
--- a/src/cmd/go/internal/work/gccgo.go
+++ b/src/cmd/go/internal/work/gccgo.go
@@ -6,9 +6,9 @@
 
 import (
 	"fmt"
+	exec "internal/execabs"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 
diff --git a/src/cmd/go/testdata/addmod.go b/src/cmd/go/testdata/addmod.go
index d9c3aab..9c74cf5 100644
--- a/src/cmd/go/testdata/addmod.go
+++ b/src/cmd/go/testdata/addmod.go
@@ -25,7 +25,7 @@
 	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
+	exec "internal/execabs"
 	"path/filepath"
 	"strings"
 
diff --git a/src/cmd/internal/browser/browser.go b/src/cmd/internal/browser/browser.go
index 6867c85..577d317 100644
--- a/src/cmd/internal/browser/browser.go
+++ b/src/cmd/internal/browser/browser.go
@@ -6,8 +6,8 @@
 package browser
 
 import (
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"runtime"
 	"time"
 )
diff --git a/src/cmd/internal/diff/diff.go b/src/cmd/internal/diff/diff.go
index e9d2c23..c0ca2f3 100644
--- a/src/cmd/internal/diff/diff.go
+++ b/src/cmd/internal/diff/diff.go
@@ -7,9 +7,9 @@
 package diff
 
 import (
+	exec "internal/execabs"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"runtime"
 )
 
diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go
index 56b44a1..f97a127 100644
--- a/src/cmd/internal/dwarf/dwarf.go
+++ b/src/cmd/internal/dwarf/dwarf.go
@@ -12,7 +12,7 @@
 	"cmd/internal/objabi"
 	"errors"
 	"fmt"
-	"os/exec"
+	exec "internal/execabs"
 	"sort"
 	"strconv"
 	"strings"
diff --git a/src/cmd/link/internal/ld/execarchive.go b/src/cmd/link/internal/ld/execarchive.go
index fe5cc40..4687c62 100644
--- a/src/cmd/link/internal/ld/execarchive.go
+++ b/src/cmd/link/internal/ld/execarchive.go
@@ -7,8 +7,8 @@
 package ld
 
 import (
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"syscall"
 )
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index cd63963d..be6ffa0 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -51,11 +51,11 @@
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"runtime"
 	"sort"
diff --git a/src/cmd/test2json/main.go b/src/cmd/test2json/main.go
index 0385d8f..52bad6b 100644
--- a/src/cmd/test2json/main.go
+++ b/src/cmd/test2json/main.go
@@ -82,9 +82,9 @@
 import (
 	"flag"
 	"fmt"
+	exec "internal/execabs"
 	"io"
 	"os"
-	"os/exec"
 
 	"cmd/internal/test2json"
 )
diff --git a/src/cmd/trace/pprof.go b/src/cmd/trace/pprof.go
index a31d71b..e6ea1a4 100644
--- a/src/cmd/trace/pprof.go
+++ b/src/cmd/trace/pprof.go
@@ -9,12 +9,12 @@
 import (
 	"bufio"
 	"fmt"
+	exec "internal/execabs"
 	"internal/trace"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"runtime"
 	"sort"
diff --git a/src/go/build/build.go b/src/go/build/build.go
index 1a122c6..9136319 100644
--- a/src/go/build/build.go
+++ b/src/go/build/build.go
@@ -12,13 +12,13 @@
 	"go/doc"
 	"go/parser"
 	"go/token"
+	exec "internal/execabs"
 	"internal/goroot"
 	"internal/goversion"
 	"io"
 	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	pathpkg "path"
 	"path/filepath"
 	"runtime"
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 8eed626..6106afa 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -207,6 +207,8 @@
 	"internal/lazyregexp":      {"L2", "OS", "regexp"},
 	"internal/lazytemplate":    {"L2", "OS", "text/template"},
 
+	"internal/execabs": {"L2", "OS", "fmt", "context", "reflect"},
+
 	// L4 is defined as L3+fmt+log+time, because in general once
 	// you're using L3 packages, use of fmt, log, or time is not a big deal.
 	"L4": {
@@ -240,7 +242,7 @@
 	"go/constant":               {"L4", "go/token", "math/big"},
 	"go/importer":               {"L4", "go/build", "go/internal/gccgoimporter", "go/internal/gcimporter", "go/internal/srcimporter", "go/token", "go/types"},
 	"go/internal/gcimporter":    {"L4", "OS", "go/build", "go/constant", "go/token", "go/types", "text/scanner"},
-	"go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner"},
+	"go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner", "internal/execabs"},
 	"go/internal/srcimporter":   {"L4", "OS", "fmt", "go/ast", "go/build", "go/parser", "go/token", "go/types", "path/filepath"},
 	"go/types":                  {"L4", "GOPARSER", "container/heap", "go/constant"},
 
@@ -272,7 +274,7 @@
 	"encoding/pem":                   {"L4"},
 	"encoding/xml":                   {"L4", "encoding"},
 	"flag":                           {"L4", "OS"},
-	"go/build":                       {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion"},
+	"go/build":                       {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion", "internal/execabs"},
 	"html":                           {"L4"},
 	"image/draw":                     {"L4", "image/internal/imageutil"},
 	"image/gif":                      {"L4", "compress/lzw", "image/color/palette", "image/draw"},
@@ -280,7 +282,7 @@
 	"image/jpeg":                     {"L4", "image/internal/imageutil"},
 	"image/png":                      {"L4", "compress/zlib"},
 	"index/suffixarray":              {"L4", "regexp"},
-	"internal/goroot":                {"L4", "OS"},
+	"internal/goroot":                {"L4", "OS", "internal/execabs"},
 	"internal/singleflight":          {"sync"},
 	"internal/trace":                 {"L4", "OS", "container/heap"},
 	"internal/xcoff":                 {"L4", "OS", "debug/dwarf"},
diff --git a/src/go/internal/gccgoimporter/gccgoinstallation.go b/src/go/internal/gccgoimporter/gccgoinstallation.go
index 8fc7ce3..e90a3cc 100644
--- a/src/go/internal/gccgoimporter/gccgoinstallation.go
+++ b/src/go/internal/gccgoimporter/gccgoinstallation.go
@@ -7,8 +7,8 @@
 import (
 	"bufio"
 	"go/types"
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 )
diff --git a/src/internal/execabs/execabs.go b/src/internal/execabs/execabs.go
new file mode 100644
index 0000000..547c3a5
--- /dev/null
+++ b/src/internal/execabs/execabs.go
@@ -0,0 +1,70 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package execabs is a drop-in replacement for os/exec
+// that requires PATH lookups to find absolute paths.
+// That is, execabs.Command("cmd") runs the same PATH lookup
+// as exec.Command("cmd"), but if the result is a path
+// which is relative, the Run and Start methods will report
+// an error instead of running the executable.
+package execabs
+
+import (
+	"context"
+	"fmt"
+	"os/exec"
+	"path/filepath"
+	"reflect"
+	"unsafe"
+)
+
+var ErrNotFound = exec.ErrNotFound
+
+type (
+	Cmd       = exec.Cmd
+	Error     = exec.Error
+	ExitError = exec.ExitError
+)
+
+func relError(file, path string) error {
+	return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
+}
+
+func LookPath(file string) (string, error) {
+	path, err := exec.LookPath(file)
+	if err != nil {
+		return "", err
+	}
+	if filepath.Base(file) == file && !filepath.IsAbs(path) {
+		return "", relError(file, path)
+	}
+	return path, nil
+}
+
+func fixCmd(name string, cmd *exec.Cmd) {
+	if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
+		// exec.Command was called with a bare binary name and
+		// exec.LookPath returned a path which is not absolute.
+		// Set cmd.lookPathErr and clear cmd.Path so that it
+		// cannot be run.
+		lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
+		if *lookPathErr == nil {
+			*lookPathErr = relError(name, cmd.Path)
+		}
+		cmd.Path = ""
+	}
+}
+
+func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
+	cmd := exec.CommandContext(ctx, name, arg...)
+	fixCmd(name, cmd)
+	return cmd
+
+}
+
+func Command(name string, arg ...string) *exec.Cmd {
+	cmd := exec.Command(name, arg...)
+	fixCmd(name, cmd)
+	return cmd
+}
diff --git a/src/internal/execabs/execabs_test.go b/src/internal/execabs/execabs_test.go
new file mode 100644
index 0000000..a0b88dd
--- /dev/null
+++ b/src/internal/execabs/execabs_test.go
@@ -0,0 +1,107 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package execabs
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+)
+
+func TestFixCmd(t *testing.T) {
+	cmd := &exec.Cmd{Path: "hello"}
+	fixCmd("hello", cmd)
+	if cmd.Path != "" {
+		t.Errorf("fixCmd didn't clear cmd.Path")
+	}
+	expectedErr := fmt.Sprintf("hello resolves to executable in current directory (.%chello)", filepath.Separator)
+	if err := cmd.Run(); err == nil {
+		t.Fatal("Command.Run didn't fail")
+	} else if err.Error() != expectedErr {
+		t.Fatalf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error())
+	}
+}
+
+func TestCommand(t *testing.T) {
+	for _, cmd := range []func(string) *Cmd{
+		func(s string) *Cmd { return Command(s) },
+		func(s string) *Cmd { return CommandContext(context.Background(), s) },
+	} {
+		tmpDir, err := ioutil.TempDir("", "execabs-test")
+		if err != nil {
+			t.Fatalf("ioutil.TempDir failed: %s", err)
+		}
+		defer os.RemoveAll(tmpDir)
+		executable := "execabs-test"
+		if runtime.GOOS == "windows" {
+			executable += ".exe"
+		}
+		if err = ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+			t.Fatalf("ioutil.WriteFile failed: %s", err)
+		}
+		cwd, err := os.Getwd()
+		if err != nil {
+			t.Fatalf("os.Getwd failed: %s", err)
+		}
+		defer os.Chdir(cwd)
+		if err = os.Chdir(tmpDir); err != nil {
+			t.Fatalf("os.Chdir failed: %s", err)
+		}
+		if runtime.GOOS != "windows" {
+			// add "." to PATH so that exec.LookPath looks in the current directory on
+			// non-windows platforms as well
+			origPath := os.Getenv("PATH")
+			defer os.Setenv("PATH", origPath)
+			os.Setenv("PATH", fmt.Sprintf(".:%s", origPath))
+		}
+		expectedErr := fmt.Sprintf("execabs-test resolves to executable in current directory (.%c%s)", filepath.Separator, executable)
+		if err = cmd("execabs-test").Run(); err == nil {
+			t.Fatalf("Command.Run didn't fail when exec.LookPath returned a relative path")
+		} else if err.Error() != expectedErr {
+			t.Errorf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error())
+		}
+	}
+}
+
+func TestLookPath(t *testing.T) {
+	tmpDir, err := ioutil.TempDir("", "execabs-test")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %s", err)
+	}
+	defer os.RemoveAll(tmpDir)
+	executable := "execabs-test"
+	if runtime.GOOS == "windows" {
+		executable += ".exe"
+	}
+	if err = ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+		t.Fatalf("ioutil.WriteFile failed: %s", err)
+	}
+	cwd, err := os.Getwd()
+	if err != nil {
+		t.Fatalf("os.Getwd failed: %s", err)
+	}
+	defer os.Chdir(cwd)
+	if err = os.Chdir(tmpDir); err != nil {
+		t.Fatalf("os.Chdir failed: %s", err)
+	}
+	if runtime.GOOS != "windows" {
+		// add "." to PATH so that exec.LookPath looks in the current directory on
+		// non-windows platforms as well
+		origPath := os.Getenv("PATH")
+		defer os.Setenv("PATH", origPath)
+		os.Setenv("PATH", fmt.Sprintf(".:%s", origPath))
+	}
+	expectedErr := fmt.Sprintf("execabs-test resolves to executable in current directory (.%c%s)", filepath.Separator, executable)
+	if _, err := LookPath("execabs-test"); err == nil {
+		t.Fatalf("LookPath didn't fail when finding a non-relative path")
+	} else if err.Error() != expectedErr {
+		t.Errorf("LookPath returned unexpected error: want %q, got %q", expectedErr, err.Error())
+	}
+}
diff --git a/src/internal/goroot/gc.go b/src/internal/goroot/gc.go
index 0f541d7..ce72bc3 100644
--- a/src/internal/goroot/gc.go
+++ b/src/internal/goroot/gc.go
@@ -7,8 +7,8 @@
 package goroot
 
 import (
+	exec "internal/execabs"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 	"sync"