mirror of https://github.com/golang/go.git
gopls/internal/vulncheck: include nonaffecting vulnerability info
This info is still useful to tell users that some required modules have known vulnerabilities, but the analyzed packages/workspaces are not affected. Those vulnerabilities are missing Symbol/PkgPath/CallStacks. Change-Id: I94ea0d8f9ebcb1270e05f055caff2a18ebacd034 Reviewed-on: https://go-review.googlesource.com/c/tools/+/412457 Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
parent
e8b9ff1291
commit
d097bc9f9d
|
|
@ -11,12 +11,15 @@ import (
|
|||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
gvc "golang.org/x/tools/gopls/internal/govulncheck"
|
||||
"golang.org/x/tools/internal/lsp/command"
|
||||
"golang.org/x/vuln/client"
|
||||
"golang.org/x/vuln/osv"
|
||||
"golang.org/x/vuln/vulncheck"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -79,29 +82,84 @@ func (c *cmd) Run(ctx context.Context, cfg *packages.Config, patterns ...string)
|
|||
}
|
||||
log.Printf("loaded %d packages\n", len(loadedPkgs))
|
||||
|
||||
r, err := gvc.Source(ctx, loadedPkgs, c.Client)
|
||||
log.Printf("analyzing %d packages...\n", len(loadedPkgs))
|
||||
|
||||
r, err := vulncheck.Source(ctx, loadedPkgs, &vulncheck.Config{Client: c.Client})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unaffectedMods := filterUnaffected(r.Vulns)
|
||||
r.Vulns = filterCalled(r)
|
||||
|
||||
callInfo := gvc.GetCallInfo(r, loadedPkgs)
|
||||
return toVulns(callInfo)
|
||||
return toVulns(callInfo, unaffectedMods)
|
||||
// TODO: add import graphs.
|
||||
}
|
||||
|
||||
func toVulns(ci *gvc.CallInfo) ([]Vuln, error) {
|
||||
// filterCalled returns vulnerabilities where the symbols are actually called.
|
||||
func filterCalled(r *vulncheck.Result) []*vulncheck.Vuln {
|
||||
var vulns []*vulncheck.Vuln
|
||||
for _, v := range r.Vulns {
|
||||
if v.CallSink != 0 {
|
||||
vulns = append(vulns, v)
|
||||
}
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
// filterUnaffected returns vulnerabilities where no symbols are called,
|
||||
// grouped by module.
|
||||
func filterUnaffected(vulns []*vulncheck.Vuln) map[string][]*osv.Entry {
|
||||
// It is possible that the same vuln.OSV.ID has vuln.CallSink != 0
|
||||
// for one symbol, but vuln.CallSink == 0 for a different one, so
|
||||
// we need to filter out ones that have been called.
|
||||
called := map[string]bool{}
|
||||
for _, vuln := range vulns {
|
||||
if vuln.CallSink != 0 {
|
||||
called[vuln.OSV.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
modToIDs := map[string]map[string]*osv.Entry{}
|
||||
for _, vuln := range vulns {
|
||||
if !called[vuln.OSV.ID] {
|
||||
if _, ok := modToIDs[vuln.ModPath]; !ok {
|
||||
modToIDs[vuln.ModPath] = map[string]*osv.Entry{}
|
||||
}
|
||||
// keep only one vuln.OSV instance for the same ID.
|
||||
modToIDs[vuln.ModPath][vuln.OSV.ID] = vuln.OSV
|
||||
}
|
||||
}
|
||||
output := map[string][]*osv.Entry{}
|
||||
for m, vulnSet := range modToIDs {
|
||||
var vulns []*osv.Entry
|
||||
for _, vuln := range vulnSet {
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
sort.Slice(vulns, func(i, j int) bool { return vulns[i].ID < vulns[j].ID })
|
||||
output[m] = vulns
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func fixed(v *osv.Entry) string {
|
||||
lf := gvc.LatestFixed(v.Affected)
|
||||
if lf != "" && lf[0] != 'v' {
|
||||
lf = "v" + lf
|
||||
}
|
||||
return lf
|
||||
}
|
||||
|
||||
func toVulns(ci *gvc.CallInfo, unaffectedMods map[string][]*osv.Entry) ([]Vuln, error) {
|
||||
var vulns []Vuln
|
||||
|
||||
for _, vg := range ci.VulnGroups {
|
||||
v0 := vg[0]
|
||||
lf := gvc.LatestFixed(v0.OSV.Affected)
|
||||
if lf != "" && lf[0] != 'v' {
|
||||
lf = "v" + lf
|
||||
}
|
||||
vuln := Vuln{
|
||||
ID: v0.OSV.ID,
|
||||
PkgPath: v0.PkgPath,
|
||||
CurrentVersion: ci.ModuleVersions[v0.ModPath],
|
||||
FixedVersion: lf,
|
||||
FixedVersion: fixed(v0.OSV),
|
||||
Details: v0.OSV.Details,
|
||||
|
||||
Aliases: v0.OSV.Aliases,
|
||||
|
|
@ -119,5 +177,19 @@ func toVulns(ci *gvc.CallInfo) ([]Vuln, error) {
|
|||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
for m, vg := range unaffectedMods {
|
||||
for _, v0 := range vg {
|
||||
vuln := Vuln{
|
||||
ID: v0.ID,
|
||||
Details: v0.Details,
|
||||
Aliases: v0.Aliases,
|
||||
ModPath: m,
|
||||
URL: href(v0),
|
||||
CurrentVersion: "",
|
||||
FixedVersion: fixed(v0),
|
||||
}
|
||||
vulns = append(vulns, vuln)
|
||||
}
|
||||
}
|
||||
return vulns, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,15 @@ func TestCmd_Run(t *testing.T) {
|
|||
"golang.org/bmod/bvuln.Vuln (bvuln.go:2)\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
Vuln: Vuln{
|
||||
ID: "GO-2022-03",
|
||||
Details: "unaffecting vulnerability",
|
||||
ModPath: "golang.org/amod",
|
||||
URL: "https://pkg.go.dev/vuln/GO-2022-03",
|
||||
FixedVersion: "v1.0.4",
|
||||
},
|
||||
},
|
||||
}
|
||||
// sort reports for stability before comparison.
|
||||
for _, rpts := range [][]report{got, want} {
|
||||
|
|
@ -228,6 +237,21 @@ var testClient1 = &mockClient{
|
|||
EcosystemSpecific: osv.EcosystemSpecific{Symbols: []string{"VulnData.Vuln1", "VulnData.Vuln2"}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
ID: "GO-2022-03",
|
||||
Details: "unaffecting vulnerability",
|
||||
References: []osv.Reference{
|
||||
{
|
||||
Type: "href",
|
||||
URL: "pkg.go.dev/vuln/GO-2022-01",
|
||||
},
|
||||
},
|
||||
Affected: []osv.Affected{{
|
||||
Package: osv.Package{Name: "golang.org/amod/avuln"},
|
||||
Ranges: osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.4"}, {Introduced: "1.1.2"}}}},
|
||||
EcosystemSpecific: osv.EcosystemSpecific{Symbols: []string{"nonExisting"}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
"golang.org/bmod": {
|
||||
{
|
||||
|
|
|
|||
|
|
@ -359,8 +359,10 @@ type Vuln struct {
|
|||
Aliases []string `json:",omitempty"`
|
||||
|
||||
// Symbol is the name of the detected vulnerable function or method.
|
||||
// Can be empty if the vulnerability exists in required modules, but no vulnerable symbols are used.
|
||||
Symbol string `json:",omitempty"`
|
||||
// PkgPath is the package path of the detected Symbol.
|
||||
// Can be empty if the vulnerability exists in required modules, but no vulnerable packages are used.
|
||||
PkgPath string `json:",omitempty"`
|
||||
// ModPath is the module path corresponding to PkgPath.
|
||||
// TODO: how do we specify standard library's vulnerability?
|
||||
|
|
|
|||
Loading…
Reference in New Issue