Skip to content

Commit 2d6d886

Browse files
committed
cmd/govulncheck: select representative symbols more carefully
Instead of using the entries (top of call stacks) as the symbols to show to the user, use the lowest symbols on the call stacks from the packages under analysis. This can greatly reduce the number of symbols. For example, in k8s.io/kubernetes, many functions call k8s.io/kubernetes/pkg/util/selinux.SELinuxEnabled, which then calls a vulnerable symbol in github.com/opencontainers/selinux/go-selinux. In this particular case, this CL reduces the number of symbols from 2,384 to 2. Change-Id: Ib191cb8ec6a09e607673af7ccdcb34ea121a5b69 Reviewed-on: https://go-review.googlesource.com/c/exp/+/391894 Trust: Jonathan Amsterdam <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Zvonimir Pavlinovic <[email protected]>
1 parent 8dddf5d commit 2d6d886

File tree

2 files changed

+83
-14
lines changed

2 files changed

+83
-14
lines changed

cmd/govulncheck/main.go

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,7 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
144144
// Build a map from module paths to versions.
145145
moduleVersions := map[string]string{}
146146
packages.Visit(pkgs, nil, func(p *packages.Package) {
147-
m := p.Module
148-
if m != nil {
149-
if m.Replace != nil {
150-
m = m.Replace
151-
}
147+
if m := packageModule(p); m != nil {
152148
moduleVersions[m.Path] = m.Version
153149
}
154150
})
@@ -159,6 +155,12 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
159155
fmt.Printf("%-*s%s\n", labelWidth, label, text)
160156
}
161157

158+
// Create set of top-level packages, used to find
159+
// representative symbols
160+
topPackages := map[string]bool{}
161+
for _, p := range pkgs {
162+
topPackages[p.PkgPath] = true
163+
}
162164
vulnGroups := groupByIDAndPackage(r.Vulns)
163165
for _, vg := range vulnGroups {
164166
// All the vulns in vg have the same PkgPath, ModPath and OSV.
@@ -169,15 +171,9 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
169171
fixed := "v" + latestFixed(v0.OSV.Affected)
170172
ref := fmt.Sprintf("https://pkg.go.dev/vuln/%s", v0.OSV.ID)
171173

172-
// Collect unique top of call stacks.
173-
fns := map[*vulncheck.FuncNode]bool{}
174-
for _, v := range vg {
175-
for _, cs := range callStacks[v] {
176-
fns[cs[0].Function] = true
177-
}
178-
}
179-
// Use first top of first vuln as representative.
180-
rep := funcName(callStacks[v0][0][0].Function)
174+
fns := representativeFuncs(vg, topPackages, callStacks)
175+
// Use first as representative.
176+
rep := funcName(fns[0])
181177
var syms string
182178
if len(fns) == 1 {
183179
syms = rep
@@ -224,6 +220,17 @@ func groupByIDAndPackage(vs []*vulncheck.Vuln) [][]*vulncheck.Vuln {
224220
return res
225221
}
226222

223+
func packageModule(p *packages.Package) *packages.Module {
224+
m := p.Module
225+
if m == nil {
226+
return nil
227+
}
228+
if r := m.Replace; r != nil {
229+
return r
230+
}
231+
return m
232+
}
233+
227234
func isFile(path string) bool {
228235
s, err := os.Stat(path)
229236
if err != nil {
@@ -267,6 +274,42 @@ func latestFixed(as []osv.Affected) string {
267274
return v
268275
}
269276

277+
// representativeFuncs collects representative functions for the group
278+
// of vulns from the the call stacks.
279+
func representativeFuncs(vg []*vulncheck.Vuln, topPkgs map[string]bool, callStacks map[*vulncheck.Vuln][]vulncheck.CallStack) []*vulncheck.FuncNode {
280+
// Collect unique top of call stacks.
281+
fns := map[*vulncheck.FuncNode]bool{}
282+
for _, v := range vg {
283+
for _, cs := range callStacks[v] {
284+
// Find the lowest function in the stack that is in
285+
// one of the top packages.
286+
for i := len(cs) - 1; i > 0; i-- {
287+
pkg := pkgPath(cs[i].Function)
288+
if topPkgs[pkg] {
289+
fns[cs[i].Function] = true
290+
break
291+
}
292+
}
293+
}
294+
}
295+
var res []*vulncheck.FuncNode
296+
for fn := range fns {
297+
res = append(res, fn)
298+
}
299+
return res
300+
}
301+
302+
func pkgPath(fn *vulncheck.FuncNode) string {
303+
if fn.PkgPath != "" {
304+
return fn.PkgPath
305+
}
306+
s := strings.TrimPrefix(fn.RecvType, "*")
307+
if i := strings.LastIndexByte(s, '.'); i > 0 {
308+
s = s[:i]
309+
}
310+
return s
311+
}
312+
270313
func funcName(fn *vulncheck.FuncNode) string {
271314
return strings.TrimPrefix(fn.String(), "*")
272315
}

cmd/govulncheck/main_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package main
77
import (
88
"testing"
99

10+
"golang.org/x/exp/vulncheck"
1011
"golang.org/x/vuln/osv"
1112
)
1213

@@ -81,3 +82,28 @@ func TestLatestFixed(t *testing.T) {
8182
})
8283
}
8384
}
85+
86+
func TestPkgPath(t *testing.T) {
87+
for _, test := range []struct {
88+
in vulncheck.FuncNode
89+
want string
90+
}{
91+
{
92+
vulncheck.FuncNode{PkgPath: "math", Name: "Floor"},
93+
"math",
94+
},
95+
{
96+
vulncheck.FuncNode{RecvType: "a.com/b.T", Name: "M"},
97+
"a.com/b",
98+
},
99+
{
100+
vulncheck.FuncNode{RecvType: "*a.com/b.T", Name: "M"},
101+
"a.com/b",
102+
},
103+
} {
104+
got := pkgPath(&test.in)
105+
if got != test.want {
106+
t.Errorf("%+v: got %q, want %q", test.in, got, test.want)
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)