| // 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. |
| |
| package vulncheck |
| |
| import ( |
| "reflect" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/vuln/internal/osv" |
| ) |
| |
| func TestFilterVulns(t *testing.T) { |
| past := time.Now().Add(-3 * time.Hour) |
| mv := []*ModVulns{ |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "a", Affected: []osv.Affected{ |
| {Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}, |
| {Module: osv.Module{Path: "a.example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}, // should be filtered out |
| {Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "0.9.0"}}}}}, // should be filtered out |
| }}, |
| {ID: "b", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.1"}}}}, |
| EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ |
| GOOS: []string{"windows", "linux"}, |
| }}, |
| }}}}, |
| {ID: "c", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}, |
| EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ |
| GOARCH: []string{"arm64", "amd64"}, |
| }}, |
| }}}}, |
| {ID: "d", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"windows"}, |
| }}, |
| }}}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/b", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "e", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"arm64"}, |
| }}, |
| }}}}, |
| {ID: "f", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}}}, |
| {ID: "g", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"amd64"}, |
| }}, |
| }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.1"}}}}}}}, |
| {ID: "h", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"windows"}, GOARCH: []string{"amd64"}, |
| }}, |
| }}}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/c", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "i", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"amd64"}, |
| }}, |
| }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.0"}}}}}}}, |
| {ID: "j", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"amd64"}, |
| }}, |
| }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "3.0.0"}}}}}}}, |
| {ID: "k"}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/d", |
| Version: "v1.2.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "l", Affected: []osv.Affected{ |
| {Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"windows"}, // should be filtered out |
| }}, |
| }}, |
| {Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}, |
| }}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/w", |
| Version: "v1.3.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "m", Withdrawn: &past, Affected: []osv.Affected{ // should be filtered out |
| {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}, |
| }}, |
| {ID: "n", Affected: []osv.Affected{ |
| {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}, |
| }}, |
| }, |
| }, |
| } |
| |
| want := affectingVulns{ |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "a", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}}}, |
| {ID: "c", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"arm64", "amd64"}, |
| }}, |
| }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}}}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/b", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "f", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}}}, |
| {ID: "g", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOARCH: []string{"amd64"}, |
| }}, |
| }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.1"}}}}}}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/c", |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/d", |
| Version: "v1.2.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "l", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/w", |
| Version: "v1.3.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "n", Affected: []osv.Affected{ |
| {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| GOOS: []string{"linux"}, |
| }}, |
| }}, |
| }}, |
| }, |
| }, |
| } |
| |
| got := affectingVulnerabilities(mv, "linux", "amd64") |
| if diff := cmp.Diff(want, got, cmp.Exporter(func(t reflect.Type) bool { |
| return reflect.TypeOf(affectingVulns{}) == t || reflect.TypeOf(ModVulns{}) == t |
| })); diff != "" { |
| t.Errorf("(-want,+got):\n%s", diff) |
| } |
| } |
| |
| func TestVulnsForPackage(t *testing.T) { |
| aff := affectingVulns{ |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "a", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a/b", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "b", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/d", |
| Version: "v0.0.1", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "d", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/d"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/d", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| } |
| |
| got := aff.ForPackage("example.mod/a/b", "example.mod/a/b/c") |
| want := []*osv.Entry{ |
| {ID: "b", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| }}, |
| }, |
| }}}, |
| } |
| |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("(-want,+got):\n%s", diff) |
| } |
| } |
| |
| func TestVulnsForPackageReplaced(t *testing.T) { |
| aff := affectingVulns{ |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "a", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a/b", |
| Replace: &packages.Module{ |
| Path: "example.mod/b", |
| }, |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "c", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/b/c", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| } |
| |
| got := aff.ForPackage("example.mod/a/b", "example.mod/a/b/c") |
| want := []*osv.Entry{ |
| {ID: "c", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/b/c", |
| }}, |
| }, |
| }}}, |
| } |
| |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("(-want,+got):\n%s", diff) |
| } |
| } |
| |
| func TestVulnsForSymbol(t *testing.T) { |
| aff := affectingVulns{ |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "a", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| { |
| Module: &packages.Module{ |
| Path: "example.mod/a/b", |
| Version: "v1.0.0", |
| }, |
| Vulns: []*osv.Entry{ |
| {ID: "b", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| Symbols: []string{"a"}, |
| }}, |
| }, |
| }}}, |
| {ID: "c", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| Symbols: []string{"b"}, |
| }}, |
| }, |
| }}}, |
| }, |
| }, |
| } |
| |
| got := aff.ForSymbol("example.mod/a/b", "example.mod/a/b/c", "a") |
| want := []*osv.Entry{ |
| {ID: "b", Affected: []osv.Affected{{ |
| Module: osv.Module{Path: "example.mod/a/b"}, |
| EcosystemSpecific: osv.EcosystemSpecific{ |
| Packages: []osv.Package{{ |
| Path: "example.mod/a/b/c", |
| Symbols: []string{"a"}, |
| }}, |
| }, |
| }}}, |
| } |
| |
| if diff := cmp.Diff(want, got); diff != "" { |
| t.Errorf("(-want,+got):\n%s", diff) |
| } |
| } |
| |
| func TestReceiver(t *testing.T) { |
| tcs := []struct { |
| name string |
| fn *FuncNode |
| want string |
| }{ |
| { |
| name: "empty", |
| fn: &FuncNode{ |
| RecvType: "", |
| Package: &packages.Package{PkgPath: "example.com/a/pkg"}, |
| }, |
| want: "", |
| }, |
| { |
| name: "pointer", |
| fn: &FuncNode{ |
| RecvType: "*example.com/a/pkg.Atype", |
| Package: &packages.Package{PkgPath: "example.com/a/pkg"}, |
| }, |
| want: "*Atype", |
| }, |
| { |
| name: "not pointer", |
| fn: &FuncNode{ |
| RecvType: "example.com/a/pkg.Atype", |
| Package: &packages.Package{PkgPath: "example.com/a/pkg"}, |
| }, |
| want: "Atype", |
| }, |
| { |
| name: "no prefix", |
| fn: &FuncNode{ |
| RecvType: "Atype", |
| Package: &packages.Package{PkgPath: "example.com/a/pkg"}, |
| }, |
| want: "Atype", |
| }, |
| } |
| |
| for _, tc := range tcs { |
| t.Run(tc.name, func(t *testing.T) { |
| got := tc.fn.Receiver() |
| if got != tc.want { |
| t.Errorf("want %s; got %s", tc.want, got) |
| } |
| }) |
| } |
| } |