[release-branch.go1.14-security] cmd/go: pass resolved CC, GCCGO to cgo

This makes sure the go command and cgo agree about
exactly which compiler is being used.

This issue was reported by RyotaK.

Fixes CVE-2021-3115.

Change-Id: If171c5c8b2523efb5ea2d957e5ad1380a038149c
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/949416
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Jay Conrod <jayconrod@google.com>
(cherry picked from commit 4cf399ca38587a6e4a3e85b494cd9a9b4cc53378)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/955294
Reviewed-by: Katie Hockman <katiehockman@google.com>
diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go
index e3cb87f..8256681 100644
--- a/src/cmd/go/internal/work/action.go
+++ b/src/cmd/go/internal/work/action.go
@@ -56,6 +56,9 @@
 	id           sync.Mutex
 	toolIDCache  map[string]string // tool name -> tool ID
 	buildIDCache map[string]string // file name -> build ID
+
+	cgoEnvOnce  sync.Once
+	cgoEnvCache []string
 }
 
 // NOTE: Much of Action would not need to be exported if not for test.
diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go
index a87bc80..b65e2a0 100644
--- a/src/cmd/go/internal/work/exec.go
+++ b/src/cmd/go/internal/work/exec.go
@@ -1075,10 +1075,8 @@
 		return err
 	}
 
-	env := b.cCompilerEnv()
-	if cfg.BuildToolchainName == "gccgo" {
-		env = append(env, "GCCGO="+BuildToolchain.compiler())
-	}
+	// TODO(rsc): Why do we pass $GCCGO to go vet?
+	env := b.cgoEnv()
 
 	p := a.Package
 	tool := VetTool
@@ -1979,6 +1977,24 @@
 	return []string{"TERM=dumb"}
 }
 
+// cgoEnv returns environment variables to set when running cgo.
+// Some of these pass through to cgo running the C compiler,
+// so it includes cCompilerEnv.
+func (b *Builder) cgoEnv() []string {
+	b.cgoEnvOnce.Do(func() {
+		cc, err := exec.LookPath(b.ccExe()[0])
+		if err != nil || filepath.Base(cc) == cc { // reject relative path
+			cc = "/missing-cc"
+		}
+		gccgo := GccgoBin
+		if filepath.Base(gccgo) == gccgo { // reject relative path
+			gccgo = "/missing-gccgo"
+		}
+		b.cgoEnvCache = append(b.cCompilerEnv(), "CC="+cc, "GCCGO="+gccgo)
+	})
+	return b.cgoEnvCache
+}
+
 // mkdir makes the named directory.
 func (b *Builder) Mkdir(dir string) error {
 	// Make Mkdir(a.Objdir) a no-op instead of an error when a.Objdir == "".
@@ -2524,13 +2540,13 @@
 	// along to the host linker. At this point in the code, cgoLDFLAGS
 	// consists of the original $CGO_LDFLAGS (unchecked) and all the
 	// flags put together from source code (checked).
-	cgoenv := b.cCompilerEnv()
+	cgoenv := b.cgoEnv()
 	if len(cgoLDFLAGS) > 0 {
 		flags := make([]string, len(cgoLDFLAGS))
 		for i, f := range cgoLDFLAGS {
 			flags[i] = strconv.Quote(f)
 		}
-		cgoenv = []string{"CGO_LDFLAGS=" + strings.Join(flags, " ")}
+		cgoenv = append(cgoenv, "CGO_LDFLAGS="+strings.Join(flags, " "))
 	}
 
 	if cfg.BuildToolchainName == "gccgo" {
@@ -2745,7 +2761,7 @@
 	if p.Standard && p.ImportPath == "runtime/cgo" {
 		cgoflags = []string{"-dynlinker"} // record path to dynamic linker
 	}
-	return b.run(a, p.Dir, p.ImportPath, b.cCompilerEnv(), cfg.BuildToolexec, cgoExe, "-dynpackage", p.Name, "-dynimport", dynobj, "-dynout", importGo, cgoflags)
+	return b.run(a, p.Dir, p.ImportPath, b.cgoEnv(), cfg.BuildToolexec, cgoExe, "-dynpackage", p.Name, "-dynimport", dynobj, "-dynout", importGo, cgoflags)
 }
 
 // Run SWIG on all SWIG input files.