Skip to content

Commit

Permalink
add lazy evaluation option
Browse files Browse the repository at this point in the history
  • Loading branch information
bradleyjkemp committed Sep 5, 2024
1 parent 0da7f75 commit 87dd4d8
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 8 deletions.
34 changes: 29 additions & 5 deletions evaluator/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type RuleEvaluator struct {

expandPlaceholder func(ctx context.Context, placeholderName string) ([]string, error)
caseSensitive bool
lazy bool
comparators map[string]modifiers.Comparator

count func(ctx context.Context, gb GroupedByValues) (float64, error)
Expand Down Expand Up @@ -105,16 +106,39 @@ func (rule RuleEvaluator) matches(ctx context.Context, event Event, comparators
SearchResults: map[string]bool{},
ConditionResults: make([]bool, len(rule.Detection.Conditions)),
}
for identifier, search := range rule.Detection.Searches {

if !rule.lazy {
// must evaluate all searches up front
for identifier, search := range rule.Detection.Searches {
var err error
result.SearchResults[identifier], err = rule.evaluateSearch(ctx, search, event, rule.comparators)
if err != nil {
return Result{}, fmt.Errorf("error evaluating search %s: %w", identifier, err)
}
}
}

var searchErr error
searchResults := func(identifier string) bool {
searchResult, ok := result.SearchResults[identifier]
if ok {
return searchResult
}

search, ok := rule.Detection.Searches[identifier]
if !ok {
return false // compatibility with old behaviour
}
var err error
result.SearchResults[identifier], err = rule.evaluateSearch(ctx, search, event, rule.comparators)
if err != nil {
return Result{}, fmt.Errorf("error evaluating search %s: %w", identifier, err)
searchErr = fmt.Errorf("error evaluating search %s: %w", identifier, err)
return false
}
return result.SearchResults[identifier]
}

for conditionIndex, condition := range rule.Detection.Conditions {
searchMatches := rule.evaluateSearchExpression(condition.Search, result.SearchResults)
searchMatches := rule.evaluateSearchExpression(condition.Search, searchResults)

switch {
// Event didn't match filters
Expand Down Expand Up @@ -142,5 +166,5 @@ func (rule RuleEvaluator) matches(ctx context.Context, event Event, comparators
}
}

return result, nil
return result, searchErr
}
4 changes: 2 additions & 2 deletions evaluator/evaluate_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"strings"
)

func (rule RuleEvaluator) evaluateSearchExpression(search sigma.SearchExpr, searchResults map[string]bool) bool {
func (rule RuleEvaluator) evaluateSearchExpression(search sigma.SearchExpr, searchResults func(string) bool) bool {
switch s := search.(type) {
case sigma.And:
for _, node := range s {
Expand All @@ -36,7 +36,7 @@ func (rule RuleEvaluator) evaluateSearchExpression(search sigma.SearchExpr, sear

case sigma.SearchIdentifier:
// If `s.Name` is not defined, this is always false
return searchResults[s.Name]
return searchResults(s.Name)

case sigma.OneOfThem:
for name := range rule.Detection.Searches {
Expand Down
31 changes: 30 additions & 1 deletion evaluator/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ func FuzzRuleMatches(f *testing.F) {
eval := ForRule(r, WithConfig(c))
_, err = eval.Matches(context.Background(), e)
})

}

func FuzzRuleBundleMatches(f *testing.F) {
Expand Down Expand Up @@ -139,3 +138,33 @@ func FuzzRuleBundleMatches(f *testing.F) {
}
})
}

func FuzzRuleLazyMatches(f *testing.F) {
f.Add(testRule, testConfig, `{"foo": "bar", "bar": "baz"}`)
f.Fuzz(func(t *testing.T, rule, config, payload string) {
r, err := sigma.ParseRule([]byte(rule))
if err != nil {
return
}
c, err := sigma.ParseConfig([]byte(config))
if err != nil {
return
}

var e Event
json.Unmarshal([]byte(payload), &e)

eval := ForRule(r, WithConfig(c))
lazyEval := ForRule(r, WithConfig(c), LazyEvaluation)
evalResult, evalErr := eval.Matches(context.Background(), e)
lazyEvalResult, lazyEvalErr := lazyEval.Matches(context.Background(), e)

if (evalErr == nil) != (lazyEvalErr == nil) {
f.Fatal("err mismatch", evalErr, lazyEvalErr)
}

if evalResult.Match != lazyEvalResult.Match {
f.Fatal("result mismatch", evalErr, lazyEvalErr)
}
})
}
5 changes: 5 additions & 0 deletions evaluator/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,8 @@ func CaseSensitive(e *RuleEvaluator) {
e.caseSensitive = true
e.comparators = modifiers.ComparatorsCaseSensitive
}

// LazyEvaluation allows the evaluator to skip evaluating searches if they won't affect the overall match result
func LazyEvaluation(e *RuleEvaluator) {
e.lazy = true
}

0 comments on commit 87dd4d8

Please sign in to comment.