blob: aa95b125d167cdb77d1951b5eea8b1f526d4b266 [file] [log] [blame]
// 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 (
"path"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/vuln/osv"
)
func TestFilterVulns(t *testing.T) {
mv := moduleVulnerabilities{
{
mod: &Module{
Path: "example.mod/a",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{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"}}}}},
{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{{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{{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{{Package: osv.Package{Name: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"windows"},
}},
}}}},
},
},
{
mod: &Module{
Path: "example.mod/b",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{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{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"linux"},
}},
}}}},
{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{{Package: osv.Package{Name: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"windows"}, GOARCH: []string{"amd64"},
}},
}}}},
},
},
{
mod: &Module{
Path: "example.mod/c",
},
vulns: []*osv.Entry{
{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{{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{{Fixed: "3.0.0"}}}}}}},
{ID: "k"},
},
},
{
mod: &Module{
Path: "example.mod/d",
Version: "v1.2.0",
},
vulns: []*osv.Entry{
{ID: "l", Affected: []osv.Affected{
{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"windows"}, // should be filtered out
}},
}},
{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"linux"},
}},
}},
}},
},
},
}
expected := moduleVulnerabilities{
{
mod: &Module{
Path: "example.mod/a",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{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"},
}},
}, Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}}}},
},
},
{
mod: &Module{
Path: "example.mod/b",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{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{{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"}}}}}}},
},
},
{
mod: &Module{
Path: "example.mod/c",
},
},
{
mod: &Module{
Path: "example.mod/d",
Version: "v1.2.0",
},
vulns: []*osv.Entry{
{ID: "l", Affected: []osv.Affected{{Package: osv.Package{Name: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
GOOS: []string{"linux"},
}},
}}}},
},
},
}
filtered := mv.filter("linux", "amd64")
if diff := diffModuleVulnerabilities(expected, filtered); diff != "" {
t.Fatalf("Filter returned unexpected results (-want,+got):\n%s", diff)
}
}
func diffModuleVulnerabilities(a, b moduleVulnerabilities) string {
return cmp.Diff(a, b, cmp.Exporter(func(t reflect.Type) bool {
return reflect.TypeOf(moduleVulnerabilities{}) == t || reflect.TypeOf(modVulns{}) == t
}))
}
func TestVulnsForPackage(t *testing.T) {
mv := moduleVulnerabilities{
{
mod: &Module{
Path: "example.mod/a",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "a", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
}},
},
}}},
},
},
{
mod: &Module{
Path: "example.mod/a/b",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "b", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
}},
},
}}},
},
},
{
mod: &Module{
Path: "example.mod/d",
Version: "v0.0.1",
},
vulns: []*osv.Entry{
{ID: "d", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/d"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/d",
}},
},
}}},
},
},
}
filtered := mv.vulnsForPackage("example.mod/a/b/c")
expected := []*osv.Entry{
{ID: "b", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
}},
},
}}},
}
if !reflect.DeepEqual(filtered, expected) {
t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected))
}
}
func TestVulnsForPackageReplaced(t *testing.T) {
mv := moduleVulnerabilities{
{
mod: &Module{
Path: "example.mod/a",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "a", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
}},
},
}}},
},
},
{
mod: &Module{
Path: "example.mod/a/b",
Replace: &Module{
Path: "example.mod/b",
},
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "c", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/b/c",
}},
},
}}},
},
},
}
filtered := mv.vulnsForPackage("example.mod/a/b/c")
expected := []*osv.Entry{
{ID: "c", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/b/c",
}},
},
}}},
}
if !reflect.DeepEqual(filtered, expected) {
t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected))
}
}
func TestVulnsForSymbol(t *testing.T) {
mv := moduleVulnerabilities{
{
mod: &Module{
Path: "example.mod/a",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "a", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
}},
},
}}},
},
},
{
mod: &Module{
Path: "example.mod/a/b",
Version: "v1.0.0",
},
vulns: []*osv.Entry{
{ID: "b", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
Symbols: []string{"a"},
}},
},
}}},
{ID: "c", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
Symbols: []string{"b"},
}},
},
}}},
},
},
}
filtered := mv.vulnsForSymbol("example.mod/a/b/c", "a")
expected := []*osv.Entry{
{ID: "b", Affected: []osv.Affected{{
Package: osv.Package{Name: "example.mod/a/b"},
EcosystemSpecific: osv.EcosystemSpecific{
Imports: []osv.EcosystemSpecificImport{{
Path: "example.mod/a/b/c",
Symbols: []string{"a"},
}},
},
}}},
}
if !reflect.DeepEqual(filtered, expected) {
t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected))
}
}
func TestConvert(t *testing.T) {
e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
{
Name: "golang.org/entry",
Files: map[string]interface{}{
"x/x.go": `
package x
import _ "golang.org/amod/avuln"
`}},
{
Name: "golang.org/zmod@v0.0.0",
Files: map[string]interface{}{"z/z.go": `
package z
`},
},
{
Name: "golang.org/amod@v1.1.3",
Files: map[string]interface{}{"avuln/avuln.go": `
package avuln
import _ "golang.org/wmod/w"
`},
},
{
Name: "golang.org/bmod@v0.5.0",
Files: map[string]interface{}{"bvuln/bvuln.go": `
package bvuln
`},
},
{
Name: "golang.org/wmod@v0.0.0",
Files: map[string]interface{}{"w/w.go": `
package w
import _ "golang.org/bmod/bvuln"
`},
},
})
defer e.Cleanup()
// Load x as entry package.
pkgs, err := loadPackages(e, path.Join(e.Temp(), "entry/x"))
if err != nil {
t.Fatal(err)
}
vpkgs := Convert(pkgs)
wantPkgs := map[string][]string{
"golang.org/amod/avuln": {"golang.org/wmod/w"},
"golang.org/bmod/bvuln": nil,
"golang.org/entry/x": {"golang.org/amod/avuln"},
"golang.org/wmod/w": {"golang.org/bmod/bvuln"},
}
if got := pkgPathToImports(vpkgs); !reflect.DeepEqual(got, wantPkgs) {
t.Errorf("want %v;got %v", wantPkgs, got)
}
wantMods := map[string]string{
"golang.org/amod": "v1.1.3",
"golang.org/bmod": "v0.5.0",
"golang.org/entry": "",
"golang.org/wmod": "v0.0.0",
}
if got := modulePathToVersion(vpkgs); !reflect.DeepEqual(got, wantMods) {
t.Errorf("want %v;got %v", wantMods, got)
}
}