diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 37324b856153..1d5b5dee4448 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -1944,6 +1944,9 @@ linters-settings: - T any - m map[string]int + vulncheck: + vuln-database: [https://vuln.go.dev] + whitespace: # Enforces newlines (or comments) after every multi-line if statement. # Default: false @@ -2159,6 +2162,7 @@ linters: - usestdlibvars - varcheck - varnamelen + - vulncheck - wastedassign - whitespace - wrapcheck @@ -2272,6 +2276,7 @@ linters: - usestdlibvars - varcheck - varnamelen + - vulncheck - wastedassign - whitespace - wrapcheck diff --git a/go.mod b/go.mod index a9a44d009b96..6d00c2f3c0d2 100644 --- a/go.mod +++ b/go.mod @@ -113,7 +113,9 @@ require ( github.com/ykadowak/zerologlint v0.1.1 gitlab.com/bosi/decorder v0.2.3 go.tmz.dev/musttag v0.6.0 + golang.org/x/net v0.9.0 golang.org/x/tools v0.8.0 + golang.org/x/vuln v0.0.0-20220902211423-27dd78d2ca39 gopkg.in/yaml.v3 v3.0.1 honnef.co/go/tools v0.4.3 mvdan.cc/gofumpt v0.5.0 @@ -187,7 +189,7 @@ require ( golang.org/x/mod v0.10.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index fbf7a3e3d398..06cd382886a4 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,7 @@ github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXa github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -691,6 +692,7 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -798,8 +800,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -874,6 +877,8 @@ golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/vuln v0.0.0-20220902211423-27dd78d2ca39 h1:501+NfNjDh4IT4HOzdeezTOFD7njtY49aXJN1oY3E1s= +golang.org/x/vuln v0.0.0-20220902211423-27dd78d2ca39/go.mod h1:7tDfEDtOLlzHQRi4Yzfg5seVBSvouUIjyPzBx4q5CxQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 9386b4631e19..2ef051192e04 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -224,6 +224,7 @@ type LintersSettings struct { Whitespace WhitespaceSettings Wrapcheck WrapcheckSettings WSL WSLSettings + Vulncheck VulncheckSettings Custom map[string]CustomLinterSettings } @@ -744,6 +745,10 @@ type VarnamelenSettings struct { IgnoreDecls []string `mapstructure:"ignore-decls"` } +type VulncheckSettings struct { + VulnDatabase []string `mapstructure:"vuln-database"` +} + type WhitespaceSettings struct { MultiIf bool `mapstructure:"multi-if"` MultiFunc bool `mapstructure:"multi-func"` diff --git a/pkg/golinters/vulncheck.go b/pkg/golinters/vulncheck.go new file mode 100644 index 000000000000..62c852d197a2 --- /dev/null +++ b/pkg/golinters/vulncheck.go @@ -0,0 +1,106 @@ +package golinters + +import ( + "fmt" + "strings" + "sync" + + "golang.org/x/net/context" + "golang.org/x/tools/go/analysis" + "golang.org/x/vuln/client" + "golang.org/x/vuln/vulncheck" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const ( + vulncheckName = "vulncheck" + vulncheckDoc = "Package vulncheck detects uses of known vulnerabilities in Go programs." +) + +func NewVulncheck(settings *config.VulncheckSettings) *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: vulncheckName, + Doc: vulncheckDoc, + Run: goanalysis.DummyRun, + } + + return goanalysis.NewLinter( + "vulncheck", + "Package vulncheck detects uses of known vulnerabilities in Go programs.", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + issues, err := vulncheckRun(lintCtx, pass, settings) + if err != nil { + return nil, err + } + + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return resIssues + }) +} + +func vulncheckRun(lintCtx *linter.Context, pass *analysis.Pass, settings *config.VulncheckSettings) ([]goanalysis.Issue, error) { + dbs := []string{"https://vuln.go.dev"} + if len(settings.VulnDatabase) > 0 { + dbs = settings.VulnDatabase + } + dbClient, err := client.NewClient(dbs, client.Options{}) + if err != nil { + return nil, err + } + + vcfg := &vulncheck.Config{Client: dbClient, SourceGoVersion: lintCtx.Cfg.Run.Go} + vpkgs := vulncheck.Convert(lintCtx.Packages) + ctx := context.Background() + + r, err := vulncheck.Source(ctx, vpkgs, vcfg) + if err != nil { + return nil, err + } + + imports := vulncheck.ImportChains(r) + issues := make([]goanalysis.Issue, 0, len(r.Vulns)) + + for idx, vuln := range r.Vulns { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + Text: writeVulnerability(idx, vuln.OSV.ID, vuln.OSV.Details, writeImports(imports[vuln])), + }, pass)) + } + + return issues, nil +} + +func writeImports(imports []vulncheck.ImportChain) string { + var s strings.Builder + for _, i := range imports { + indent := 0 + for _, pkg := range i { + s.WriteString(fmt.Sprintf("%s|_ %s", strings.Repeat(" ", indent), pkg.Name)) + } + } + + return s.String() +} + +func writeVulnerability(idx int, id, details, imports string) string { + return fmt.Sprintf(`Vulnerability #%d: %s +%s +%s + More info: https://pkg.go.dev/vuln/%s +`, idx, id, details, imports, id) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index ffe10721cf43..428a35f22d9e 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -178,6 +178,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { whitespaceCfg *config.WhitespaceSettings wrapcheckCfg *config.WrapcheckSettings wslCfg *config.WSLSettings + vulncheckCfg *config.VulncheckSettings ) if m.cfg != nil { @@ -258,6 +259,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { whitespaceCfg = &m.cfg.LintersSettings.Whitespace wrapcheckCfg = &m.cfg.LintersSettings.Wrapcheck wslCfg = &m.cfg.LintersSettings.WSL + vulncheckCfg = &m.cfg.LintersSettings.Vulncheck if govetCfg != nil { govetCfg.Go = m.cfg.Run.Go @@ -897,6 +899,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetBugs). WithLoadForGoAnalysis(). WithURL("https://github.com/ykadowak/zerologlint"), + + linter.NewConfig(golinters.NewVulncheck(vulncheckCfg)). + WithSince("v1.53.0"). + WithPresets(linter.PresetModule). + WithURL("https://vuln.go.dev/"), } enabledByDefault := map[string]bool{ diff --git a/test/linters_test.go b/test/linters_test.go index dd130db3e7db..d519b2ac0f4f 100644 --- a/test/linters_test.go +++ b/test/linters_test.go @@ -32,6 +32,7 @@ func TestSourcesFromTestdataSubDir(t *testing.T) { "loggercheck", "ginkgolinter", "zerologlint", + "vulncheck", } for _, dir := range subDirs { diff --git a/test/testdata/vulncheck/go.mod b/test/testdata/vulncheck/go.mod new file mode 100644 index 000000000000..53b79c11c515 --- /dev/null +++ b/test/testdata/vulncheck/go.mod @@ -0,0 +1,5 @@ +module vulncheck + +go 1.19 + +require golang.org/x/text v0.3.7 diff --git a/test/testdata/vulncheck/go.sum b/test/testdata/vulncheck/go.sum new file mode 100644 index 000000000000..1f78e039072f --- /dev/null +++ b/test/testdata/vulncheck/go.sum @@ -0,0 +1,2 @@ +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/test/testdata/vulncheck/vulncheck.go b/test/testdata/vulncheck/vulncheck.go new file mode 100644 index 000000000000..0b0aecc7a93f --- /dev/null +++ b/test/testdata/vulncheck/vulncheck.go @@ -0,0 +1,13 @@ +//golangcitest:args -Evulncheck +package vulncheck + +import ( + "fmt" + + "golang.org/x/text/language" +) + +func ParseRegion() { + us := language.MustParseRegion("US") + fmt.Println(us) +}