cvefeed/nvd/match_cpe.go (88 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nvd
import (
"fmt"
"github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
"github.com/facebookincubator/nvdtools/wfn"
)
// cpeMatch is a wrapper around the actual NVDCVEFeedJSON10DefCPEMatch
type cpeMatch struct {
*wfn.Attributes
vulnerable bool
versionEndExcluding string
versionEndIncluding string
versionStartExcluding string
versionStartIncluding string
hasVersionRanges bool
}
// Matcher returns an object which knows how to match attributes
func cpeMatcher(ID string, nvdMatch *schema.NVDCVEFeedJSON10DefCPEMatch) (wfn.Matcher, error) {
parse := func(uri string) (*wfn.Attributes, error) {
if uri == "" {
return nil, fmt.Errorf("%s: can't parse empty uri", ID)
}
return wfn.Parse(uri)
}
// parse
match := cpeMatch{vulnerable: nvdMatch.Vulnerable}
var err error
if match.Attributes, err = parse(nvdMatch.Cpe23Uri); err != nil {
if match.Attributes, err = parse(nvdMatch.Cpe22Uri); err != nil {
return nil, fmt.Errorf("%s: unable to parse both cpe2.2 and cpe2.3", ID)
}
}
match.versionEndExcluding = nvdMatch.VersionEndExcluding
match.versionEndIncluding = nvdMatch.VersionEndIncluding
match.versionStartExcluding = nvdMatch.VersionStartExcluding
match.versionStartIncluding = nvdMatch.VersionStartIncluding
if match.versionStartIncluding != "" || match.versionStartExcluding != "" ||
match.versionEndIncluding != "" || match.versionEndExcluding != "" {
match.hasVersionRanges = true
}
return &match, nil
}
// Match is part of the Matcher interface
func (cm *cpeMatch) Match(attrs []*wfn.Attributes, requireVersion bool) (matches []*wfn.Attributes) {
for _, attr := range attrs {
if cm.match(attr, requireVersion) {
matches = append(matches, attr)
}
}
return matches
}
// Match implements wfn.Matcher interface
func (cm *cpeMatch) match(attr *wfn.Attributes, requireVersion bool) bool {
if cm == nil || cm.Attributes == nil {
return false
}
if requireVersion {
// if we require version, then we need either version ranges or version not to be *
if !cm.hasVersionRanges && cm.Attributes.Version == wfn.Any {
return false
}
}
// here we have a version: either actual one or ranges
// check whether everything except for version matches
if !cm.Attributes.MatchWithoutVersion(attr) {
return false
}
if cm.Attributes.Version == wfn.Any {
if !cm.hasVersionRanges {
// if version is any and doesn't have version ranges, then it matches any
return !requireVersion
} // otherwise we try to match it at the end of the function
} else if cm.Attributes.MatchOnlyVersion(attr) {
return true // version matched
}
// if it got to here, it means:
// - matched attr without version
// - didn't match version, or require version was set and version was *
if attr.Version == wfn.Any {
return true
}
if !cm.hasVersionRanges {
return false
}
// match version to ranges
ver := wfn.StripSlashes(attr.Version)
matches := true
if cm.versionStartIncluding != "" {
matches = matches && smartVerCmp(ver, cm.versionStartIncluding) >= 0
}
if cm.versionStartExcluding != "" {
matches = matches && smartVerCmp(ver, cm.versionStartExcluding) > 0
}
if cm.versionEndIncluding != "" {
matches = matches && smartVerCmp(ver, cm.versionEndIncluding) <= 0
}
if cm.versionEndExcluding != "" {
matches = matches && smartVerCmp(ver, cm.versionEndExcluding) < 0
}
return matches
}