vulncheck: filter affected related but different modules

Consider a report that has vulnerability info for two modules, m1 and
m2. An example is GO-2022-0969.yaml. If m1 appears in the program, a
vuln client can load vulnerability info for m1 that can also contain
info on the related module m2. The vuln client in this repository is an
example of this behavior.

This can create problems. If both m1 and m2 are imported by the program
at versions at which the vulnerabilities are present, this can result in
incorrectly computed latest fix: computing a latest fix for m1 would
also consider latest fix for m2.

The current solution is to filter out affected data for m2 from info on
vulns of m1. This is simple and should not affect correctness: if m2 is
used by the current program, the same vulnerability report will be
loaded for m2. This is what vuln client of this repo does.

Change-Id: I752bb96d0b01751c68052f85d8e192bbde37312e
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/430684
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/cmd/govulncheck/testdata/manystacks-verbose.ct b/cmd/govulncheck/testdata/manystacks-verbose.ct
index f16cc25..ae6980a 100644
--- a/cmd/govulncheck/testdata/manystacks-verbose.ct
+++ b/cmd/govulncheck/testdata/manystacks-verbose.ct
@@ -1155,3 +1155,19 @@
   Found in: github.com/shiyanhui/dht@v0.0.0-20201219151056-5a20f3199263
   Fixed in: N/A
   More info: https://pkg.go.dev/vuln/GO-2020-0040
+
+=== Informational ===
+
+The vulnerabilities below are in packages that you import, but your code
+doesn't appear to call any vulnerable functions. You may not need to take any
+action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
+for details.
+
+Vulnerability #1: GO-2022-0969
+  HTTP/2 server connections can hang forever waiting for a clean shutdown
+  that was preempted by a fatal error. This condition can be exploited
+  by a malicious client to cause a denial of service.
+
+  Found in: net/http@go1.18
+  Fixed in: net/http@go1.19.1
+  More info: https://pkg.go.dev/vuln/GO-2022-0969
diff --git a/cmd/govulncheck/testdata/manystacks.ct b/cmd/govulncheck/testdata/manystacks.ct
index e8c7593..00e0354 100644
--- a/cmd/govulncheck/testdata/manystacks.ct
+++ b/cmd/govulncheck/testdata/manystacks.ct
@@ -18,3 +18,19 @@
   Found in: github.com/shiyanhui/dht@v0.0.0-20201219151056-5a20f3199263
   Fixed in: N/A
   More info: https://pkg.go.dev/vuln/GO-2020-0040
