Skip to content
This repository has been archived by the owner on Dec 1, 2024. It is now read-only.

Fix: wrong cpe version match #214

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
39 changes: 28 additions & 11 deletions cvefeed/nvd/match_cpe.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (cm *cpeMatch) match(attr *wfn.Attributes, requireVersion bool) bool {
return false
}

if cm.Attributes.Version == wfn.Any {
if cm.Attributes.Version == wfn.Any || cm.Attributes.Version == wfn.NA {
if !cm.hasVersionRanges {
// if version is any and doesn't have version ranges, then it matches any
return !requireVersion
Expand Down Expand Up @@ -121,21 +121,38 @@ func (cm *cpeMatch) match(attr *wfn.Attributes, requireVersion bool) bool {

// match version to ranges
ver := wfn.StripSlashes(attr.Version)
return cm.compareVersions(ver)
}

matches := true
func (cm *cpeMatch) compareVersions(targetVersion string) bool {
result := cm.versionEndExcluding != "" || cm.versionEndIncluding != "" || cm.versionStartExcluding != "" || cm.versionStartIncluding != ""

if cm.versionStartIncluding != "" {
matches = matches && smartVerCmp(ver, cm.versionStartIncluding) >= 0
if !result && cm.MatchOnlyVersion(&wfn.Attributes{
Version: targetVersion,
}) {
return true
}
if cm.versionStartExcluding != "" {
matches = matches && smartVerCmp(ver, cm.versionStartExcluding) > 0

target := ParseVersion(targetVersion)
if target == nil {
return false
}
if cm.versionEndIncluding != "" {
matches = matches && smartVerCmp(ver, cm.versionEndIncluding) <= 0
if result && cm.versionEndExcluding != "" {
endExcluding := ParseVersion(cm.versionEndExcluding)
result = endExcluding.CompareTo(target) > 0
}
if cm.versionEndExcluding != "" {
matches = matches && smartVerCmp(ver, cm.versionEndExcluding) < 0
if result && cm.versionStartExcluding != "" {
startExcluding := ParseVersion(cm.versionStartExcluding)
result = startExcluding.CompareTo(target) < 0
}
if result && cm.versionEndIncluding != "" {
endIncluding := ParseVersion(cm.versionEndIncluding)
result = result && endIncluding.CompareTo(target) >= 0
}
if result && cm.versionStartIncluding != "" {
startIncluding := ParseVersion(cm.versionStartIncluding)
result = result && startIncluding.CompareTo(target) <= 0
}

return matches
return result
}
1 change: 1 addition & 0 deletions cvefeed/nvd/smartvercmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,4 @@ func lpad(s string, n int) string {
sb.WriteString(s)
return sb.String()
}

86 changes: 86 additions & 0 deletions cvefeed/nvd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package nvd

import (
"regexp"
"strconv"
"strings"
)

type ComponentVersion struct {
VersionParts []string
}

var versionPattern = regexp.MustCompile(`(\d+[a-z]{1,3}$|[a-z]{1,3}[_-]?\d+|\d+|(rc|release|snapshot|beta|alpha)$)`)

func ParseVersion(version string) *ComponentVersion {
versionParts := make([]string, 0)
if version == "" {
return nil
}

lcVersion := strings.ToLower(version)
versionSubmatch := versionPattern.FindAllStringSubmatch(lcVersion, -1)
for _, vs := range versionSubmatch {
versionParts = append(versionParts, vs...)
}
if len(versionParts) == 0 {
versionParts = append(versionParts, version)
}

return &ComponentVersion{
VersionParts: versionParts,
}
}

func (cv *ComponentVersion) CompareTo(v *ComponentVersion) int {
if v == nil {
return 1
}
left := cv.VersionParts
right := v.VersionParts

var max int
if len(left) < len(right) {
max = len(left)
} else {
max = len(right)
}
for i := 0; i < max; i++ {
lStr := left[i]
rStr := right[i]
if lStr == rStr {
continue
}
l, lerr := strconv.Atoi(lStr)
r, rerr := strconv.Atoi(rStr)
if lerr != nil || rerr != nil {
comp := strings.Compare(lStr, rStr)
if comp < 0 {
return -1
} else if comp > 0 {
return 1
}
}
if l < r {
return -1
} else if l > r {
return 1
}
}
if len(left) == max && len(right) == len(left) + 1 && right[len(right) - 1] == "0" {
return 0
} else if len(right) == max && len(left) == len(right) + 1 && left[len(left) - 1] == "0" {
return 0
} else {
if len(left) > len(right) {
return 1
}
if len(right) > len(left) {
return -1
}
if len(right) == len(left) {
return 0
}
}
return 0
}
59 changes: 59 additions & 0 deletions cvefeed/nvd/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nvd

import "testing"

func TestComponentVersion_CompareTo(t *testing.T) {
type fields struct {
VersionParts []string
}
type args struct {
v *ComponentVersion
}
tests := []struct {
name string
fields fields
args args
want int
}{
{
name: "test-version-compare",
fields: fields{
VersionParts: ParseVersion("2.6.3").VersionParts,
},
args: args{
v: ParseVersion("2.6.4"),
},
want: -1,
},
{
name: "test-version-compare",
fields: fields{
VersionParts: ParseVersion("7.0.0").VersionParts,
},
args: args{
v: ParseVersion("7.0.0."),
},
want: 0,
},
{
name: "test-version-compare",
fields: fields{
VersionParts: ParseVersion("x.y.z").VersionParts,
},
args: args{
v: ParseVersion("7.0.0"),
},
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cv := &ComponentVersion{
VersionParts: tt.fields.VersionParts,
}
if got := cv.CompareTo(tt.args.v); got != tt.want {
t.Errorf("ComponentVersion.CompareTo() = %v, want %v", got, tt.want)
}
})
}
}
2 changes: 1 addition & 1 deletion wfn/wfn.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var KnownParts = map[string]string{
// Possible logical value of Attributes
// empty string considered ANY when parsing and unquoted "-" is illegal in WFN attribute-value
const (
Any = ""
Any = "*"
NA = "-"
)

Expand Down