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 }