Skip to content

Commit ee3ce09

Browse files
authored
fix: more nvd optimisations and badrepo cache (#4723)
This change fixes some nits, introduces a bad repository cache and stops the conversion from discarding versions just because a commit was provided in the references. - Removed CVEID from error formatting as at time of logging, CVEID is already provided via slog. - Removed warnings from some logs in favour of adding to metrics.Notes file for better investigation later. There may be value in keeping these at warning levels only if we also are prepared to handle investigating spikes, in which case logs should appear closer to what is causing the errors (network requests generally). - made errors lowercase as linter demands
1 parent 36bdab6 commit ee3ce09

7 files changed

Lines changed: 195 additions & 102 deletions

File tree

vulnfeeds/cmd/converters/cve/nvd-cve-osv/main.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ func main() {
8989
logger.Info("NVD Conversion run complete")
9090
}
9191

92-
func processCVE(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, repoTagsCache *git.RepoTagsCache) error {
92+
func processCVE(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, repoTagsCache *git.RepoTagsCache) models.ConversionOutcome {
9393
metrics := &models.ConversionMetrics{
9494
CVEID: cve.ID,
9595
CNA: "nvd",
9696
}
97-
repos := nvd.FindRepos(cve, vpRepoCache, metrics)
97+
repos := nvd.FindRepos(cve, vpRepoCache, repoTagsCache, metrics)
9898
metrics.Repos = repos
9999

100100
var err error
@@ -106,29 +106,28 @@ func processCVE(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, repoTagsCache
106106
}
107107
// Parse this error to determine which failure mode it was
108108
if err != nil {
109-
logger.Warn("Failed to generate an OSV record", slog.String("cve", string(cve.ID)), slog.Any("err", err))
110109
if errors.Is(err, nvd.ErrNoRanges) {
111110
metrics.Outcome = models.NoRanges
112-
return err
111+
return models.NoRanges
113112
}
114113
if errors.Is(err, nvd.ErrUnresolvedFix) {
115114
metrics.Outcome = models.FixUnresolvable
116-
return err
115+
return models.FixUnresolvable
117116
}
118117
metrics.Outcome = models.ConversionUnknown
119118

120-
return err
119+
return models.ConversionUnknown
121120
}
122121
metrics.Outcome = models.Successful
123122

124-
return nil
123+
return models.Successful
125124
}
126125

127126
func worker(wg *sync.WaitGroup, jobs <-chan models.NVDCVE, _ string, vpRepoCache *cves.VPRepoCache, repoTagsCache *git.RepoTagsCache) {
128127
defer wg.Done()
129128
for cve := range jobs {
130-
if err := processCVE(cve, vpRepoCache, repoTagsCache); err != nil {
131-
logger.Warn("Failed to generate an OSV record", slog.String("cve", string(cve.ID)), slog.Any("err", err))
129+
if processCVE(cve, vpRepoCache, repoTagsCache) != models.Successful {
130+
logger.Info("Failed to generate an OSV record", slog.String("cve", string(cve.ID)))
132131
} else {
133132
logger.Info("Generated OSV record for "+string(cve.ID), slog.String("cve", string(cve.ID)))
134133
}

vulnfeeds/conversion/nvd/converter.go

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
3636
maybeVendorName = CPE.Vendor
3737
maybeProductName = CPE.Product
3838
if err != nil {
39-
return fmt.Errorf("[%s]: Can't generate an OSV record without valid CPE data", cve.ID)
39+
return errors.New("can't generate an OSV record without valid CPE data")
4040
}
4141
}
4242

@@ -48,13 +48,13 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
4848
// There are some AffectedVersions to try and resolve to AffectedCommits.
4949
if len(repos) == 0 {
5050
metrics.AddNote("No affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
51-
return fmt.Errorf("[%s]: No affected ranges for %q, and no repos to try and convert %+v to tags with", cve.ID, maybeProductName, versions.AffectedVersions)
51+
return fmt.Errorf("no affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
5252
}
5353
metrics.AddNote("Trying to convert version tags to commits: %v with repos: %v", versions, repos)
54-
versions, err = cves.GitVersionsToCommits(cve.ID, versions, repos, cache, metrics)
54+
versions, err = cves.GitVersionsToCommits(versions, repos, cache, metrics)
5555
if err != nil {
56-
metrics.AddNote("Failed to convert version tags to commits: %#v", err)
57-
return fmt.Errorf("[%s]: Failed to convert version tags to commits: %#w", cve.ID, err)
56+
metrics.AddNote("Failed to convert version tags to commits: %+v", err)
57+
return fmt.Errorf("failed to convert version tags to commits: %+v %w", versions, err)
5858
}
5959
hasAnyFixedCommits := false
6060
for _, repo := range repos {
@@ -65,8 +65,8 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
6565
}
6666

6767
if versions.HasFixedVersions() && !hasAnyFixedCommits {
68-
metrics.AddNote("Failed to convert fixed version tags to commits: %#v", versions)
69-
return fmt.Errorf("[%s]: Failed to convert fixed version tags to commits: %#v %w", cve.ID, versions, ErrUnresolvedFix)
68+
metrics.AddNote("Failed to convert fixed version tags to commits: %+v", versions)
69+
return fmt.Errorf("failed to convert fixed version tags to commits: %+v %w", versions, ErrUnresolvedFix)
7070
}
7171

7272
hasAnyLastAffectedCommits := false
@@ -78,8 +78,8 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
7878
}
7979

8080
if versions.HasLastAffectedVersions() && !hasAnyLastAffectedCommits && !hasAnyFixedCommits {
81-
metrics.AddNote("Failed to convert last_affected version tags to commits: %#v", versions)
82-
return fmt.Errorf("[%s]: Failed to convert last_affected version tags to commits: %#v %w", cve.ID, versions, ErrUnresolvedFix)
81+
metrics.AddNote("Failed to convert last_affected version tags to commits: %+v", versions)
82+
return fmt.Errorf("failed to convert last_affected version tags to commits: %+v %w", versions, ErrUnresolvedFix)
8383
}
8484
}
8585

@@ -89,7 +89,7 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
8989

9090
if len(v.Affected) == 0 {
9191
metrics.AddNote("No affected ranges detected for %q", maybeProductName)
92-
return fmt.Errorf("[%s]: No affected ranges detected for %q %w", cve.ID, maybeProductName, ErrNoRanges)
92+
return fmt.Errorf("no affected ranges detected for %q %w", maybeProductName, ErrNoRanges)
9393
}
9494

9595
vulnDir := filepath.Join(directory, maybeVendorName, maybeProductName)
@@ -131,7 +131,7 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
131131
maybeVendorName = CPE.Vendor
132132
maybeProductName = CPE.Product
133133
if err != nil {
134-
return fmt.Errorf("[%s]: Can't generate an OSV record without valid CPE data", cve.ID)
134+
return errors.New("can't generate an OSV record without valid CPE data")
135135
}
136136
}
137137

@@ -143,13 +143,13 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
143143
// There are some AffectedVersions to try and resolve to AffectedCommits.
144144
if len(repos) == 0 {
145145
metrics.AddNote("No affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
146-
return fmt.Errorf("[%s]: No affected ranges for %q, and no repos to try and convert %+v to tags with", cve.ID, maybeProductName, versions.AffectedVersions)
146+
return fmt.Errorf("no affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
147147
}
148148
logger.Info("Trying to convert version tags to commits", slog.String("cve", string(cve.ID)), slog.Any("versions", versions), slog.Any("repos", repos))
149-
versions, err = cves.GitVersionsToCommits(cve.ID, versions, repos, cache, metrics)
149+
versions, err = cves.GitVersionsToCommits(versions, repos, cache, metrics)
150150
if err != nil {
151-
metrics.AddNote("Failed to convert version tags to commits: %#v", err)
152-
return fmt.Errorf("[%s]: Failed to convert version tags to commits: %#w", cve.ID, err)
151+
metrics.AddNote("Failed to convert version tags to commits: %+v", err)
152+
return fmt.Errorf("failed to convert version tags to commits: %+v %w", versions, err)
153153
}
154154
}
155155

@@ -161,8 +161,8 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
161161
}
162162

163163
if versions.HasFixedVersions() && !hasAnyFixedCommits {
164-
metrics.AddNote("Failed to convert fixed version tags to commits: %#v", versions)
165-
return fmt.Errorf("[%s]: Failed to convert fixed version tags to commits: %#v %w", cve.ID, versions, ErrUnresolvedFix)
164+
metrics.AddNote("Failed to convert fixed version tags to commits: %+v", versions)
165+
return fmt.Errorf("failed to convert fixed version tags to commits: %+v %w", versions, ErrUnresolvedFix)
166166
}
167167

168168
hasAnyLastAffectedCommits := false
@@ -173,13 +173,13 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
173173
}
174174

175175
if versions.HasLastAffectedVersions() && !hasAnyLastAffectedCommits && !hasAnyFixedCommits {
176-
metrics.AddNote("Failed to convert last_affected version tags to commits: %#v", versions)
177-
return fmt.Errorf("[%s]: Failed to convert last_affected version tags to commits: %#v %w", cve.ID, versions, ErrUnresolvedFix)
176+
metrics.AddNote("Failed to convert last_affected version tags to commits: %+v", versions)
177+
return fmt.Errorf("failed to convert last_affected version tags to commits: %+v %w", versions, ErrUnresolvedFix)
178178
}
179179

180180
if len(versions.AffectedCommits) == 0 {
181181
metrics.AddNote("No affected commit ranges determined for %q", maybeProductName)
182-
return fmt.Errorf("[%s]: No affected commit ranges determined for %q %w", cve.ID, maybeProductName, ErrNoRanges)
182+
return fmt.Errorf("no affected commit ranges determined for %q %w", maybeProductName, ErrNoRanges)
183183
}
184184

185185
versions.AffectedVersions = nil // these have served their purpose and are not required in the resulting output.
@@ -229,7 +229,7 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
229229
}
230230

231231
// FindRepos attempts to find the source code repositories for a given CVE.
232-
func FindRepos(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, metrics *models.ConversionMetrics) []string {
232+
func FindRepos(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, repoTagsCache *git.RepoTagsCache, metrics *models.ConversionMetrics) []string {
233233
// Find repos
234234
refs := cve.References
235235
CPEs := cves.CPEs(cve)
@@ -246,29 +246,38 @@ func FindRepos(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, metrics *models
246246

247247
// Edge case: No CPEs, but perhaps usable references.
248248
if len(refs) > 0 && len(CPEs) == 0 {
249-
repos := cves.ReposFromReferences(nil, nil, refs, cves.RefTagDenyList, metrics)
249+
repos := cves.ReposFromReferences(nil, nil, refs, cves.RefTagDenyList, repoTagsCache, metrics)
250250
if len(repos) == 0 {
251251
metrics.AddNote("Failed to derive any repos and there were no CPEs")
252252
return nil
253253
}
254254
metrics.AddNote("Derived repos for CVE with no CPEs: %v", repos)
255255
reposForCVE = repos
256256
}
257-
258-
// Does it have any application CPEs? Look for pre-computed repos based on VendorProduct.
259257
appCPECount := 0
258+
vendorProductCombinations := make(map[cves.VendorProduct]bool)
260259
for _, CPEstr := range CPEs {
261260
CPE, err := cves.ParseCPE(CPEstr)
262261
if err != nil {
263262
metrics.AddNote("Failed to parse CPE: %v", CPEstr)
264-
metrics.Outcome = models.ConversionUnknown
265-
266263
continue
267264
}
268-
if CPE.Part == "a" {
269-
appCPECount += 1
265+
if CPE.Part != "a" {
266+
continue
270267
}
271-
vendorProductKey := cves.VendorProduct{Vendor: CPE.Vendor, Product: CPE.Product}
268+
appCPECount += 1
269+
vendorProductCombinations[cves.VendorProduct{Vendor: CPE.Vendor, Product: CPE.Product}] = true
270+
}
271+
272+
if len(CPEs) > 0 && appCPECount == 0 {
273+
// This CVE is not for software (based on there being CPEs but not any application ones), skip.
274+
metrics.Outcome = models.NoSoftware
275+
return nil
276+
}
277+
278+
// If there wasn't a repo from the CPE Dictionary, try and derive one from the CVE references.
279+
for vendorProductKey := range vendorProductCombinations {
280+
// Does it have any application CPEs? Look for pre-computed repos based on VendorProduct.
272281
if repos, ok := vpRepoCache.Get(vendorProductKey); ok {
273282
metrics.AddNote("Pre-references, derived repos using cache: %v", repos)
274283
if len(reposForCVE) == 0 {
@@ -282,35 +291,11 @@ func FindRepos(cve models.NVDCVE, vpRepoCache *cves.VPRepoCache, metrics *models
282291
}
283292
}
284293
}
285-
}
286-
287-
if len(CPEs) > 0 && appCPECount == 0 {
288-
// This CVE is not for software (based on there being CPEs but not any application ones), skip.
289-
metrics.Outcome = models.NoSoftware
290-
return nil
291-
}
292-
293-
vendorProductCombinations := make(map[cves.VendorProduct]bool)
294-
for _, CPEstr := range CPEs {
295-
CPE, err := cves.ParseCPE(CPEstr)
296-
if err != nil {
297-
metrics.AddNote("Failed to parse CPE: %v", CPEstr)
298-
continue
299-
}
300-
if CPE.Part != "a" {
301-
continue
302-
}
303-
vendorProductCombinations[cves.VendorProduct{Vendor: CPE.Vendor, Product: CPE.Product}] = true
304-
}
305-
306-
// If there wasn't a repo from the CPE Dictionary, try and derive one from the CVE references.
307-
for vendorProductKey := range vendorProductCombinations {
308294
if len(reposForCVE) == 0 && len(refs) > 0 {
309-
// Continue to only focus on application CPEs.
310295
if slices.Contains(cves.VendorProductDenyList, vendorProductKey) {
311296
continue
312297
}
313-
repos := cves.ReposFromReferences(vpRepoCache, &vendorProductKey, refs, cves.RefTagDenyList, metrics)
298+
repos := cves.ReposFromReferences(vpRepoCache, &vendorProductKey, refs, cves.RefTagDenyList, repoTagsCache, metrics)
314299
if len(repos) == 0 {
315300
metrics.AddNote("Failed to derive any repos for %s/%s", vendorProductKey.Vendor, vendorProductKey.Product)
316301
continue

0 commit comments

Comments
 (0)