Skip to content

Commit

Permalink
gopls/internal/vulncheck: copy logic of govulncheck -html
Browse files Browse the repository at this point in the history
reference commit: b2400d8
The latest relevant change in the code copied is CL 403075.

Change-Id: If50cb4e0096e4f33876236cf8620430e1bcfcd86
Reviewed-on: https://go-review.googlesource.com/c/tools/+/405795
Reviewed-by: Jamal Carvalho <jamal@golang.org>
  • Loading branch information
hyangah committed May 12, 2022
1 parent a518b79 commit f918e87
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 173 deletions.
1 change: 1 addition & 0 deletions gopls/doc/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ Result:
"CurrentVersion": string,
"FixedVersion": string,
"CallStacks": [][]golang.org/x/tools/internal/lsp/command.StackEntry,
"CallStackSummaries": []string,
},
}
```
Expand Down
98 changes: 45 additions & 53 deletions gopls/internal/vulncheck/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package vulncheck

import (
"context"
"fmt"
"log"
"os"
"strings"
Expand Down Expand Up @@ -68,79 +67,72 @@ type cmd struct {

// Run runs the govulncheck after loading packages using the provided packages.Config.
func (c *cmd) Run(ctx context.Context, cfg *packages.Config, patterns ...string) (_ []Vuln, err error) {
// TODO: how&where can we ensure cfg is the right config for the given patterns?

// vulncheck.Source may panic if the packages are incomplete. (e.g. broken code or failed dependency fetch)
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("cannot run vulncheck: %v", r)
}
}()
return c.run(ctx, cfg, patterns)
}

func (c *cmd) run(ctx context.Context, packagesCfg *packages.Config, patterns []string) ([]Vuln, error) {
packagesCfg.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles |
cfg.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles |
packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes |
packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps

log.Println("loading packages...")

loadedPkgs, err := packages.Load(packagesCfg, patterns...)
loadedPkgs, err := packages.Load(cfg, patterns...)
if err != nil {
log.Printf("package load failed: %v", err)
return nil, err
}
log.Printf("loaded %d packages\n", len(loadedPkgs))

pkgs := vulncheck.Convert(loadedPkgs)
res, err := vulncheck.Source(ctx, pkgs, &vulncheck.Config{
Client: c.Client,
ImportsOnly: false,
r, err := vulncheck.Source(ctx, pkgs, &vulncheck.Config{
Client: c.Client,
})
cs := vulncheck.CallStacks(res)
if err != nil {
return nil, err
}

// Skip vulns that are in the import graph but have no calls to them.
var vulns []*vulncheck.Vuln
for _, v := range r.Vulns {
if v.CallSink != 0 {
vulns = append(vulns, v)
}
}

return toVulns(loadedPkgs, cs)
callStacks := vulncheck.CallStacks(r)
// Create set of top-level packages, used to find representative symbols
topPackages := map[string]bool{}
for _, p := range pkgs {
topPackages[p.PkgPath] = true
}
vulnGroups := groupByIDAndPackage(vulns)
moduleVersions := moduleVersionMap(r.Modules)

return toVulns(callStacks, moduleVersions, topPackages, vulnGroups)
// TODO: add import graphs.
}

func packageModule(p *packages.Package) *packages.Module {
m := p.Module
if m == nil {
return nil
}
if r := m.Replace; r != nil {
return r
}
return m
}
func toVulns(callStacks map[*vulncheck.Vuln][]vulncheck.CallStack, moduleVersions map[string]string, topPackages map[string]bool, vulnGroups [][]*vulncheck.Vuln) ([]Vuln, error) {
var vulns []Vuln

func toVulns(pkgs []*packages.Package, callstacks map[*vulncheck.Vuln][]vulncheck.CallStack) ([]Vuln, error) {
// Build a map from module paths to versions.
moduleVersions := map[string]string{}
packages.Visit(pkgs, nil, func(p *packages.Package) {
if m := packageModule(p); m != nil {
moduleVersions[m.Path] = m.Version
for _, vg := range vulnGroups {
v0 := vg[0]
vuln := Vuln{
ID: v0.OSV.ID,
PkgPath: v0.PkgPath,
CurrentVersion: moduleVersions[v0.ModPath],
FixedVersion: latestFixed(v0.OSV.Affected),
Details: v0.OSV.Details,

Aliases: v0.OSV.Aliases,
Symbol: v0.Symbol,
ModPath: v0.ModPath,
URL: href(v0.OSV),
}
})

var vulns []Vuln
for v, trace := range callstacks {
if len(trace) == 0 {
continue
}
vuln := Vuln{
ID: v.OSV.ID,
Details: v.OSV.Details,
Aliases: v.OSV.Aliases,
Symbol: v.Symbol,
PkgPath: v.PkgPath,
ModPath: v.ModPath,
URL: href(v.OSV),
CurrentVersion: moduleVersions[v.ModPath],
FixedVersion: fixedVersion(v.OSV),
CallStacks: toCallStacks(trace),
// Keep first call stack for each vuln.
for _, v := range vg {
if css := callStacks[v]; len(css) > 0 {
vuln.CallStacks = append(vuln.CallStacks, toCallStack(css[0]))
vuln.CallStackSummaries = append(vuln.CallStackSummaries, summarizeCallStack(css[0], topPackages, v.PkgPath))
}
}
vulns = append(vulns, vuln)
}
Expand Down
103 changes: 17 additions & 86 deletions gopls/internal/vulncheck/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"golang.org/x/tools/internal/lsp/tests"
"golang.org/x/vuln/client"
"golang.org/x/vuln/osv"
"golang.org/x/vuln/vulncheck"
)

func TestCmd_Run(t *testing.T) {
Expand Down Expand Up @@ -54,51 +53,40 @@ func TestCmd_Run(t *testing.T) {
URL: "https://pkg.go.dev/vuln/GO-2022-01",
CurrentVersion: "v1.1.3",
FixedVersion: "v1.0.4",
CallStackSummaries: []string{
"golang.org/entry/x.X calls golang.org/amod/avuln.VulnData.Vuln1",
"golang.org/entry/x.X calls golang.org/cmod/c.C1, which eventually calls golang.org/amod/avuln.VulnData.Vuln2",
},
},
CallStacksStr: []string{
"golang.org/cmod/c.I.t0 called from golang.org/entry/x.X [approx.] (x.go:8)\n" +
"golang.org/entry/x.X [approx.] (x.go:8)\n" +
"golang.org/amod/avuln.VulnData.Vuln1 (avuln.go:3)\n",
},
},
{
Vuln: Vuln{
ID: "GO-2022-01",
Symbol: "VulnData.Vuln2",
PkgPath: "golang.org/amod/avuln",
ModPath: "golang.org/amod",
URL: "https://pkg.go.dev/vuln/GO-2022-01",
CurrentVersion: "v1.1.3",
FixedVersion: "v1.0.4",
},
CallStacksStr: []string{
"C1 called from golang.org/entry/x.X (x.go:8)\n" +
"Vuln2 called from golang.org/cmod/c.C1 (c.go:13)\n" +
"golang.org/entry/x.X (x.go:8)\n" +
"golang.org/cmod/c.C1 (c.go:13)\n" +
"golang.org/amod/avuln.VulnData.Vuln2 (avuln.go:4)\n",
},
},
{
Vuln: Vuln{
ID: "GO-2022-02",
Symbol: "Vuln",
PkgPath: "golang.org/bmod/bvuln",
ModPath: "golang.org/bmod",
URL: "https://pkg.go.dev/vuln/GO-2022-02",
CurrentVersion: "v0.5.0",
ID: "GO-2022-02",
Symbol: "Vuln",
PkgPath: "golang.org/bmod/bvuln",
ModPath: "golang.org/bmod",
URL: "https://pkg.go.dev/vuln/GO-2022-02",
CurrentVersion: "v0.5.0",
CallStackSummaries: []string{"golang.org/entry/y.Y calls golang.org/bmod/bvuln.Vuln"},
},
CallStacksStr: []string{
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\n" +
"golang.org/bmod/bvuln.Vuln (bvuln.go:2)\n",
"Y called from golang.org/entry/x.CallY (x.go:12)\n" +
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\n" +
"golang.org/entry/y.Y [approx.] (y.go:5)\n" +
"golang.org/bmod/bvuln.Vuln (bvuln.go:2)\n",
},
},
}
// sort reports for stability before comparison.
for _, rpts := range [][]report{got, want} {
sort.Slice(rpts, func(i, j int) bool {
a, b := got[i], got[j]
if b.ID != b.ID {
a, b := rpts[i], rpts[j]
if a.ID != b.ID {
return a.ID < b.ID
}
if a.PkgPath != b.PkgPath {
Expand Down Expand Up @@ -254,50 +242,6 @@ var testClient1 = &mockClient{
},
}

var goldenReport1 = []string{`
{
ID: "GO-2022-01",
Symbol: "VulnData.Vuln1",
PkgPath: "golang.org/amod/avuln",
ModPath: "golang.org/amod",
URL: "https://pkg.go.dev/vuln/GO-2022-01",
CurrentVersion "v1.1.3",
FixedVersion "v1.0.4",
"call_stacks": [
"golang.org/cmod/c.I.t0 called from golang.org/entry/x.X [approx.] (x.go:8)\ngolang.org/amod/avuln.VulnData.Vuln1 (avuln.go:3)\n\n"
]
}
`,
`
{
"id": "GO-2022-02",
"symbol": "Vuln",
"pkg_path": "golang.org/bmod/bvuln",
"mod_path": "golang.org/bmod",
"url": "https://pkg.go.dev/vuln/GO-2022-02",
"current_version": "v0.5.0",
"call_stacks": [
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\ngolang.org/bmod/bvuln.Vuln (bvuln.go:2)\n\n",
"Y called from golang.org/entry/x.CallY (x.go:12)\nt0 called from golang.org/entry/y.Y [approx.] (y.go:5)\ngolang.org/bmod/bvuln.Vuln (bvuln.go:2)\n\n"
]
}
`,
`
{
"id": "GO-2022-01",
"symbol": "VulnData.Vuln2",
"pkg_path": "golang.org/amod/avuln",
"mod_path": "golang.org/amod",
"url": "https://pkg.go.dev/vuln/GO-2022-01",
"current_version": "v1.1.3",
FixedVersion: "v1.0.4",
"call_stacks": [
"C1 called from golang.org/entry/x.X (x.go:8)\nVuln2 called from golang.org/cmod/c.C1 (c.go:13)\ngolang.org/amod/avuln.VulnData.Vuln2 (avuln.go:4)\n\n"
]
}
`,
}

type mockClient struct {
client.Client
ret map[string][]*osv.Entry
Expand Down Expand Up @@ -347,19 +291,6 @@ func runTest(t *testing.T, workspaceData, proxyData string, test func(context.Co
test(ctx, snapshot)
}

func sortStrs(s []string) []string {
sort.Strings(s)
return s
}

func pkgPaths(pkgs []*vulncheck.Package) []string {
var r []string
for _, p := range pkgs {
r = append(r, p.PkgPath)
}
return sortStrs(r)
}

// TODO: expose this as a method of Snapshot.
func packagesCfg(ctx context.Context, snapshot source.Snapshot) *packages.Config {
view := snapshot.View()
Expand Down
Loading

0 comments on commit f918e87

Please sign in to comment.