cvefeed/stats.go (121 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 cvefeed
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/facebookincubator/nvdtools/cvefeed/nvd"
"github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
)
var cpeParts = map[string]string{
"a": "application",
"h": "hardware",
"o": "operating system",
}
type stack struct {
items []string
rwLock sync.RWMutex
}
func (s *stack) push(item string) {
s.rwLock.Lock()
defer s.rwLock.Unlock()
if s.items == nil {
s.items = []string{}
}
s.items = append(s.items, item)
}
func (s *stack) pop() (string, bool) {
if s.isEmpty() {
return "", false
}
s.rwLock.Lock()
defer s.rwLock.Unlock()
item := s.items[len(s.items)-1]
s.items = s.items[0 : len(s.items)-1]
return item, true
}
func (s *stack) isEmpty() bool {
s.rwLock.Lock()
defer s.rwLock.Unlock()
return len(s.items) == 0
}
// Stats contains the stats information of a NVD JSON feed
type Stats struct {
totalCVEs int64
totalRules int64
totalRulesWithAND int64
operatorANDs map[string]int64
}
// NewStats creates a new Stats object
func NewStats() *Stats {
s := Stats{}
s.Reset()
return &s
}
// Reset clears out a Stats object
func (s *Stats) Reset() {
s.totalCVEs = 0
s.totalRules = 0
s.totalRulesWithAND = 0
s.operatorANDs = make(map[string]int64)
}
// ReportOperatorAND prints the stats of operator AND
func (s *Stats) ReportOperatorAND() {
if s.totalRulesWithAND <= 0 {
fmt.Println("No rules found with AND operator.")
return
}
keys := make([]string, 0, len(s.operatorANDs))
for key := range s.operatorANDs {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return s.operatorANDs[keys[i]] > s.operatorANDs[keys[j]]
})
fmt.Printf("Total rules with AND operator: %0.2f%%\n", percentage(s.totalRulesWithAND, s.totalRules))
for _, key := range keys {
fmt.Printf("%05.2f%%: %s\n", percentage(s.operatorANDs[key], s.totalRulesWithAND), key)
}
}
// Gather feeds a Stats object by gathering stats from a NVD JSON feed dictionary
func (s *Stats) Gather(dict Dictionary) {
for key := range dict {
s.totalCVEs++
schema := dict[key].(*nvd.Vuln).Schema()
for _, node := range schema.Configurations.Nodes {
s.totalRules++
rule := flattenRule(node, &stack{})
if strings.Contains(rule, "AND") {
s.totalRulesWithAND++
s.operatorANDs[rule]++
}
}
}
}
func flattenRule(node *schema.NVDCVEFeedJSON10DefNode, operators *stack) string {
cpePart := ""
operators.push(node.Operator)
switch {
case len(node.Children) > 0:
outputs := []string{}
for _, c := range node.Children {
outputs = append(outputs, flattenRule(c, operators))
}
operator, _ := operators.pop()
return fmt.Sprintf("(%s)", strings.Join(outputs, fmt.Sprintf(" %s ", operator)))
case len(node.CPEMatch) > 0:
for _, cpeMatch := range node.CPEMatch {
cpeItems := strings.Split(cpeMatch.Cpe23Uri, ":")
if len(cpeItems) > 2 {
part := cpeItems[2]
if _, ok := cpeParts[part]; ok && !strings.Contains(cpePart, part) {
cpePart += part
}
}
}
operator, _ := operators.pop()
if len(cpePart) > 1 {
return fmt.Sprintf("(%s)", strings.Join(strings.Split(cpePart, ""), fmt.Sprintf(" %s ", operator)))
}
}
return cpePart
}
func percentage(partial, total int64) (delta float64) {
delta = (float64(partial) / float64(total)) * 100
return
}