Skip to content

Commit

Permalink
Reduce complexity of the approver discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
safchain committed Mar 31, 2022
1 parent fabb98b commit 699b202
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 440 deletions.
2 changes: 1 addition & 1 deletion pkg/security/secl/compiler/eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
6 changes: 6 additions & 0 deletions pkg/security/secl/compiler/eval/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
134 changes: 134 additions & 0 deletions pkg/security/secl/rules/approvers.go
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 0 additions & 83 deletions pkg/security/secl/rules/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
8 changes: 1 addition & 7 deletions pkg/security/secl/rules/filter_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@ 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
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
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/security/secl/rules/ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 699b202

Please sign in to comment.