Skip to content

Commit

Permalink
Add vex.AugmentMatches() to the vex processor
Browse files Browse the repository at this point in the history
This commit adds a new AugmentMatches() phase to the VEX processor.

This new step goes throught the configured ignore rules and acts on any
that have `affected` or `under_investigtion` as status.

The purpose of this rule is to move matches back from the ignored matches
list to the active results when a statement with either of those statuses
apply to ignored matches.

Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
  • Loading branch information
puerco committed Aug 23, 2023
1 parent 128f85b commit 4d41133
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 2 deletions.
129 changes: 127 additions & 2 deletions grype/vex/openvex/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ func New() *Processor {
return &Processor{}
}

// Match captures the criteria that caused a vulnerability to match
type Match struct {
Statement openvex.Statement
}

// SearchedBy captures the prameters used to search through the VEX data
type SearchedBy struct {
Vulnerability string
Product string
Subcomponents []string
}

// augmentStatuses are the VEX statuses that augment results
var augmentStatuses = []openvex.Status{
openvex.StatusAffected,
openvex.StatusUnderInvestigation,
}

// filterStatuses are the VEX statuses that filter matched to the ignore list
var ignoreStatuses = []openvex.Status{
openvex.StatusNotAffected,
openvex.StatusFixed,
}

// ReadVexDocuments reads and merges VEX documents
func (ovm *Processor) ReadVexDocuments(docs []string) (interface{}, error) {
// Combine all VEX documents into a single VEX document
Expand Down Expand Up @@ -157,7 +181,7 @@ func (ovm *Processor) FilterMatches(
continue
}

rule := matchingRule(ignoreRules, m, statement)
rule := matchingRule(ignoreRules, m, statement, ignoreStatuses)
if rule == nil {
fmt.Fprintf(outfile, " >> None of the %d rules apply to the statement %+v\n\n", len(ignoreRules), statement)
remainingMatches.Add(m)
Expand Down Expand Up @@ -191,10 +215,15 @@ func (ovm *Processor) FilterMatches(

// matchingRule cycles through a set of ignore rules and returns the first
// one that matches the statement and the match. Returns nil if none match.
func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *openvex.Statement) *match.IgnoreRule {
func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *openvex.Statement, allowedStatuses []openvex.Status) *match.IgnoreRule {
ms := match.NewMatches()
ms.Add(m)

revStatuses := map[string]struct{}{}
for _, s := range allowedStatuses {
revStatuses[string(s)] = struct{}{}
}

for _, rule := range ignoreRules {
// If the rule has more conditions than just the VEX statement, check if
// it applies to the current match.
Expand All @@ -212,6 +241,13 @@ func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *open
continue
}

// If the rule has a statement other than the allowed ones, skip:
if len(revStatuses) > 0 && rule.VexStatus != "" {
if _, ok := revStatuses[rule.VexStatus]; !ok {
continue
}
}

// If the rule applies to a VEX justification it needs to match the
// statement, note that justifications only apply to not_affected:
if statement.Status == openvex.StatusNotAffected && rule.VexJustification != "" &&
Expand All @@ -233,3 +269,92 @@ func matchingRule(ignoreRules []match.IgnoreRule, m match.Match, statement *open
}
return nil
}

// AugmentMatches adds results to the match.Matches array when matching data
// about an affected VEX product is found on loaded VEX documents. Matches
// are moved from the ignore list or synthesized when no previous data is found.
func (ovm *Processor) AugmentMatches(
docRaw interface{}, ignoreRules []match.IgnoreRule, pkgContext *pkg.Context, remainingMatches *match.Matches, ignoredMatches []match.IgnoredMatch,
) (*match.Matches, []match.IgnoredMatch, error) {
doc, ok := docRaw.(*openvex.VEX)
if !ok {
return nil, nil, errors.New("unable to cast vex document as openvex")
}

nignoredMatches := []match.IgnoredMatch{}

outfile, err := os.Create("/tmp/output.txt")
if err != nil {
return nil, nil, fmt.Errorf("unable to open output file: %w", err)
}
defer outfile.Close()

fmt.Fprintf(outfile, "VEX Debug Data\n")
fmt.Fprintf(outfile, "VEX Ignore Rules:\n\n%+v\n\n", ignoreRules)
fmt.Fprintf(outfile, "Processing %d matches\n", len(remainingMatches.Sorted()))

products, err := productIDentifiersFromContext(pkgContext)
if err != nil {
return nil, nil, fmt.Errorf("reading product identifiers from context: %w", err)
}

// Now, let's go through grype's matches
for _, m := range ignoredMatches {
var statement *openvex.Statement
var searchedBy *SearchedBy
subcmp := subcomponentIdentifiersFromMatch(&m.Match)

fmt.Fprintf(outfile, "%s → %+v → %+v\n", m.Vulnerability.ID, products, subcmp)

// Range through the product's different names to see if they match the
// statement data
for _, product := range products {
if matchingStatements := doc.Matches(m.Vulnerability.ID, product, subcmp); len(matchingStatements) != 0 {
// TODO Take this from the rules
if matchingStatements[0].Status != openvex.Status(openvex.StatusAffected) &&
matchingStatements[0].Status != openvex.Status(openvex.StatusUnderInvestigation) {
// Ignore statement if it from a different status
break
}
statement = &matchingStatements[0]
searchedBy = &SearchedBy{
Vulnerability: m.Vulnerability.ID,
Product: product,
Subcomponents: subcmp,
}
break
}
}

// No data about this match's component. Next.
if statement == nil {
fmt.Fprintf(outfile, " >> No statement matches the match\n\n")
nignoredMatches = append(nignoredMatches, m)
continue
}

// Only match if rules to augment are configured
rule := matchingRule(ignoreRules, m.Match, statement, augmentStatuses)
if rule == nil {
fmt.Fprintf(outfile, " >> None of the %d rules apply to the statement %+v\n\n", len(ignoreRules), statement)
nignoredMatches = append(nignoredMatches, m)
continue
}

newMatch := m.Match
newMatch.Details = append(newMatch.Details, match.Detail{
Type: match.ExactDirectMatch,
SearchedBy: searchedBy,
Found: Match{
Statement: *statement,
},
Matcher: match.OpenVexMatcher,
})

remainingMatches.Add(newMatch)
}

fmt.Fprintf(outfile, " %d matches remaining %d ignored\n\n", len(remainingMatches.Enumerate()), len(ignoredMatches))

return remainingMatches, nignoredMatches, nil
}
12 changes: 12 additions & 0 deletions grype/vex/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type vexProcessorImplementation interface {
// the scanning context and matching results and filters the fixed and
// not_affected results,moving them to the list of ignored matches.
FilterMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error)

// AugmentMatches reads known affected VEX products from loaded documents and
// adds new results to the scanner results when the product is marked as
// affected in the VEX data.
AugmentMatches(interface{}, []match.IgnoreRule, *pkg.Context, *match.Matches, []match.IgnoredMatch) (*match.Matches, []match.IgnoredMatch, error)
}

// getVexImplementation this function returns the vex processor implementation
Expand Down Expand Up @@ -80,6 +85,13 @@ func (vm *Processor) ApplyVEX(pkgContext *pkg.Context, remainingMatches *match.M
return nil, nil, fmt.Errorf("checking matches against VEX data: %w", err)
}

remainingMatches, ignoredMatches, err = vm.impl.AugmentMatches(
rawVexData, extractVexRules(vm.Options.IgnoreRules), pkgContext, remainingMatches, ignoredMatches,
)
if err != nil {
return nil, nil, fmt.Errorf("checking matches to augment from VEX data: %w", err)
}

return remainingMatches, ignoredMatches, nil
}

Expand Down

0 comments on commit 4d41133

Please sign in to comment.