Skip to content

Commit ef4735b

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. Cherry-picked: https://go-review.googlesource.com/c/exp/+/391894 Change-Id: Ib191cb8ec6a09e607673af7ccdcb34ea121a5b69 Reviewed-on: https://go-review.googlesource.com/c/vuln/+/395240 Trust: Julie Qiu <julie@golang.org> Run-TryBot: Julie Qiu <julie@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent 67cbee0 commit ef4735b

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
@@ -147,11 +147,7 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
147147
// Build a map from module paths to versions.
148148
moduleVersions := map[string]string{}
149149
packages.Visit(pkgs, nil, func(p *packages.Package) {
150-
m := p.Module
151-
if m != nil {
152-
if m.Replace != nil {
153-
m = m.Replace
154-
}
150+
if m := packageModule(p); m != nil {
155151
moduleVersions[m.Path] = m.Version
156152
}
157153
})
@@ -162,6 +158,12 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
162158
fmt.Printf("%-*s%s\n", labelWidth, label, text)
163159
}
164160

161+
// Create set of top-level packages, used to find
162+
// representative symbols
163+
topPackages := map[string]bool{}
164+
for _, p := range pkgs {
165+
topPackages[p.PkgPath] = true
166+
}
165167
vulnGroups := groupByIDAndPackage(r.Vulns)
166168
for _, vg := range vulnGroups {
167169
// All the vulns in vg have the same PkgPath, ModPath and OSV.
@@ -172,15 +174,9 @@ func writeText(r *vulncheck.Result, pkgs []*packages.Package) {
172174
fixed := "v" + latestFixed(v0.OSV.Affected)
173175
ref := fmt.Sprintf("https://pkg.go.dev/vuln/%s", v0.OSV.ID)
174176

175-
// Collect unique top of call stacks.
176-
fns := map[*vulncheck.FuncNode]bool{}
177-
for _, v := range vg {
178-
for _, cs := range callStacks[v] {
179-
fns[cs[0].Function] = true
180-
}
181-
}
182-
// Use first top of first vuln as representative.
183-
rep := funcName(callStacks[v0][0][0].Function)
177+
fns := representativeFuncs(vg, topPackages, callStacks)
178+
// Use first as representative.
179+
rep := funcName(fns[0])
184180
var syms string
185181
if len(fns) == 1 {
186182
syms = rep
@@ -227,6 +223,17 @@ func groupByIDAndPackage(vs []*vulncheck.Vuln) [][]*vulncheck.Vuln {
227223
return res
228224
}
229225

226+
func packageModule(p *packages.Package) *packages.Module {
227+
m := p.Module
228+
if m == nil {
229+
return nil
230+
}
231+
if r := m.Replace; r != nil {
232+
return r
233+
}
234+
return m
235+
}
236+
230237
func isFile(path string) bool {
231238
s, err := os.Stat(path)
232239
if err != nil {
@@ -270,6 +277,42 @@ func latestFixed(as []osv.Affected) string {
270277
return v
271278
}
272279

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

cmd/govulncheck/main_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package main
1010
import (
1111
"testing"
1212

13+
"golang.org/x/exp/vulncheck"
1314
"golang.org/x/vuln/osv"
1415
)
1516

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

0 commit comments

Comments
 (0)