diff --git a/pkg/security/secl/compiler/eval/eval_test.go b/pkg/security/secl/compiler/eval/eval_test.go index ef749a01f119b..0f4c13fe3f208 100644 --- a/pkg/security/secl/compiler/eval/eval_test.go +++ b/pkg/security/secl/compiler/eval/eval_test.go @@ -531,7 +531,7 @@ func TestPartial(t *testing.T) { {Expr: `process.name =~ "/usr/sbin/*" && process.uid == 0 && process.is_root`, Field: "process.uid", IsDiscarder: true}, } - ctx := NewContext(unsafe.Pointer(&event)) + ctx := NewContext(event.GetPointer()) for _, test := range tests { model := &testModel{} diff --git a/pkg/security/secl/compiler/eval/state.go b/pkg/security/secl/compiler/eval/state.go index eca871291848a..4d224fd0d68d0 100644 --- a/pkg/security/secl/compiler/eval/state.go +++ b/pkg/security/secl/compiler/eval/state.go @@ -34,6 +34,12 @@ func (s *State) UpdateFieldValues(field Field, value FieldValue) error { if !ok { values = []FieldValue{} } + for _, v := range values { + if v == value { + return nil + } + } + values = append(values, value) s.fieldValues[field] = values return s.model.ValidateField(field, value) diff --git a/pkg/security/secl/rules/approvers.go b/pkg/security/secl/rules/approvers.go new file mode 100644 index 0000000000000..fa145e2cd17e3 --- /dev/null +++ b/pkg/security/secl/rules/approvers.go @@ -0,0 +1,134 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package rules + +import ( + "github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval" +) + +// Approvers are just filter values indexed by field +type Approvers map[eval.Field]FilterValues + +// isAnApprover returns whether the given value is an approver for the given rule +func isAnApprover(event eval.Event, ctx *eval.Context, rule *Rule, field eval.Field, value interface{}) (bool, error) { + if err := event.SetFieldValue(field, value); err != nil { + return false, err + } + origResult, err := rule.PartialEval(ctx, field) + if err != nil { + return false, err + } + + notValue, err := eval.NotOfValue(value) + if err != nil { + return false, err + } + + if err := event.SetFieldValue(field, notValue); err != nil { + return false, err + } + notResult, err := rule.PartialEval(ctx, field) + if err != nil { + return false, err + } + + if origResult && !notResult { + return true, nil + } + + return false, nil +} + +func bitmaskCombinations(bitmasks []int) []int { + if len(bitmasks) == 0 { + return nil + } + + var result []int + for i := 0; i < (1 << len(bitmasks)); i++ { + var mask int + for j, value := range bitmasks { + if (i & (1 << j)) > 0 { + mask |= value + } + } + result = append(result, mask) + } + + return result +} + +// GetApprovers returns approvers for the given rules +func GetApprovers(rules []*Rule, event eval.Event, fieldCaps FieldCapabilities) (Approvers, error) { + approvers := make(Approvers) + + ctx := eval.NewContext(event.GetPointer()) + + // for each rule we should at least find one approver otherwise we will return no approver for the field + for _, rule := range rules { + var bestFilterField eval.Field + var bestFilterValues FilterValues + var bestFilterWeight int + + LOOP: + for _, fieldCap := range fieldCaps { + field := fieldCap.Field + + var filterValues FilterValues + var bitmasks []int + + for _, value := range rule.GetFieldValues(field) { + switch value.Type { + case eval.ScalarValueType, eval.PatternValueType: + isAnApprover, err := isAnApprover(event, ctx, rule, field, value.Value) + if err != nil { + return nil, err + } + + if isAnApprover { + filterValues = filterValues.Merge(FilterValue{Field: field, Value: value.Value, Type: value.Type}) + } else if fieldCap.Types&eval.BitmaskValueType == 0 { + // if not a bitmask we need to have all the value as approvers + // basically a list of values ex: in ["test123", "test456"] + continue LOOP + } + case eval.BitmaskValueType: + bitmasks = append(bitmasks, value.Value.(int)) + } + } + + for _, bitmask := range bitmaskCombinations(bitmasks) { + isAnApprover, err := isAnApprover(event, ctx, rule, field, bitmask) + if err != nil { + return nil, err + } + + if isAnApprover { + filterValues = filterValues.Merge(FilterValue{Field: field, Value: bitmask, Type: eval.BitmaskValueType}) + } + } + + if len(filterValues) == 0 || !fieldCaps.Validate(filterValues) { + continue + } + + if bestFilterValues == nil || fieldCap.FilterWeight > bestFilterWeight { + bestFilterField = field + bestFilterValues = filterValues + bestFilterWeight = fieldCap.FilterWeight + } + } + + // no filter value for a rule thus no approver for the event type + if bestFilterValues == nil { + return nil, nil + } + + approvers[bestFilterField] = append(approvers[bestFilterField], bestFilterValues...) + } + + return approvers, nil +} diff --git a/pkg/security/secl/rules/bucket.go b/pkg/security/secl/rules/bucket.go index 7d46aa9fe562a..e2803f1c96262 100644 --- a/pkg/security/secl/rules/bucket.go +++ b/pkg/security/secl/rules/bucket.go @@ -13,9 +13,6 @@ import ( "github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval" ) -// Approvers are just filter values indexed by field -type Approvers map[eval.Field]FilterValues - // RuleBucket groups rules with the same event type type RuleBucket struct { rules []*Rule @@ -48,83 +45,3 @@ func (rb *RuleBucket) AddRule(rule *Rule) error { func (rb *RuleBucket) GetRules() []*Rule { return rb.rules } - -// FieldCombinations - array all the combinations of field -type FieldCombinations [][]eval.Field - -func (a FieldCombinations) Len() int { return len(a) } -func (a FieldCombinations) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a FieldCombinations) Less(i, j int) bool { return len(a[i]) < len(a[j]) } - -func fieldCombinations(fields []eval.Field) FieldCombinations { - var result FieldCombinations - - for i := 1; i < (1 << len(fields)); i++ { - var subResult []eval.Field - for j, field := range fields { - if (i & (1 << j)) > 0 { - subResult = append(subResult, field) - } - } - result = append(result, subResult) - } - - // order the list with the single field first - sort.Sort(result) - - return result -} - -func getFilterValuesWeight(filterValues FilterValues, fieldCaps FieldCapabilities) int { - var maxWeight int - for _, filterValue := range filterValues { - for _, fc := range fieldCaps { - if filterValue.Field == fc.Field && fc.FilterWeight > maxWeight { - maxWeight = fc.FilterWeight - } - } - } - return maxWeight -} - -// GetApprovers returns the approvers for an event -func (rb *RuleBucket) GetApprovers(event eval.Event, fieldCaps FieldCapabilities) (Approvers, error) { - fcs := fieldCombinations(fieldCaps.GetFields()) - - approvers := make(Approvers) - for _, rule := range rb.rules { - truthTable, err := newTruthTable(rule.Rule, event) - if err != nil { - return nil, err - } - - var filterValues FilterValues - var maxWeight int - - for _, fields := range fcs { - candidates := truthTable.getApprovers(fields...) - - // only one combination of the approvers is required, keep the best one meaning - // the one with the highest weight - if len(candidates) > 0 && fieldCaps.Validate(candidates) { - // as fieldCombinations return all the combinations in the less fields order - // we keep the highest weight with a minimal set of fields. - if weight := getFilterValuesWeight(candidates, fieldCaps); filterValues == nil || weight > maxWeight { - filterValues = candidates - maxWeight = weight - } - } - } - - if len(filterValues) == 0 { - return nil, &ErrNoApprover{Fields: fieldCaps.GetFields()} - } - - // dispatch per field - for _, filterValue := range filterValues { - approvers[filterValue.Field] = approvers[filterValue.Field].Merge(filterValue) - } - } - - return approvers, nil -} diff --git a/pkg/security/secl/rules/filter_values.go b/pkg/security/secl/rules/filter_values.go index 9fe21cb02aefd..e90effcc4d8bc 100644 --- a/pkg/security/secl/rules/filter_values.go +++ b/pkg/security/secl/rules/filter_values.go @@ -16,12 +16,6 @@ type FilterValue struct { Field eval.Field Value interface{} Type eval.FieldValueType - - // indicate process.uid == process.gid for example - isScalar bool - // opposite value of the field Value - notValue interface{} - not bool } // Merge merges to FilterValues ensuring there is no duplicate value @@ -29,7 +23,7 @@ func (fv FilterValues) Merge(n ...FilterValue) FilterValues { LOOP: for _, v1 := range n { for _, v2 := range fv { - if v1.Field == v2.Field && v1.Value == v2.Value && v1.not == v2.not { + if v1.Field == v2.Field && v1.Value == v2.Value { continue LOOP } } diff --git a/pkg/security/secl/rules/ruleset.go b/pkg/security/secl/rules/ruleset.go index 1e67e156e637d..3588b0fc10f8d 100644 --- a/pkg/security/secl/rules/ruleset.go +++ b/pkg/security/secl/rules/ruleset.go @@ -396,7 +396,7 @@ func (rs *RuleSet) GetApprovers(fieldCaps map[eval.EventType]FieldCapabilities) } eventApprovers, err := rs.GetEventApprovers(eventType, caps) - if err != nil { + if err != nil || len(eventApprovers) == 0 { continue } approvers[eventType] = eventApprovers @@ -412,7 +412,7 @@ func (rs *RuleSet) GetEventApprovers(eventType eval.EventType, fieldCaps FieldCa return nil, ErrNoEventTypeBucket{EventType: eventType} } - return bucket.GetApprovers(rs.eventCtor(), fieldCaps) + return GetApprovers(bucket.rules, rs.eventCtor(), fieldCaps) } // GetFieldValues returns all the values of the given field diff --git a/pkg/security/secl/rules/ruleset_test.go b/pkg/security/secl/rules/ruleset_test.go index 3ec5624d15180..bb962bf473144 100644 --- a/pkg/security/secl/rules/ruleset_test.go +++ b/pkg/security/secl/rules/ruleset_test.go @@ -175,9 +175,9 @@ func TestRuleSetDiscarders(t *testing.T) { } } -func TestRuleSetFilters1(t *testing.T) { +func TestRuleSetApprovers1(t *testing.T) { rs := newRuleSet() - addRuleExpr(t, rs, `open.filename in ["/etc/passwd", "/etc/shadow"] && (process.uid == 0 || process.gid == 0)`) + addRuleExpr(t, rs, `open.filename in ["/etc/passwd", "/etc/shadow"] && (process.uid == 0 && process.gid == 0)`) caps := FieldCapabilities{ { @@ -192,17 +192,21 @@ func TestRuleSetFilters1(t *testing.T) { }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { + t.Fatal("should get an approver") } - if _, exists := approvers["process.uid"]; !exists { + if values, exists := approvers["process.gid"]; !exists || len(values) != 1 { t.Fatal("expected approver not found") } - if _, exists := approvers["process.gid"]; !exists { - t.Fatal("expected approver not found") + if _, exists := approvers["process.uid"]; exists { + t.Fatal("unexpected approver found") + } + + if _, exists := approvers["open.filename"]; exists { + t.Fatal("unexpected approver found") } caps = FieldCapabilities{ @@ -212,32 +216,20 @@ func TestRuleSetFilters1(t *testing.T) { }, } - approvers, err = rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ = rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { + t.Fatal("should get an approver") } if values, exists := approvers["open.filename"]; !exists || len(values) != 2 { - t.Fatalf("expected approver not found: %v", values) - } - - caps = FieldCapabilities{ - { - Field: "process.uid", - Types: eval.ScalarValueType, - }, - } - - _, err = rs.GetEventApprovers("open", caps) - if err == nil { - t.Fatal("shouldn't get any approver") + t.Fatal("expected approver not found") } } -func TestRuleSetFilters2(t *testing.T) { +func TestRuleSetApprovers2(t *testing.T) { exprs := []string{ `open.filename in ["/etc/passwd", "/etc/shadow"] && process.uid == 0`, - `open.flags & O_CREAT > 0 && (process.uid == 0 || process.gid == 0)`, + `open.flags & O_CREAT > 0 && process.uid == 0`, } rs := newRuleSet() @@ -250,8 +242,8 @@ func TestRuleSetFilters2(t *testing.T) { }, } - _, err := rs.GetEventApprovers("open", caps) - if err == nil { + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) != 0 { t.Fatal("shouldn't get any approver") } @@ -261,37 +253,28 @@ func TestRuleSetFilters2(t *testing.T) { Types: eval.ScalarValueType, FilterWeight: 3, }, - { - Field: "process.gid", - Types: eval.ScalarValueType, - FilterWeight: 2, - }, { Field: "process.uid", Types: eval.ScalarValueType, - FilterWeight: 1, + FilterWeight: 2, }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ = rs.GetEventApprovers("open", caps) + if len(approvers) != 2 { + t.Fatal("should get 2 field approvers") } if values, exists := approvers["open.filename"]; !exists || len(values) != 2 { - t.Fatal("expected approver not found") + t.Fatalf("expected approver not found: %+v", values) } if _, exists := approvers["process.uid"]; !exists { t.Fatal("expected approver not found") } - - if _, exists := approvers["process.gid"]; !exists { - t.Fatal("expected approver not found") - } } -func TestRuleSetFilters3(t *testing.T) { +func TestRuleSetApprovers3(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.filename in ["/etc/passwd", "/etc/shadow"] && (process.uid == process.gid)`) @@ -302,21 +285,17 @@ func TestRuleSetFilters3(t *testing.T) { }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) != 1 { + t.Fatal("should get only one field approver") } if values, exists := approvers["open.filename"]; !exists || len(values) != 2 { t.Fatal("expected approver not found") } - - if len(approvers) != 1 { - t.Fatal("should get only one field approver") - } } -func TestRuleSetFilters4(t *testing.T) { +func TestRuleSetApprovers4(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.filename =~ "/etc/passwd" && process.uid == 0`) @@ -327,7 +306,7 @@ func TestRuleSetFilters4(t *testing.T) { }, } - if approvers, err := rs.GetEventApprovers("open", caps); err == nil { + if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) != 0 { t.Fatalf("shouldn't get any approver, got: %+v", approvers) } @@ -338,12 +317,12 @@ func TestRuleSetFilters4(t *testing.T) { }, } - if _, err := rs.GetEventApprovers("open", caps); err != nil { + if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) == 0 { t.Fatal("expected approver not found") } } -func TestRuleSetFilters5(t *testing.T) { +func TestRuleSetApprovers5(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `(open.flags & O_CREAT > 0 || open.flags & O_EXCL > 0) && open.flags & O_RDWR > 0`) @@ -352,14 +331,10 @@ func TestRuleSetFilters5(t *testing.T) { Field: "open.flags", Types: eval.ScalarValueType | eval.BitmaskValueType, }, - { - Field: "open.filename", - Types: eval.ScalarValueType, - }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { t.Fatal("expected approver not found") } @@ -370,7 +345,7 @@ func TestRuleSetFilters5(t *testing.T) { } } -func TestRuleSetFilters6(t *testing.T) { +func TestRuleSetApprovers6(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.filename == "123456"`) @@ -384,7 +359,7 @@ func TestRuleSetFilters6(t *testing.T) { }, } - if _, err := rs.GetEventApprovers("open", caps); err != nil { + if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) == 0 { t.Fatal("expected approver not found") } @@ -398,12 +373,12 @@ func TestRuleSetFilters6(t *testing.T) { }, } - if _, err := rs.GetEventApprovers("open", caps); err == nil { + if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) > 0 { t.Fatal("shouldn't get any approver") } } -func TestRuleSetFilters7(t *testing.T) { +func TestRuleSetApprovers7(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT`) @@ -414,8 +389,8 @@ func TestRuleSetFilters7(t *testing.T) { }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { t.Fatal("expected approver not found") } @@ -424,7 +399,7 @@ func TestRuleSetFilters7(t *testing.T) { } } -func TestRuleSetFilters8(t *testing.T) { +func TestRuleSetApprovers8(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT && open.filename in ["/etc/passwd", "/etc/shadow"]`) @@ -440,9 +415,9 @@ func TestRuleSetFilters8(t *testing.T) { }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { + t.Fatal("expected approver not found") } if values, exists := approvers["open.filename"]; !exists || len(values) != 2 { @@ -454,7 +429,7 @@ func TestRuleSetFilters8(t *testing.T) { } } -func TestRuleSetFilters9(t *testing.T) { +func TestRuleSetApprovers9(t *testing.T) { rs := newRuleSet() addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT && open.filename not in ["/etc/passwd", "/etc/shadow"]`) @@ -470,9 +445,9 @@ func TestRuleSetFilters9(t *testing.T) { }, } - approvers, err := rs.GetEventApprovers("open", caps) - if err != nil { - t.Fatal(err) + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { + t.Fatal("expected approver not found") } if _, exists := approvers["open.filename"]; exists { @@ -484,6 +459,82 @@ func TestRuleSetFilters9(t *testing.T) { } } +func TestRuleSetApprovers10(t *testing.T) { + rs := newRuleSet() + addRuleExpr(t, rs, `open.filename in [~"/etc/passwd", "/etc/shadow"]`) + + caps := FieldCapabilities{ + { + Field: "open.filename", + Types: eval.ScalarValueType, + FilterWeight: 3, + }, + } + + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) != 0 { + t.Fatal("shouldn't get an approver for filename") + } +} + +func TestRuleSetApprovers11(t *testing.T) { + rs := newRuleSet() + addRuleExpr(t, rs, `open.filename in [~"/etc/passwd", "/etc/shadow"]`) + + caps := FieldCapabilities{ + { + Field: "open.filename", + Types: eval.ScalarValueType | eval.PatternValueType, + FilterWeight: 3, + }, + } + + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) == 0 { + t.Fatal("expected approver not found") + } +} + +func TestRuleSetApprovers12(t *testing.T) { + exprs := []string{ + `open.filename in ["/etc/passwd", "/etc/shadow"]`, + `open.filename in [~"/etc/httpd", "/etc/nginx"]`, + } + + rs := newRuleSet() + addRuleExpr(t, rs, exprs...) + + caps := FieldCapabilities{ + { + Field: "open.filename", + Types: eval.ScalarValueType, + FilterWeight: 3, + }, + } + + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) != 0 { + t.Fatal("shouldn't get an approver for filename") + } +} + +func TestRuleSetApprovers13(t *testing.T) { + rs := newRuleSet() + addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_RDWR`) + + caps := FieldCapabilities{ + { + Field: "open.flags", + Types: eval.ScalarValueType | eval.BitmaskValueType, + }, + } + + approvers, _ := rs.GetEventApprovers("open", caps) + if len(approvers) != 0 { + t.Fatal("shouldn't get an approver for filename") + } +} + func TestGetRuleEventType(t *testing.T) { rule := &eval.Rule{ ID: "aaa", diff --git a/pkg/security/secl/rules/truth_table.go b/pkg/security/secl/rules/truth_table.go deleted file mode 100644 index ca3ea44b87d5d..0000000000000 --- a/pkg/security/secl/rules/truth_table.go +++ /dev/null @@ -1,273 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016-present Datadog, Inc. - -package rules - -import ( - "reflect" - - "github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval" -) - -type truthEntry struct { - Values FilterValues - Result bool -} - -type truthTable struct { - Entries []truthEntry -} - -func (tt *truthTable) isAlwaysFalseWith(filterValues ...FilterValue) bool { -LOOP: - for _, entry := range tt.Entries { - // keep entries for which all the filterValues are "not". We want to check whether - // with all the opposive values we can find an entry with a "true" result. In that - // case it means that the final result doesn't depend on the given field values. - for _, eValue := range entry.Values { - for _, fValue := range filterValues { - if eValue.Field == fValue.Field { - // since not scalar value we can't determine the result - if !eValue.isScalar { - return false - } - - if eValue.Value != fValue.notValue { - continue LOOP - } - } - } - } - - if entry.Result { - return false - } - } - return true -} - -// retrieve positive field value from the truth table for the given fields -func (tt *truthTable) getFieldValues(entry *truthEntry, fields ...string) FilterValues { - var filterValues FilterValues - for _, eValue := range entry.Values { - for _, field := range fields { - if eValue.Field == field { - if eValue.not || !eValue.isScalar { - return nil - } - filterValues = filterValues.Merge(eValue) - } - } - } - return filterValues -} - -func (tt *truthTable) getApprovers(fields ...string) FilterValues { - var allFilterValues FilterValues - - for _, entry := range tt.Entries { - // consider only True result for later check if an opposite result with the same - // field values. - if !entry.Result { - continue - } - - filterValues := tt.getFieldValues(&entry, fields...) - if filterValues == nil { - continue - } - - // check whether a result is "true" while having all the fields values set to the - // "not" value. In that case it means that the field value are not approvers. - if tt.isAlwaysFalseWith(filterValues...) { - allFilterValues = allFilterValues.Merge(filterValues...) - } - } - - return allFilterValues -} - -func combineBitmasks(bitmasks []int) []int { - var result []int - - for i := 0; i < (1 << len(bitmasks)); i++ { - var mask int - for j, value := range bitmasks { - if (i & (1 << j)) > 0 { - mask |= value - } - } - result = append(result, mask) - } - - return result -} - -func genFilterValues(rule *eval.Rule, event eval.Event) ([]FilterValues, error) { - var filterValues []FilterValues - for field, fValues := range rule.GetEvaluator().FieldValues { - // case where there is no static value, ex: process.gid == process.uid - // so generate fake value in order to be able to get the truth table - // note that we want to have the comparison returning true - if len(fValues) == 0 { - var value interface{} - - kind, err := event.GetFieldType(field) - if err != nil { - return nil, err - } - switch kind { - case reflect.String: - value = "" - case reflect.Int: - value = 0 - case reflect.Bool: - value = false - default: - return nil, &ErrFieldTypeUnknown{Field: field} - } - - filterValues = append(filterValues, FilterValues{ - { - Field: field, - Value: value, - Type: eval.ScalarValueType, - isScalar: false, - }, - }) - - continue - } - - var bitmasks []int - - var values FilterValues - for _, fValue := range fValues { - switch fValue.Type { - case eval.ScalarValueType, eval.PatternValueType: - notValue, err := eval.NotOfValue(fValue.Value) - if err != nil { - return nil, &ErrValueTypeUnknown{Field: field} - } - - values = values.Merge(FilterValue{ - Field: field, - Value: fValue.Value, - Type: fValue.Type, - notValue: notValue, - isScalar: true, - }) - - values = values.Merge(FilterValue{ - Field: field, - Value: notValue, - Type: fValue.Type, - notValue: fValue.Value, - not: true, - isScalar: true, - }) - case eval.BitmaskValueType: - bitmasks = append(bitmasks, fValue.Value.(int)) - } - } - - // add combinations of bitmask if bitmasks are used - if len(bitmasks) > 0 { - for _, mask := range combineBitmasks(bitmasks) { - notValue, err := eval.NotOfValue(mask) - if err != nil { - return nil, &ErrValueTypeUnknown{Field: field} - } - - values = values.Merge(FilterValue{ - Field: field, - Value: mask, - Type: eval.BitmaskValueType, - notValue: notValue, - isScalar: true, - }) - - values = values.Merge(FilterValue{ - Field: field, - Value: mask, - Type: eval.BitmaskValueType, - notValue: mask, - not: true, - isScalar: true, - }) - } - } - - filterValues = append(filterValues, values) - } - - return filterValues, nil -} - -func combineFilterValues(filterValues []FilterValues) []FilterValues { - combine := func(a []FilterValues, b FilterValues) []FilterValues { - var result []FilterValues - - for _, va := range a { - for _, vb := range b { - var s = make(FilterValues, len(va)) - copy(s, va) - result = append(result, append(s, vb)) - } - } - - return result - } - - var combined []FilterValues - for _, value := range filterValues[0] { - combined = append(combined, FilterValues{value}) - } - - for _, values := range filterValues[1:] { - combined = combine(combined, values) - } - - return combined -} - -func newTruthTable(rule *eval.Rule, event eval.Event) (*truthTable, error) { - ctx := eval.NewContext(event.GetPointer()) - - if len(rule.GetEvaluator().FieldValues) == 0 { - return nil, nil - } - - filterValues, err := genFilterValues(rule, event) - if err != nil { - return nil, err - } - - var truthTable truthTable - for _, combination := range combineFilterValues(filterValues) { - var entry truthEntry - - for _, filterValue := range combination { - if err = event.SetFieldValue(filterValue.Field, filterValue.Value); err != nil { - return nil, err - } - - entry.Values = append(entry.Values, FilterValue{ - Field: filterValue.Field, - Value: filterValue.Value, - Type: filterValue.Type, - notValue: filterValue.notValue, - not: filterValue.not, - isScalar: filterValue.isScalar, - }) - } - - entry.Result = rule.GetEvaluator().Eval(ctx) - - truthTable.Entries = append(truthTable.Entries, entry) - } - - return &truthTable, nil -}