vulncheck: Source returns vulns for all GOOS/GOARCH
vulncheck.Source will return all matching vulns, regardless of the
current values of GOOS and GOARCH.
cmd/govulncheck displays the GOOS/GOARCH values if there are any.
Fixes golang/go#54841.
Change-Id: I98dbb75fe631416d0b53a8d4c851ee01a2d00c6c
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/432356
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/govulncheck/doc.go b/cmd/govulncheck/doc.go
index dc43c59..c65e8cc 100644
--- a/cmd/govulncheck/doc.go
+++ b/cmd/govulncheck/doc.go
@@ -15,12 +15,10 @@
specification at https://go.dev/security/vuln/database.
Govulncheck looks for vulnerabilities in Go programs using a specific build
-configuration. For analyzing source code, that configuration is the operating
-system, architecture, and Go version specified by GOOS, GOARCH, and the “go”
-command found on the PATH. For binaries, the build configuration is the one
-used to build the binary. Note that different build configurations may have
-different known vulnerabilities. For example, a dependency with a
-Windows-specific vulnerability will not be reported for a Linux build.
+configuration. For analyzing source code, that configuration is the Go version
+specified by the “go” command found on the PATH. For binaries, the build
+configuration is the one used to build the binary. Note that different build
+configurations may have different known vulnerabilities.
Govulncheck must be built with Go version 1.18 or later.
@@ -86,13 +84,10 @@
report false positives for code that is in the binary but unreachable.
- There is no support for silencing vulnerability findings.
- Govulncheck only reads binaries compiled with Go 1.18 and later.
- - Govulncheck only reports vulnerabilities that apply to the current Go build
- system and configuration (GOOS/GOARCH settings). For example, a
- vulnerability that only applies to Linux will not be reported if
- govulncheck is run on a Windows machine. A standard library vulnerability
- that only applies for Go 1.18 will not be reported if the current Go
- version is 1.19. See https://go.dev/issue/54841 for updates to this
- limitation.
+ - Govulncheck only reports vulnerabilities that apply to the current Go
+ version. For example, a standard library vulnerability that only applies for
+ Go 1.18 will not be reported if the current Go version is 1.19. See
+ https://go.dev/issue/54841 for updates to this limitation.
# Feedback
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index bd97ff9..240d719 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -17,6 +17,7 @@
"sort"
"strings"
+ "golang.org/x/exp/maps"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages"
"golang.org/x/vuln/client"
@@ -248,7 +249,7 @@
b.WriteString(indent("\n\nCall stacks in your code:\n", 2))
b.WriteString(indent(stacks, 6))
}
- writeVulnerability(idx+1, id, details, b.String(), found, fixed)
+ writeVulnerability(idx+1, id, details, b.String(), found, fixed, platforms(v0.OSV))
}
if len(unaffected) > 0 {
fmt.Printf(`
@@ -263,21 +264,24 @@
found := foundVersion(vuln.ModPath, vuln.PkgPath, ci)
fixed := fixedVersion(vuln.PkgPath, vuln.OSV.Affected)
fmt.Println()
- writeVulnerability(idx+1, vuln.OSV.ID, vuln.OSV.Details, "", found, fixed)
+ writeVulnerability(idx+1, vuln.OSV.ID, vuln.OSV.Details, "", found, fixed, platforms(vuln.OSV))
}
}
}
-func writeVulnerability(idx int, id, details, callstack, found, fixed string) {
+func writeVulnerability(idx int, id, details, callstack, found, fixed, platforms string) {
if fixed == "" {
fixed = "N/A"
}
+ if platforms != "" {
+ platforms = " Platforms: " + platforms + "\n"
+ }
fmt.Printf(`Vulnerability #%d: %s
%s%s
Found in: %s
Fixed in: %s
- More info: https://pkg.go.dev/vuln/%s
-`, idx, id, indent(details, 2), callstack, found, fixed, id)
+%s More info: https://pkg.go.dev/vuln/%s
+`, idx, id, indent(details, 2), callstack, found, fixed, platforms, id)
}
func foundVersion(modulePath, pkgPath string, ci *govulncheck.CallInfo) string {
@@ -344,6 +348,24 @@
return b.String()
}
+// platforms returns a string describing the GOOS/GOARCH pairs that the vuln affects.
+// If it affects all of them, it returns the empty string.
+func platforms(e *osv.Entry) string {
+ platforms := map[string]bool{}
+ for _, a := range e.Affected {
+ for _, p := range a.EcosystemSpecific.Imports {
+ for _, os := range p.GOOS {
+ for _, arch := range p.GOARCH {
+ platforms[os+"/"+arch] = true
+ }
+ }
+ }
+ }
+ keys := maps.Keys(platforms)
+ sort.Strings(keys)
+ return strings.Join(keys, ", ")
+}
+
func isFile(path string) bool {
s, err := os.Stat(path)
if err != nil {
diff --git a/cmd/govulncheck/main_test.go b/cmd/govulncheck/main_test.go
index 0648f71..93095a5 100644
--- a/cmd/govulncheck/main_test.go
+++ b/cmd/govulncheck/main_test.go
@@ -108,6 +108,63 @@
}
}
+func TestPlatforms(t *testing.T) {
+ for _, test := range []struct {
+ entry *osv.Entry
+ want string
+ }{
+ {
+ entry: &osv.Entry{ID: "All"},
+ want: "",
+ },
+ {
+ entry: &osv.Entry{
+ ID: "one-import",
+ Affected: []osv.Affected{{
+ Package: osv.Package{Name: "golang.org/vmod"},
+ Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.2.0"}}}},
+ EcosystemSpecific: osv.EcosystemSpecific{
+ Imports: []osv.EcosystemSpecificImport{{
+ GOOS: []string{"windows", "linux"},
+ GOARCH: []string{"amd64", "wasm"},
+ }},
+ },
+ }},
+ },
+ want: "linux/amd64, linux/wasm, windows/amd64, windows/wasm",
+ },
+ {
+ entry: &osv.Entry{
+ ID: "two-imports",
+ Affected: []osv.Affected{{
+ Package: osv.Package{Name: "golang.org/vmod"},
+ Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.2.0"}}}},
+ EcosystemSpecific: osv.EcosystemSpecific{
+ Imports: []osv.EcosystemSpecificImport{
+ {
+ GOOS: []string{"windows"},
+ GOARCH: []string{"amd64"},
+ },
+ {
+ GOOS: []string{"linux"},
+ GOARCH: []string{"amd64"},
+ },
+ },
+ },
+ }},
+ },
+ want: "linux/amd64, windows/amd64",
+ },
+ } {
+ t.Run(test.entry.ID, func(t *testing.T) {
+ got := platforms(test.entry)
+ if got != test.want {
+ t.Errorf("got %q, want %q", got, test.want)
+ }
+ })
+ }
+}
+
func TestIndent(t *testing.T) {
for _, test := range []struct {
name string
diff --git a/cmd/govulncheck/testdata/default.ct b/cmd/govulncheck/testdata/default.ct
index e86d477..b02c50b 100644
--- a/cmd/govulncheck/testdata/default.ct
+++ b/cmd/govulncheck/testdata/default.ct
@@ -43,4 +43,5 @@
GJSON allowed a ReDoS (regular expression denial of service) attack.
Found in: github.com/tidwall/gjson@v1.9.2
Fixed in: github.com/tidwall/gjson@v1.9.3
+ Platforms: linux/amd64, windows/amd64
More info: https://pkg.go.dev/vuln/GO-2021-0265
diff --git a/cmd/govulncheck/testdata/two-symbols.ct b/cmd/govulncheck/testdata/two-symbols.ct
index 8e0e087..3fc8095 100644
--- a/cmd/govulncheck/testdata/two-symbols.ct
+++ b/cmd/govulncheck/testdata/two-symbols.ct
@@ -40,4 +40,5 @@
GJSON allowed a ReDoS (regular expression denial of service) attack.
Found in: github.com/tidwall/gjson@v1.9.2
Fixed in: github.com/tidwall/gjson@v1.9.3
+ Platforms: linux/amd64, windows/amd64
More info: https://pkg.go.dev/vuln/GO-2021-0265
diff --git a/cmd/govulncheck/testdata/verbose.ct b/cmd/govulncheck/testdata/verbose.ct
index dcab77f..5c1e49b 100644
--- a/cmd/govulncheck/testdata/verbose.ct
+++ b/cmd/govulncheck/testdata/verbose.ct
@@ -46,4 +46,5 @@
GJSON allowed a ReDoS (regular expression denial of service) attack.
Found in: github.com/tidwall/gjson@v1.9.2
Fixed in: github.com/tidwall/gjson@v1.9.3
+ Platforms: linux/amd64, windows/amd64
More info: https://pkg.go.dev/vuln/GO-2021-0265
diff --git a/cmd/govulncheck/testdata/vulndb/github.com/tidwall/gjson.json b/cmd/govulncheck/testdata/vulndb/github.com/tidwall/gjson.json
index b682d88..423378d 100644
--- a/cmd/govulncheck/testdata/vulndb/github.com/tidwall/gjson.json
+++ b/cmd/govulncheck/testdata/vulndb/github.com/tidwall/gjson.json
@@ -157,7 +157,9 @@
"path": "github.com/tidwall/gjson",
"symbols": [
"match.Match"
- ]
+ ],
+ "goos": ["linux", "windows"],
+ "goarch": ["amd64"]
}
]
}
diff --git a/vulncheck/source.go b/vulncheck/source.go
index deba9e7..533c6f2 100644
--- a/vulncheck/source.go
+++ b/vulncheck/source.go
@@ -59,8 +59,7 @@
if err != nil {
return nil, err
}
- modVulns = modVulns.filter(lookupEnv("GOOS", runtime.GOOS), lookupEnv("GOARCH", runtime.GOARCH))
-
+ modVulns = modVulns.filter(cfg.GOOS, cfg.GOARCH)
result := &Result{
Imports: &ImportGraph{Packages: make(map[int]*PkgNode)},
Requires: &RequireGraph{Modules: make(map[int]*ModNode)},
diff --git a/vulncheck/source_test.go b/vulncheck/source_test.go
index 77362ce..02cd78a 100644
--- a/vulncheck/source_test.go
+++ b/vulncheck/source_test.go
@@ -610,8 +610,8 @@
t.Fatal(err)
}
- if len(result.Vulns) != 1 {
- t.Errorf("want 1 Vuln, got %d", len(result.Vulns))
+ if g, w := len(result.Vulns), 1; g != w {
+ t.Errorf("got %d Vulns, want %d", g, w)
}
os.Setenv("GOOS", "freebsd")
@@ -622,8 +622,9 @@
t.Fatal(err)
}
- if len(result.Vulns) != 0 {
- t.Errorf("want 0 Vulns, got %d", len(result.Vulns))
+ // GOOS and GOARCH no longer affect the vulns.
+ if g, w := len(result.Vulns), 1; g != w {
+ t.Errorf("got %d Vulns, want %d", g, w)
}
}
diff --git a/vulncheck/utils.go b/vulncheck/utils.go
index 868fa72..878b76c 100644
--- a/vulncheck/utils.go
+++ b/vulncheck/utils.go
@@ -8,7 +8,6 @@
"bytes"
"go/token"
"go/types"
- "os"
"strings"
"golang.org/x/tools/go/callgraph"
@@ -233,13 +232,6 @@
return buf.String()
}
-func lookupEnv(key, defaultValue string) string {
- if v, ok := os.LookupEnv(key); ok {
- return v
- }
- return defaultValue
-}
-
// allSymbols returns all top-level functions and methods defined in pkg.
func allSymbols(pkg *types.Package) []string {
var names []string
diff --git a/vulncheck/vulncheck.go b/vulncheck/vulncheck.go
index 3b72f2c..48fc8dc 100644
--- a/vulncheck/vulncheck.go
+++ b/vulncheck/vulncheck.go
@@ -30,6 +30,11 @@
// to vulncheck. If not provided, the current underlying Go version
// is used to detect vulnerabilities in Go standard library.
SourceGoVersion string
+
+ // Consider only vulnerabilities that apply to this OS and architecture.
+ // An empty string means "all" (don't filter).
+ // Applies only to Source.
+ GOOS, GOARCH string
}
// Package is a Go package for vulncheck analysis. It is a version of
@@ -369,21 +374,23 @@
}
func matchesPlatform(os, arch string, e osv.EcosystemSpecificImport) bool {
- matchesOS := len(e.GOOS) == 0
- matchesArch := len(e.GOARCH) == 0
- for _, o := range e.GOOS {
- if os == o {
- matchesOS = true
- break
+ return matchesPlatformComponent(os, e.GOOS) &&
+ matchesPlatformComponent(arch, e.GOARCH)
+}
+
+// matchesPlatformComponent reports whether a GOOS (or GOARCH)
+// matches a list of GOOS (or GOARCH) values from an osv.EcosystemSpecificImport.
+func matchesPlatformComponent(s string, ps []string) bool {
+ // An empty input or an empty GOOS or GOARCH list means "matches everything."
+ if s == "" || len(ps) == 0 {
+ return true
+ }
+ for _, p := range ps {
+ if s == p {
+ return true
}
}
- for _, a := range e.GOARCH {
- if arch == a {
- matchesArch = true
- break
- }
- }
- return matchesOS && matchesArch
+ return false
}
// vulnsForPackage returns the vulnerabilities for the module which is the most