+
+=== Informational ===
+
+The vulnerabilities below are in packages that you import, but your code
+doesn't appear to call any vulnerable functions. You may not need to take any
+action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
+for details.
+
+Vulnerability #1: GO-2022-0969
+  HTTP/2 server connections can hang forever waiting for a clean shutdown
+  that was preempted by a fatal error. This condition can be exploited
+  by a malicious client to cause a denial of service.
+
+  Found in: net/http@go1.18
+  Fixed in: net/http@go1.19.1
+  More info: https://pkg.go.dev/vuln/GO-2022-0969
diff --git a/cmd/govulncheck/testdata/modules/multimodvuln/go.mod b/cmd/govulncheck/testdata/modules/multimodvuln/go.mod
new file mode 100644
index 0000000..81092b9
--- /dev/null
+++ b/cmd/govulncheck/testdata/modules/multimodvuln/go.mod
@@ -0,0 +1,7 @@
+module golang.org/multimodvuln
+
+go 1.18
+
+require golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
+
+require golang.org/x/text v0.3.7 // indirect
diff --git a/cmd/govulncheck/testdata/modules/multimodvuln/go.sum b/cmd/govulncheck/testdata/modules/multimodvuln/go.sum
new file mode 100644
index 0000000..ce35e63
--- /dev/null
+++ b/cmd/govulncheck/testdata/modules/multimodvuln/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff --git a/cmd/govulncheck/testdata/modules/multimodvuln/vuln.go b/cmd/govulncheck/testdata/modules/multimodvuln/vuln.go
new file mode 100644
index 0000000..e1c5b8a
--- /dev/null
+++ b/cmd/govulncheck/testdata/modules/multimodvuln/vuln.go
@@ -0,0 +1,5 @@
+package vuln
+
+import _ "golang.org/x/net/http2"
+
+func main() {}
diff --git a/cmd/govulncheck/testdata/multi-module.ct b/cmd/govulncheck/testdata/multi-module.ct
new file mode 100644
index 0000000..c191c53
--- /dev/null
+++ b/cmd/govulncheck/testdata/multi-module.ct
@@ -0,0 +1,24 @@
+# Test fix correctness for vulns affecting multiple modules
+
+$ cdmodule multimodvuln
+$ govulncheck .
+govulncheck is an experimental tool. Share feedback at https://go.dev/s/govulncheck-feedback.
+
+Scanning for dependencies with known vulnerabilities...
+No vulnerabilities found.
+
+=== Informational ===
+
+The vulnerabilities below are in packages that you import, but your code
+doesn't appear to call any vulnerable functions. You may not need to take any
+action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
+for details.
+
+Vulnerability #1: GO-2022-0969
+  HTTP/2 server connections can hang forever waiting for a clean shutdown
+  that was preempted by a fatal error. This condition can be exploited
+  by a malicious client to cause a denial of service.
+
+  Found in: golang.org/x/net/http2@v0.0.0-20220425223048-2871e0cb64e4
+  Fixed in: golang.org/x/net/http2@v0.0.0-20220906165146-f3363e06e74c
+  More info: https://pkg.go.dev/vuln/GO-2022-0969
diff --git a/cmd/govulncheck/testdata/vulndb/golang.org/x/net.json b/cmd/govulncheck/testdata/vulndb/golang.org/x/net.json
new file mode 100644
index 0000000..093054d
--- /dev/null
+++ b/cmd/govulncheck/testdata/vulndb/golang.org/x/net.json
@@ -0,0 +1 @@
+[{"id":"GO-2022-0969","published":"2022-09-12T20:23:06Z","modified":"2022-09-12T20:23:06Z","aliases":["CVE-2022-27664"],"details":"HTTP/2 server connections can hang forever waiting for a clean shutdown\nthat was preempted by a fatal error. This condition can be exploited\nby a malicious client to cause a denial of service.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.18.6"},{"introduced":"1.19.0"},{"fixed":"1.19.1"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0969"},"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["ListenAndServe","ListenAndServeTLS","Serve","ServeTLS","Server.ListenAndServe","Server.ListenAndServeTLS","Server.Serve","Server.ServeTLS","http2Server.ServeConn","http2serverConn.goAway"]}]}},{"package":{"name":"golang.org/x/net","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20220906165146-f3363e06e74c"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0969"},"ecosystem_specific":{"imports":[{"path":"golang.org/x/net/http2","symbols":["Server.ServeConn","serverConn.goAway"]}]}}],"references":[{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/x49AQzIVX-s"},{"type":"REPORT","url":"https://go.dev/issue/54658"},{"type":"FIX","url":"https://go.dev/cl/428735"}]}]
diff --git a/cmd/govulncheck/testdata/vulndb/stdlib.json b/cmd/govulncheck/testdata/vulndb/stdlib.json
index 756727b..7a1de40 100644
--- a/cmd/govulncheck/testdata/vulndb/stdlib.json
+++ b/cmd/govulncheck/testdata/vulndb/stdlib.json
@@ -1 +1 @@
-[{"id":"STD","affected":[{"package":{"name":"stdlib"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["OpenReader"]}]}}]}]
+[{"id":"STD","affected":[{"package":{"name":"stdlib"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["OpenReader"]}]}}]},{"id":"GO-2022-0969","published":"2022-09-12T20:23:06Z","modified":"2022-09-12T20:23:06Z","aliases":["CVE-2022-27664"],"details":"HTTP/2 server connections can hang forever waiting for a clean shutdown\nthat was preempted by a fatal error. This condition can be exploited\nby a malicious client to cause a denial of service.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.18.6"},{"introduced":"1.19.0"},{"fixed":"1.19.1"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0969"},"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["ListenAndServe","ListenAndServeTLS","Serve","ServeTLS","Server.ListenAndServe","Server.ListenAndServeTLS","Server.Serve","Server.ServeTLS","http2Server.ServeConn","http2serverConn.goAway"]}]}},{"package":{"name":"golang.org/x/net","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20220906165146-f3363e06e74c"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0969"},"ecosystem_specific":{"imports":[{"path":"golang.org/x/net/http2","symbols":["Server.ServeConn","serverConn.goAway"]}]}}],"references":[{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/x49AQzIVX-s"},{"type":"REPORT","url":"https://go.dev/issue/54658"},{"type":"FIX","url":"https://go.dev/cl/428735"}]}]
diff --git a/vulncheck/vulncheck.go b/vulncheck/vulncheck.go
index 728a15d..3b72f2c 100644
--- a/vulncheck/vulncheck.go
+++ b/vulncheck/vulncheck.go
@@ -317,6 +317,16 @@
 		for _, v := range mod.vulns {
 			var filteredAffected []osv.Affected
 			for _, a := range v.Affected {
+				// Vulnerabilities from some databases might contain
+				// information on related but different modules that
+				// were, say, reported in the same CVE. We filter such
+				// information out as it might lead to incorrect results:
+				// Computing a latest fix could consider versions of these
+				// different packages.
+				if a.Package.Name != module.Path {
+					continue
+				}
+
 				// A module version is affected if
 				//  - it is included in one of the affected version ranges
 				//  - and module version is not ""
diff --git a/vulncheck/vulncheck_test.go b/vulncheck/vulncheck_test.go
index f003391..aa95b12 100644
--- a/vulncheck/vulncheck_test.go
+++ b/vulncheck/vulncheck_test.go
@@ -23,20 +23,21 @@
 			},
 			vulns: []*osv.Entry{
 				{ID: "a", Affected: []osv.Affected{
-					{Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}},
-					{Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "0.9.0"}}}}}, // should be filtered out
+					{Package: osv.Package{Name: "example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}},
+					{Package: osv.Package{Name: "a.example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}, // should be filtered out
+					{Package: osv.Package{Name: "example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "0.9.0"}}}}},       // should be filtered out
 				}},
-				{ID: "b", Affected: []osv.Affected{{Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.1"}}}}, EcosystemSpecific: osv.EcosystemSpecific{
-					Imports: []osv.EcosystemSpecificImport{{
+				{ID: "b", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.1"}}}},
+					EcosystemSpecific: osv.EcosystemSpecific{Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"windows", "linux"},
 					}},
-				}}}},
-				{ID: "c", Affected: []osv.Affected{{Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}, EcosystemSpecific: osv.EcosystemSpecific{
-					Imports: []osv.EcosystemSpecificImport{{
+					}}}},
+				{ID: "c", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}},
+					EcosystemSpecific: osv.EcosystemSpecific{Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"arm64", "amd64"},
 					}},
-				}}}},
-				{ID: "d", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+					}}}},
+				{ID: "d", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"windows"},
 					}},
