Skip to content

Commit 248e257

Browse files
authored
refactor(vulnfeeds): move GitVersionsToCommits to common dir and refactor to output resolved ranges (#4815)
Move more common functions and remove useless ones. GitVersionsToCommits now outputs resolved ranges, unresolved ranges and successful repositories. This function will be more heavily used in the next PR for the NVD refactor. It should output resolved/unresolved ranges instead of affected ranges so that we are able to group the ranges by repository into one set of ranges/events later on, so as not to have duplicate repository/package information in the record. Renamed another function also called GitVersionsToCommits to VersionInfoToCommits to better reflect what it's actually doing and as to reduce the confusion around the same name.
1 parent ee72ee8 commit 248e257

5 files changed

Lines changed: 134 additions & 133 deletions

File tree

vulnfeeds/conversion/common.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import (
1414
"strings"
1515
"time"
1616

17+
"github.com/google/osv/vulnfeeds/git"
1718
"github.com/google/osv/vulnfeeds/models"
19+
"github.com/google/osv/vulnfeeds/utility"
1820
"github.com/google/osv/vulnfeeds/utility/logger"
1921
"github.com/google/osv/vulnfeeds/vulns"
2022
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -169,6 +171,90 @@ func WriteMetricsFile(metrics *models.ConversionMetrics, metricsFile *os.File) e
169171
return nil
170172
}
171173

174+
// Examines repos and tries to convert versions to commits by treating them as Git tags.
175+
func GitVersionsToCommits(versionRanges []*osvschema.Range, repos []string, metrics *models.ConversionMetrics, cache *git.RepoTagsCache) ([]*osvschema.Range, []*osvschema.Range, []string) {
176+
var newVersionRanges []*osvschema.Range
177+
unresolvedRanges := versionRanges
178+
var successfulRepos []string
179+
180+
for _, repo := range repos {
181+
if len(unresolvedRanges) == 0 {
182+
break // All ranges have been resolved.
183+
}
184+
185+
normalizedTags, err := git.NormalizeRepoTags(repo, cache)
186+
if err != nil {
187+
metrics.AddNote("Failed to normalize tags - %s", repo)
188+
continue
189+
}
190+
191+
var stillUnresolvedRanges []*osvschema.Range
192+
for _, vr := range unresolvedRanges {
193+
var introduced, fixed, lastAffected string
194+
for _, e := range vr.GetEvents() {
195+
if e.GetIntroduced() != "" {
196+
introduced = e.GetIntroduced()
197+
}
198+
if e.GetFixed() != "" {
199+
fixed = e.GetFixed()
200+
}
201+
if e.GetLastAffected() != "" {
202+
lastAffected = e.GetLastAffected()
203+
}
204+
}
205+
206+
var introducedCommit string
207+
if introduced == "0" {
208+
introducedCommit = "0"
209+
} else {
210+
introducedCommit = resolveVersionToCommit(introduced, normalizedTags)
211+
}
212+
fixedCommit := resolveVersionToCommit(fixed, normalizedTags)
213+
lastAffectedCommit := resolveVersionToCommit(lastAffected, normalizedTags)
214+
215+
if introducedCommit != "" && (fixedCommit != "" || lastAffectedCommit != "") {
216+
var newVR *osvschema.Range
217+
218+
if fixedCommit != "" {
219+
newVR = BuildVersionRange(introducedCommit, "", fixedCommit)
220+
} else {
221+
newVR = BuildVersionRange(introducedCommit, lastAffectedCommit, "")
222+
}
223+
successfulRepos = append(successfulRepos, repo)
224+
newVR.Repo = repo
225+
newVR.Type = osvschema.Range_GIT
226+
if len(vr.GetEvents()) > 0 {
227+
databaseSpecific, err := utility.NewStructpbFromMap(map[string]any{"versions": vr.GetEvents()})
228+
if err != nil {
229+
metrics.AddNote("failed to make database specific: %v", err)
230+
} else {
231+
newVR.DatabaseSpecific = databaseSpecific
232+
}
233+
}
234+
235+
newVersionRanges = append(newVersionRanges, newVR)
236+
} else {
237+
stillUnresolvedRanges = append(stillUnresolvedRanges, vr)
238+
}
239+
}
240+
unresolvedRanges = stillUnresolvedRanges
241+
}
242+
243+
if len(newVersionRanges) > 0 {
244+
metrics.ResolvedRangesCount += len(newVersionRanges)
245+
metrics.Outcome = models.Successful
246+
}
247+
248+
if len(unresolvedRanges) > 0 {
249+
metrics.UnresolvedRangesCount += len(unresolvedRanges)
250+
if len(newVersionRanges) == 0 {
251+
metrics.Outcome = models.NoCommitRanges
252+
}
253+
}
254+
255+
return newVersionRanges, unresolvedRanges, successfulRepos
256+
}
257+
172258
// BuildVersionRange is a helper function that adds 'introduced', 'fixed', or 'last_affected'
173259
// events to an OSV version range. If 'intro' is empty, it defaults to "0".
174260
func BuildVersionRange(intro string, lastAff string, fixed string) *osvschema.Range {
@@ -193,3 +279,17 @@ func BuildVersionRange(intro string, lastAff string, fixed string) *osvschema.Ra
193279

194280
return &versionRange
195281
}
282+
283+
// resolveVersionToCommit is a helper to convert a version string to a commit hash.
284+
// It logs the outcome of the conversion attempt and returns an empty string on failure.
285+
func resolveVersionToCommit(version string, normalizedTags map[string]git.NormalizedTag) string {
286+
if version == "" {
287+
return ""
288+
}
289+
commit, err := git.VersionToCommit(version, normalizedTags)
290+
if err != nil {
291+
return ""
292+
}
293+
294+
return commit
295+
}

vulnfeeds/conversion/nvd/converter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func CVEToOSV(cve models.NVDCVE, repos []string, cache *git.RepoTagsCache, direc
5050
return fmt.Errorf("no affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
5151
}
5252
metrics.AddNote("Trying to convert version tags to commits: %v with repos: %v", versions, repos)
53-
versions = cves.GitVersionsToCommits(versions, repos, cache, metrics)
53+
versions = cves.VersionInfoToCommits(versions, repos, cache, metrics)
5454
hasAnyFixedCommits := false
5555
for _, ac := range versions.AffectedCommits {
5656
if ac.Fixed != "" {
@@ -140,7 +140,7 @@ func CVEToPackageInfo(cve models.NVDCVE, repos []string, cache *git.RepoTagsCach
140140
return fmt.Errorf("no affected ranges for %q, and no repos to try and convert %+v to tags with", maybeProductName, versions.AffectedVersions)
141141
}
142142
logger.Info("Trying to convert version tags to commits", slog.String("cve", string(cve.ID)), slog.Any("versions", versions), slog.Any("repos", repos))
143-
versions = cves.GitVersionsToCommits(versions, repos, cache, metrics)
143+
versions = cves.VersionInfoToCommits(versions, repos, cache, metrics)
144144
}
145145

146146
hasAnyFixedCommits := false

vulnfeeds/cvelist2osv/common.go

Lines changed: 0 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@ package cvelist2osv
33
import (
44
"cmp"
55
"errors"
6-
"log/slog"
76
"strconv"
87
"strings"
98

109
"github.com/google/osv/vulnfeeds/conversion"
11-
"github.com/google/osv/vulnfeeds/git"
1210
"github.com/google/osv/vulnfeeds/models"
13-
"github.com/google/osv/vulnfeeds/utility"
14-
"github.com/google/osv/vulnfeeds/utility/logger"
1511
"github.com/google/osv/vulnfeeds/vulns"
1612
"github.com/ossf/osv-schema/bindings/go/osvschema"
1713
)
@@ -55,117 +51,6 @@ func toVersionRangeType(s string) VersionRangeType {
5551
}
5652
}
5753

58-
// resolveVersionToCommit is a helper to convert a version string to a commit hash.
59-
// It logs the outcome of the conversion attempt and returns an empty string on failure.
60-
func resolveVersionToCommit(cveID models.CVEID, version, versionType, repo string, normalizedTags map[string]git.NormalizedTag) string {
61-
if version == "" {
62-
return ""
63-
}
64-
logger.Info("Attempting to resolve version to commit", slog.String("cve", string(cveID)), slog.String("version", version), slog.String("type", versionType), slog.String("repo", repo))
65-
commit, err := git.VersionToCommit(version, normalizedTags)
66-
if err != nil {
67-
logger.Warn("Failed to get Git commit for version", slog.String("cve", string(cveID)), slog.String("version", version), slog.String("type", versionType), slog.String("repo", repo), slog.Any("err", err))
68-
return ""
69-
}
70-
logger.Info("Successfully derived commit for version", slog.String("cve", string(cveID)), slog.String("commit", commit), slog.String("version", version), slog.String("type", versionType))
71-
72-
return commit
73-
}
74-
75-
// Examines repos and tries to convert versions to commits by treating them as Git tags.
76-
// Takes a CVE ID string (for logging), VersionInfo with AffectedVersions and
77-
// typically no AffectedCommits and attempts to add AffectedCommits (including Fixed commits) where there aren't any.
78-
// Refuses to add the same commit to AffectedCommits more than once.
79-
func gitVersionsToCommits(cveID models.CVEID, versionRanges []*osvschema.Range, repos []string, metrics *models.ConversionMetrics, cache *git.RepoTagsCache) (*osvschema.Affected, error) {
80-
var newAff osvschema.Affected
81-
var newVersionRanges []*osvschema.Range
82-
unresolvedRanges := versionRanges
83-
84-
for _, repo := range repos {
85-
if len(unresolvedRanges) == 0 {
86-
break // All ranges have been resolved.
87-
}
88-
89-
normalizedTags, err := git.NormalizeRepoTags(repo, cache)
90-
if err != nil {
91-
metrics.AddNote("Failed to normalize tags - %s", repo)
92-
continue
93-
}
94-
95-
var stillUnresolvedRanges []*osvschema.Range
96-
for _, vr := range unresolvedRanges {
97-
var introduced, fixed, lastAffected string
98-
for _, e := range vr.GetEvents() {
99-
if e.GetIntroduced() != "" {
100-
introduced = e.GetIntroduced()
101-
}
102-
if e.GetFixed() != "" {
103-
fixed = e.GetFixed()
104-
}
105-
if e.GetLastAffected() != "" {
106-
lastAffected = e.GetLastAffected()
107-
}
108-
}
109-
110-
var introducedCommit string
111-
if introduced == "0" {
112-
introducedCommit = "0"
113-
} else {
114-
introducedCommit = resolveVersionToCommit(cveID, introduced, "introduced", repo, normalizedTags)
115-
}
116-
fixedCommit := resolveVersionToCommit(cveID, fixed, "fixed", repo, normalizedTags)
117-
lastAffectedCommit := resolveVersionToCommit(cveID, lastAffected, "last_affected", repo, normalizedTags)
118-
119-
if introducedCommit != "" && (fixedCommit != "" || lastAffectedCommit != "") {
120-
var newVR *osvschema.Range
121-
122-
if fixedCommit != "" {
123-
newVR = conversion.BuildVersionRange(introducedCommit, "", fixedCommit)
124-
} else {
125-
newVR = conversion.BuildVersionRange(introducedCommit, lastAffectedCommit, "")
126-
}
127-
128-
newVR.Repo = repo
129-
newVR.Type = osvschema.Range_GIT
130-
if len(vr.GetEvents()) > 0 {
131-
databaseSpecific, err := utility.NewStructpbFromMap(map[string]any{"versions": vr.GetEvents()})
132-
if err != nil {
133-
logger.Warn("failed to make database specific: %v", err)
134-
} else {
135-
newVR.DatabaseSpecific = databaseSpecific
136-
}
137-
}
138-
139-
newVersionRanges = append(newVersionRanges, newVR)
140-
} else {
141-
stillUnresolvedRanges = append(stillUnresolvedRanges, vr)
142-
}
143-
}
144-
unresolvedRanges = stillUnresolvedRanges
145-
}
146-
147-
var err error
148-
if len(unresolvedRanges) > 0 {
149-
databaseSpecific, err := utility.NewStructpbFromMap(map[string]any{"unresolved_ranges": unresolvedRanges})
150-
if err != nil {
151-
logger.Warn("failed to make database specific: %v", err)
152-
} else {
153-
newAff.DatabaseSpecific = databaseSpecific
154-
}
155-
156-
metrics.UnresolvedRangesCount += len(unresolvedRanges)
157-
}
158-
159-
if len(newVersionRanges) > 0 {
160-
newAff.Ranges = newVersionRanges
161-
metrics.ResolvedRangesCount += len(newVersionRanges)
162-
} else if len(unresolvedRanges) > 0 { // Only error if there were ranges to resolve but none were.
163-
err = errors.New("was not able to get git version ranges")
164-
}
165-
166-
return &newAff, err
167-
}
168-
16954
// findCPEVersionRanges extracts version ranges and CPE strings from the CNA's
17055
// CPE applicability statements in a CVE record.
17156
func findCPEVersionRanges(cve models.CVE5) (versionRanges []*osvschema.Range, cpes []string, err error) {

vulnfeeds/cvelist2osv/default_extractor.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package cvelist2osv
22

33
import (
4-
"log/slog"
5-
64
"github.com/google/osv/vulnfeeds/conversion"
75
"github.com/google/osv/vulnfeeds/cves"
86
"github.com/google/osv/vulnfeeds/git"
97
"github.com/google/osv/vulnfeeds/models"
8+
"github.com/google/osv/vulnfeeds/utility"
109
"github.com/google/osv/vulnfeeds/utility/logger"
1110
"github.com/google/osv/vulnfeeds/vulns"
1211
"github.com/ossf/osv-schema/bindings/go/osvschema"
@@ -39,40 +38,40 @@ func (d *DefaultVersionExtractor) ExtractVersions(cve models.CVE5, v *vulns.Vuln
3938
ranges := d.handleAffected(cve.Containers.CNA.Affected, metrics)
4039

4140
if len(ranges) != 0 {
42-
aff, err := gitVersionsToCommits(cve.Metadata.CVEID, ranges, repos, metrics, repoTagsCache)
43-
if err != nil {
44-
logger.Error("Failed to convert git versions to commits", slog.Any("err", err))
41+
resolvedRanges, unresolvedRanges, _ := conversion.GitVersionsToCommits(ranges, repos, metrics, repoTagsCache)
42+
if len(resolvedRanges) == 0 {
43+
metrics.AddNote("Failed to convert git versions to commits")
4544
} else {
4645
gotVersions = true
4746
}
48-
conversion.AddAffected(v, aff, metrics)
47+
addRangesToAffected(resolvedRanges, unresolvedRanges, v, metrics)
4948
}
5049

5150
if !gotVersions {
5251
metrics.AddNote("No versions in affected, attempting to extract from CPE")
5352
versionRanges, _ := cpeVersionExtraction(cve, metrics)
5453

5554
if len(versionRanges) != 0 {
56-
aff, err := gitVersionsToCommits(cve.Metadata.CVEID, versionRanges, repos, metrics, repoTagsCache)
57-
if err != nil {
58-
logger.Error("Failed to convert git versions to commits", slog.Any("err", err))
55+
resolvedRanges, unresolvedRanges, _ := conversion.GitVersionsToCommits(versionRanges, repos, metrics, repoTagsCache)
56+
if len(resolvedRanges) == 0 {
57+
metrics.AddNote("Failed to convert git versions to commits")
5958
} else {
6059
gotVersions = true
6160
}
62-
63-
conversion.AddAffected(v, aff, metrics)
61+
addRangesToAffected(resolvedRanges, unresolvedRanges, v, metrics)
6462
}
6563
}
6664

6765
if !gotVersions {
6866
metrics.AddNote("No versions in CPEs so attempting extraction from description")
6967
versionRanges := textVersionExtraction(cve, metrics)
7068
if len(versionRanges) != 0 {
71-
aff, err := gitVersionsToCommits(cve.Metadata.CVEID, versionRanges, repos, metrics, repoTagsCache)
72-
if err != nil {
73-
logger.Error("Failed to convert git versions to commits", slog.Any("err", err))
69+
resolvedRanges, unresolvedRanges, _ := conversion.GitVersionsToCommits(versionRanges, repos, metrics, repoTagsCache)
70+
if len(resolvedRanges) == 0 {
71+
metrics.AddNote("Failed to convert git versions to commits")
7472
}
75-
conversion.AddAffected(v, aff, metrics)
73+
74+
addRangesToAffected(resolvedRanges, unresolvedRanges, v, metrics)
7675
}
7776
}
7877
}
@@ -135,3 +134,20 @@ func (d *DefaultVersionExtractor) FindNormalAffectedRanges(affected models.Affec
135134

136135
return versionRanges, mostFrequentVersionType
137136
}
137+
138+
func addRangesToAffected(resolvedRanges []*osvschema.Range, unresolvedRanges []*osvschema.Range, v *vulns.Vulnerability, metrics *models.ConversionMetrics) {
139+
if len(resolvedRanges) > 0 {
140+
aff := &osvschema.Affected{
141+
Ranges: resolvedRanges,
142+
}
143+
if len(unresolvedRanges) > 0 {
144+
databaseSpecific, err := utility.NewStructpbFromMap(map[string]any{"unresolved_ranges": unresolvedRanges})
145+
if err != nil {
146+
logger.Warn("failed to make database specific: %v", err)
147+
} else {
148+
aff.DatabaseSpecific = databaseSpecific
149+
}
150+
}
151+
conversion.AddAffected(v, aff, metrics)
152+
}
153+
}

vulnfeeds/cves/versions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ func (c *VPRepoCache) Initialize(vpMap VendorProductToRepoMap) {
10011001
// Takes a CVE ID string (for logging), VersionInfo with AffectedVersions and
10021002
// typically no AffectedCommits and attempts to add AffectedCommits (including Fixed commits) where there aren't any.
10031003
// Refuses to add the same commit to AffectedCommits more than once.
1004-
func GitVersionsToCommits(versions models.VersionInfo, repos []string, cache *git.RepoTagsCache, metrics *models.ConversionMetrics) (v models.VersionInfo) {
1004+
func VersionInfoToCommits(versions models.VersionInfo, repos []string, cache *git.RepoTagsCache, metrics *models.ConversionMetrics) (v models.VersionInfo) {
10051005
// versions is a VersionInfo with AffectedVersions and typically no AffectedCommits
10061006
// v is a VersionInfo with AffectedCommits (containing Fixed commits) included
10071007
v = versions

0 commit comments

Comments
 (0)