@@ -26,17 +26,22 @@ const (
26
26
)
27
27
28
28
type handler struct {
29
- w io.Writer
30
- cfg * govulncheck.Config
31
- osvs map [string ]* osv.Entry
32
- levels map [string ]findingLevel
29
+ w io.Writer
30
+ cfg * govulncheck.Config
31
+ osvs map [string ]* osv.Entry
32
+ // findings contains same-level findings for an
33
+ // OSV at the most precise level of granularity
34
+ // available. This means, for instance, that if
35
+ // an osv is indeed called, then all findings for
36
+ // the osv will have call stack info.
37
+ findings map [string ][]* govulncheck.Finding
33
38
}
34
39
35
40
func NewHandler (w io.Writer ) * handler {
36
41
return & handler {
37
- w : w ,
38
- osvs : make (map [string ]* osv.Entry ),
39
- levels : make (map [string ]findingLevel ),
42
+ w : w ,
43
+ osvs : make (map [string ]* osv.Entry ),
44
+ findings : make (map [string ][] * govulncheck. Finding ),
40
45
}
41
46
}
42
47
@@ -67,11 +72,52 @@ func foundAtLevel(f *govulncheck.Finding) findingLevel {
67
72
return required
68
73
}
69
74
75
+ // moreSpecific favors a call finding over a non-call
76
+ // finding and a package finding over a module finding.
77
+ func moreSpecific (f1 , f2 * govulncheck.Finding ) int {
78
+ if len (f1 .Trace ) > 1 && len (f2 .Trace ) > 1 {
79
+ // Both are call stack findings.
80
+ return 0
81
+ }
82
+ if len (f1 .Trace ) > 1 {
83
+ return - 1
84
+ }
85
+ if len (f2 .Trace ) > 1 {
86
+ return 1
87
+ }
88
+
89
+ fr1 , fr2 := f1 .Trace [0 ], f2 .Trace [0 ]
90
+ if fr1 .Function != "" && fr2 .Function == "" {
91
+ return - 1
92
+ }
93
+ if fr1 .Function == "" && fr2 .Function != "" {
94
+ return 1
95
+ }
96
+ if fr1 .Package != "" && fr2 .Package == "" {
97
+ return - 1
98
+ }
99
+ if fr1 .Package == "" && fr2 .Package != "" {
100
+ return - 1
101
+ }
102
+ return 0 // findings always have module info
103
+ }
104
+
70
105
func (h * handler ) Finding (f * govulncheck.Finding ) error {
71
- fLevel := foundAtLevel (f )
72
- if fLevel > h .levels [f .OSV ] {
73
- h .levels [f .OSV ] = fLevel
106
+ fs := h .findings [f .OSV ]
107
+ if len (fs ) == 0 {
108
+ fs = []* govulncheck.Finding {f }
109
+ } else {
110
+ if ms := moreSpecific (f , fs [0 ]); ms == - 1 {
111
+ // The new finding is more specific, so we need
112
+ // to erase existing findings and add the new one.
113
+ fs = []* govulncheck.Finding {f }
114
+ } else if ms == 0 {
115
+ // The new finding is at the same level of precision.
116
+ fs = append (fs , f )
117
+ }
118
+ // Otherwise, the new finding is at a less precise level.
74
119
}
120
+ h .findings [f .OSV ] = fs
75
121
return nil
76
122
}
77
123
@@ -102,6 +148,23 @@ func toVex(h *handler) Document {
102
148
return doc
103
149
}
104
150
151
+ // Given a slice of findings, returns those findings as a set of subcomponents
152
+ // that are unique per the vulnerable artifact's PURL.
153
+ func subcomponentSet (findings []* govulncheck.Finding ) []Component {
154
+ var scs []Component
155
+ seen := make (map [string ]bool )
156
+ for _ , f := range findings {
157
+ purl := purlFromFinding (f )
158
+ if ! seen [purl ] {
159
+ scs = append (scs , Component {
160
+ ID : purlFromFinding (f ),
161
+ })
162
+ seen [purl ] = true
163
+ }
164
+ }
165
+ return scs
166
+ }
167
+
105
168
// statements combines all OSVs found by govulncheck and generates the list of
106
169
// vex statements with the proper affected level and justification to match the
107
170
// openVex specification.
@@ -118,13 +181,16 @@ func statements(h *handler) []Statement {
118
181
119
182
var statements []Statement
120
183
for id , osv := range h .osvs {
121
- if _ , found := h .levels [id ]; ! found {
184
+ // if there are no findings emitted for a given OSV that means that
185
+ // the vulnerable module is not required at a vulnerable version.
186
+ if len (h .findings [id ]) == 0 {
122
187
continue
123
188
}
124
189
description := osv .Summary
125
190
if description == "" {
126
191
description = osv .Details
127
192
}
193
+
128
194
s := Statement {
129
195
Vulnerability : Vulnerability {
130
196
ID : fmt .Sprintf ("https://pkg.go.dev/vuln/%s" , id ),
@@ -134,19 +200,22 @@ func statements(h *handler) []Statement {
134
200
},
135
201
Products : []Product {
136
202
{
137
- ID : DefaultPID ,
203
+ Component : Component {ID : DefaultPID },
204
+ Subcomponents : subcomponentSet (h .findings [id ]),
138
205
},
139
206
},
140
207
}
141
208
142
- if h .levels [id ] >= scanLevel {
209
+ // Findings are guaranteed to be at the same level, so we can just check the first element
210
+ fLevel := foundAtLevel (h .findings [id ][0 ])
211
+ if fLevel >= scanLevel {
143
212
s .Status = StatusAffected
144
213
} else {
145
214
s .Status = StatusNotAffected
146
215
s .ImpactStatement = Impact
147
216
s .Justification = JustificationNotPresent
148
217
// We only reach this case if running in symbol mode
149
- if h . levels [ id ] == imported {
218
+ if fLevel == imported {
150
219
s .Justification = JustificationNotExecuted
151
220
}
152
221
}
0 commit comments