@@ -49,22 +50,22 @@
 				Version: "v1.0.0",
 			},
 			vulns: []*osv.Entry{
-				{ID: "e", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "e", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"arm64"},
 					}},
 				}}}},
-				{ID: "f", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "f", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"linux"},
 					}},
 				}}}},
-				{ID: "g", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "g", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"amd64"},
 					}},
 				}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.1"}}}}}}},
-				{ID: "h", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "h", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"windows"}, GOARCH: []string{"amd64"},
 					}},
@@ -76,12 +77,12 @@
 				Path: "example.mod/c",
 			},
 			vulns: []*osv.Entry{
-				{ID: "i", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "i", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"amd64"},
 					}},
 				}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.0"}}}}}}},
-				{ID: "j", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "j", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"amd64"},
 					}},
@@ -96,12 +97,12 @@
 			},
 			vulns: []*osv.Entry{
 				{ID: "l", Affected: []osv.Affected{
-					{EcosystemSpecific: osv.EcosystemSpecific{
+					{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
 						Imports: []osv.EcosystemSpecificImport{{
 							GOOS: []string{"windows"}, // should be filtered out
 						}},
 					}},
-					{EcosystemSpecific: osv.EcosystemSpecific{
+					{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
 						Imports: []osv.EcosystemSpecificImport{{
 							GOOS: []string{"linux"},
 						}},
@@ -118,8 +119,8 @@
 				Version: "v1.0.0",
 			},
 			vulns: []*osv.Entry{
-				{ID: "a", Affected: []osv.Affected{{Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}}},
-				{ID: "c", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "a", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/a"}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}}},
+				{ID: "c", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"arm64", "amd64"},
 					}},
@@ -132,12 +133,12 @@
 				Version: "v1.0.0",
 			},
 			vulns: []*osv.Entry{
-				{ID: "f", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "f", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"linux"},
 					}},
 				}}}},
-				{ID: "g", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "g", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOARCH: []string{"amd64"},
 					}},
@@ -155,7 +156,7 @@
 				Version: "v1.2.0",
 			},
 			vulns: []*osv.Entry{
-				{ID: "l", Affected: []osv.Affected{{EcosystemSpecific: osv.EcosystemSpecific{
+				{ID: "l", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
 					Imports: []osv.EcosystemSpecificImport{{
 						GOOS: []string{"linux"},
 					}},