| // Copyright 2021 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. |
| |
| //go:build go1.18 |
| // +build go1.18 |
| |
| package vulncheck |
| |
| import ( |
| "context" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "sort" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "golang.org/x/tools/go/packages/packagestest" |
| ) |
| |
| // TODO: we build binary programatically, so what if the underlying tool chain changes? |
| func TestBinary(t *testing.T) { |
| // TODO(#52160): investigate why Binary does not process plan9 binaries |
| if !hasGoBuild() || runtime.GOOS == "plan9" { |
| t.Skip("fails on android and plan9") |
| } |
| |
| e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ |
| { |
| Name: "golang.org/entry", |
| Files: map[string]interface{}{ |
| "main.go": ` |
| package main |
| |
| import ( |
| "golang.org/cmod/c" |
| "golang.org/bmod/bvuln" |
| ) |
| |
| func main() { |
| c.C() |
| bvuln.NoVuln() // no vuln use |
| print("done") |
| } |
| `, |
| }}, |
| { |
| Name: "golang.org/cmod@v1.1.3", |
| Files: map[string]interface{}{"c/c.go": ` |
| package c |
| |
| import ( |
| "golang.org/amod/avuln" |
| ) |
| |
| //go:noinline |
| func C() { |
| v := avuln.VulnData{} |
| v.Vuln1() // vuln use |
| } |
| `}, |
| }, |
| { |
| Name: "golang.org/amod@v1.1.3", |
| Files: map[string]interface{}{"avuln/avuln.go": ` |
| package avuln |
| |
| type VulnData struct {} |
| |
| //go:noinline |
| func (v VulnData) Vuln1() {} |
| |
| //go:noinline |
| func (v VulnData) Vuln2() {} |
| `}, |
| }, |
| { |
| Name: "golang.org/bmod@v0.5.0", |
| Files: map[string]interface{}{"bvuln/bvuln.go": ` |
| package bvuln |
| |
| //go:noinline |
| func Vuln() {} |
| |
| //go:noinline |
| func NoVuln() {} |
| `}, |
| }, |
| }) |
| defer e.Cleanup() |
| |
| cmd := exec.Command("go", "build", "-o", "entry") |
| cmd.Dir = e.Config.Dir |
| cmd.Env = e.Config.Env |
| out, err := cmd.CombinedOutput() |
| if err != nil || len(out) > 0 { |
| t.Fatalf("failed to build the binary %v %v", err, string(out)) |
| } |
| |
| bin, err := os.Open(filepath.Join(e.Config.Dir, "entry")) |
| if err != nil { |
| t.Fatalf("failed to access the binary %v", err) |
| } |
| defer bin.Close() |
| |
| // Test imports only mode |
| cfg := &Config{ |
| Client: testClient, |
| ImportsOnly: true, |
| } |
| res, err := Binary(context.Background(), bin, cfg) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // In importsOnly mode, all three vulnerable symbols |
| // {avuln.VulnData.Vuln1, avuln.VulnData.Vuln2, bvuln.Vuln} |
| // should be detected. |
| wantVulns := []*Vuln{ |
| {Symbol: "Vuln", PkgPath: "golang.org/bmod/bvuln", ModPath: "golang.org/bmod"}, |
| {Symbol: "VulnData.Vuln1", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"}, |
| {Symbol: "VulnData.Vuln2", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"}, |
| } |
| diff := cmp.Diff(wantVulns, res.Vulns, |
| cmpopts.IgnoreFields(Vuln{}, "OSV"), |
| cmpopts.SortSlices(func(v1, v2 *Vuln) bool { return v1.Symbol < v2.Symbol })) |
| if diff != "" { |
| t.Errorf("vulns mismatch (-want, +got)\n%s", diff) |
| } |
| |
| // Test the symbols (non-import mode) |
| cfg = &Config{Client: testClient} |
| res, err = Binary(context.Background(), bin, cfg) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // In non-importsOnly mode, only one symbol avuln.VulnData.Vuln1 should be detected. |
| if len(res.Vulns) != 1 { |
| t.Errorf("expected 1 vuln symbols got %d", len(res.Vulns)) |
| } |
| |
| // Check that the binary's modules are returned. |
| // The list does not include the module binary itself. |
| wantMods := []*Module{ |
| {Path: "golang.org/amod", Version: "v1.1.3"}, |
| {Path: "golang.org/bmod", Version: "v0.5.0"}, |
| {Path: "golang.org/cmod", Version: "v1.1.3"}, |
| } |
| gotMods := res.Modules |
| sort.Slice(gotMods, func(i, j int) bool { return gotMods[i].Path < gotMods[j].Path }) |
| if diff := cmp.Diff(wantMods, gotMods, cmpopts.IgnoreFields(Module{}, "Dir")); diff != "" { |
| t.Errorf("modules mismatch (-want, +got):\n%s", diff) |
| } |
| } |
| |
| // hasGoBuild reports whether the current system can build programs with “go build” |
| // and then run them with os.StartProcess or exec.Command. |
| // |
| // Duplicated from std/internal/testenv |
| func hasGoBuild() bool { |
| if os.Getenv("GO_GCFLAGS") != "" { |
| return false |
| } |
| switch runtime.GOOS { |
| case "android", "js", "ios": |
| return false |
| } |
| return true |
| } |