Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion vulnfeeds/cmd/converters/cve/nvd-cve-osv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func processCVE(cve models.NVDCVE, vpRepoCache *c.VPRepoCache, repoTagsCache git
var finalMetrics *models.ConversionMetrics
switch *outFormat {
case "OSV":
vuln, finalMetrics, outcome = nvd.CVEToOSV(cve, repos, repoTagsCache, metrics)
vuln, finalMetrics, outcome = nvd.CVEToOSV(cve, repos, vpRepoCache, repoTagsCache, metrics)
case "PackageInfo":
outcome = nvd.CVEToPackageInfo(cve, repos, repoTagsCache, *outDir, metrics)
finalMetrics = metrics
Expand Down
104 changes: 98 additions & 6 deletions vulnfeeds/conversion/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ func GitVersionsToCommits(versionRanges []models.RangeWithMetadata, repos []stri
unresolvedRanges := versionRanges
var successfulRepos []string

claimedRepos := make(map[string]bool)
for _, vr := range versionRanges {
if vr.Range.GetRepo() != "" {
claimedRepos[vr.Range.GetRepo()] = true
}
}

for _, repo := range repos {
if len(unresolvedRanges) == 0 {
break // All ranges have been resolved.
Expand Down Expand Up @@ -177,6 +184,10 @@ func GitVersionsToCommits(versionRanges []models.RangeWithMetadata, repos []stri

var stillUnresolvedRanges []models.RangeWithMetadata
for _, vr := range unresolvedRanges {
if (vr.Range.GetRepo() != "" && vr.Range.GetRepo() != repo) || (vr.Range.GetRepo() == "" && claimedRepos[repo]) {
stillUnresolvedRanges = append(stillUnresolvedRanges, vr)
continue
}
var introduced, fixed, lastAffected string
for _, e := range vr.Range.GetEvents() {
if e.GetIntroduced() != "" {
Expand Down Expand Up @@ -462,15 +473,23 @@ func CreateUnresolvedRanges(unresolvedRanges []models.RangeWithMetadata) *struct
}

type key struct {
Source string
CPE string
Source string
VendorProduct string
}

rangesByKey := make(map[key][]models.RangeWithMetadata)
var keys []key

for _, ur := range unresolvedRanges {
k := key{Source: string(ur.Metadata.Source), CPE: ur.Metadata.CPE}
vendorProduct := ""
if ur.Metadata.CPE != "" {
if CPE, err := ParseCPE(ur.Metadata.CPE); err == nil {
vendorProduct = CPE.Vendor + ":" + CPE.Product
} else {
vendorProduct = ur.Metadata.CPE
}
}
k := key{Source: string(ur.Metadata.Source), VendorProduct: vendorProduct}
if _, ok := rangesByKey[k]; !ok {
keys = append(keys, k)
}
Expand All @@ -482,7 +501,7 @@ func CreateUnresolvedRanges(unresolvedRanges []models.RangeWithMetadata) *struct
return strings.Compare(a.Source, b.Source)
}

return strings.Compare(a.CPE, b.CPE)
return strings.Compare(a.VendorProduct, b.VendorProduct)
})

listElements := make([]any, 0, len(keys))
Expand All @@ -491,8 +510,12 @@ func CreateUnresolvedRanges(unresolvedRanges []models.RangeWithMetadata) *struct
ranges := rangesByKey[k]
unresolvedRangesMap := make(map[string]any)
var events []*osvschema.Event
cpesSet := make(map[string]bool)

for _, ur := range ranges {
if ur.Metadata.CPE != "" {
cpesSet[ur.Metadata.CPE] = true
}
urEvents := ur.Range.GetEvents()

for _, e := range urEvents {
Expand All @@ -510,12 +533,21 @@ func CreateUnresolvedRanges(unresolvedRanges []models.RangeWithMetadata) *struct
}
}

if k.CPE != "" {
unresolvedRangesMap["cpe"] = k.CPE
var cpes []string
for cpe := range cpesSet {
cpes = append(cpes, cpe)
}
slices.Sort(cpes)

if k.VendorProduct != "" {
unresolvedRangesMap["vendor_product"] = k.VendorProduct
}
if k.Source != "" {
unresolvedRangesMap["source"] = k.Source
}
if len(cpes) > 0 {
unresolvedRangesMap["cpes"] = cpes
}

unresolvedRangesMap["extracted_events"] = events
listElements = append(listElements, unresolvedRangesMap)
Expand All @@ -532,6 +564,66 @@ func CreateUnresolvedRanges(unresolvedRanges []models.RangeWithMetadata) *struct
return ds.GetFields()["list"].GetListValue()
}

// FilterUnresolvedRanges compares resolved and unresolved version ranges by their CPE criteria
// and raw version boundaries. If an unresolved CPE range has been successfully resolved on at least
// one repository in the CVE (recorded in resolved), it will filter out/exclude that unresolved range
// copy from being outputted in database_specific, preventing duplicate unresolved product listings.
func FilterUnresolvedRanges(resolved []models.RangeWithMetadata, unresolved []models.RangeWithMetadata) []models.RangeWithMetadata {
type rangeKey struct {
CPE string
Events string
}

resolvedKeys := make(map[rangeKey]bool)
for _, r := range resolved {
var eventStrings []string
if r.Range.GetDatabaseSpecific() != nil && r.Range.GetDatabaseSpecific().GetFields()["extracted_events"] != nil {
listValue := r.Range.GetDatabaseSpecific().GetFields()["extracted_events"].GetListValue()
if listValue != nil {
for _, val := range listValue.GetValues() {
strct := val.GetStructValue()
if strct != nil {
introduced := strct.GetFields()["introduced"].GetStringValue()
fixed := strct.GetFields()["fixed"].GetStringValue()
lastAffected := strct.GetFields()["last_affected"].GetStringValue()
eventStrings = append(eventStrings, fmt.Sprintf("%s|%s|%s", introduced, fixed, lastAffected))
}
}
}
}
if len(eventStrings) == 0 {
for _, e := range r.Range.GetEvents() {
eventStrings = append(eventStrings, fmt.Sprintf("%s|%s|%s", e.GetIntroduced(), e.GetFixed(), e.GetLastAffected()))
}
}
slices.Sort(eventStrings)
k := rangeKey{
CPE: r.Metadata.CPE,
Events: strings.Join(eventStrings, ","),
}
resolvedKeys[k] = true
}

filtered := make([]models.RangeWithMetadata, 0, len(unresolved))
for _, ur := range unresolved {
var eventStrings []string
for _, e := range ur.Range.GetEvents() {
eventStrings = append(eventStrings, fmt.Sprintf("%s|%s|%s", e.GetIntroduced(), e.GetFixed(), e.GetLastAffected()))
}
slices.Sort(eventStrings)
k := rangeKey{
CPE: ur.Metadata.CPE,
Events: strings.Join(eventStrings, ","),
}
if resolvedKeys[k] {
continue
}
filtered = append(filtered, ur)
}

return filtered
}

func AddFieldToDatabaseSpecific(ds *structpb.Struct, field string, value any) error {
if ds == nil {
return errors.New("database specific is nil")
Expand Down
18 changes: 14 additions & 4 deletions vulnfeeds/conversion/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,13 @@ func TestCreateUnresolvedRanges(t *testing.T) {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"cpe": structpb.NewStringValue("cpe:2.3:a:another:app:*:*:*:*:*:*:*:*"),
"source": structpb.NewStringValue(string(models.VersionSourceCPE)),
"vendor_product": structpb.NewStringValue("another:app"),
"source": structpb.NewStringValue(string(models.VersionSourceCPE)),
"cpes": structpb.NewListValue(&structpb.ListValue{
Values: []*structpb.Value{
structpb.NewStringValue("cpe:2.3:a:another:app:*:*:*:*:*:*:*:*"),
},
}),
"extracted_events": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Expand All @@ -393,8 +398,13 @@ func TestCreateUnresolvedRanges(t *testing.T) {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"cpe": structpb.NewStringValue("cpe:2.3:a:example:app:*:*:*:*:*:*:*:*"),
"source": structpb.NewStringValue(string(models.VersionSourceDescription)),
"vendor_product": structpb.NewStringValue("example:app"),
"source": structpb.NewStringValue(string(models.VersionSourceDescription)),
"cpes": structpb.NewListValue(&structpb.ListValue{
Values: []*structpb.Value{
structpb.NewStringValue("cpe:2.3:a:example:app:*:*:*:*:*:*:*:*"),
},
}),
"extracted_events": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Expand Down
Loading
Loading