Skip to content

Commit dafa3b7

Browse files
authored
[CWS] Allow using 'in' and 'not in' for comparisons between string arrays (#46847)
### What does this PR do? Allow using 'in' and 'not in' for comparisons between string arrays ### Motivation Allow writing rules such as connect.addr.hostname not in ${var_string_array} ### Describe how you validated your changes ### Additional Notes Co-authored-by: sylvain.baubeau <sylvain.baubeau@datadoghq.com>
1 parent 83d44cb commit dafa3b7

3 files changed

Lines changed: 104 additions & 3 deletions

File tree

pkg/security/secl/compiler/eval/eval.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -964,13 +964,18 @@ func nodeToEvaluator(obj interface{}, opts *Opts, state *State) (interface{}, le
964964
if err != nil {
965965
return nil, pos, err
966966
}
967-
if *obj.ArrayComparison.Op == "notin" {
968-
return Not(boolEvaluator, state), obj.Pos, nil
967+
case *StringArrayEvaluator:
968+
boolEvaluator, err = StringArrayMatchesStringArray(unary, nextStringArray, state)
969+
if err != nil {
970+
return nil, pos, err
969971
}
970-
return boolEvaluator, obj.Pos, nil
971972
default:
972973
return nil, pos, NewArrayTypeError(pos, reflect.Array, reflect.String)
973974
}
975+
if *obj.ArrayComparison.Op == "notin" {
976+
return Not(boolEvaluator, state), obj.Pos, nil
977+
}
978+
return boolEvaluator, obj.Pos, nil
974979
case *IntEvaluator:
975980
switch nextInt := next.(type) {
976981
case *IntArrayEvaluator:

pkg/security/secl/compiler/eval/eval_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,12 @@ func TestRegister(t *testing.T) {
943943
{Expr: `process.list[A].value not in [~"ZZ*", "AAA", "nnnnn"]`, Expected: true},
944944
{Expr: `process.list[A].value not in [~"ZZ*", ~"AA*", "nnnnn"]`, Expected: true},
945945

946+
// StringArrayEvaluator in/not in StringArrayEvaluator — previously unhandled, would fail at compile time
947+
{Expr: `process.list.value in process.array.value`, Expected: false}, // ["AAA","BBB","CCC"] ∩ ["EEEE","DDDD"] = ∅
948+
{Expr: `process.list.value not in process.array.value`, Expected: true},
949+
{Expr: `process.array.value in process.list.value`, Expected: false},
950+
{Expr: `process.array.value not in process.list.value`, Expected: true},
951+
946952
{Expr: `process.list[A].key == 10 && process.list[A].value == "AAA"`, Expected: true},
947953
{Expr: `process.list[A].key == 9999 && process.list[A].value == "AAA"`, Expected: false},
948954
{Expr: `process.list[A].key == 100 && process.list[A].value == "BBB"`, Expected: true},

pkg/security/secl/compiler/eval/operators.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,96 @@ func StringArrayMatches(a *StringArrayEvaluator, b *StringValuesEvaluator, state
780780
}, nil
781781
}
782782

783+
// StringArrayMatchesStringArray weak comparison, at least one element of a should be in b. Neither side can contain regexp
784+
func StringArrayMatchesStringArray(a *StringArrayEvaluator, b *StringArrayEvaluator, state *State) (*BoolEvaluator, error) {
785+
isDc := isArithmDeterministic(a, b, state)
786+
787+
if a.Field != "" {
788+
for _, value := range b.Values {
789+
if err := state.UpdateFieldValues(a.Field, FieldValue{Value: value}); err != nil {
790+
return nil, err
791+
}
792+
}
793+
}
794+
795+
arrayOp := func(a []string, b []string) (bool, string) {
796+
for _, va := range a {
797+
for _, vb := range b {
798+
if va == vb {
799+
return true, vb
800+
}
801+
}
802+
}
803+
return false, ""
804+
}
805+
806+
if a.EvalFnc != nil && b.EvalFnc != nil {
807+
ea, eb := a.EvalFnc, b.EvalFnc
808+
809+
evalFnc := func(ctx *Context) bool {
810+
va, vb := ea(ctx), eb(ctx)
811+
res, vm := arrayOp(va, vb)
812+
if res {
813+
ctx.AddMatchingSubExpr(MatchingValue{Field: a.Field, Value: va, Offset: a.Offset}, MatchingValue{Value: vm, Offset: b.Offset})
814+
}
815+
return res
816+
}
817+
818+
return &BoolEvaluator{
819+
EvalFnc: evalFnc,
820+
Weight: a.Weight + b.Weight,
821+
isDeterministic: isDc,
822+
}, nil
823+
}
824+
825+
if a.EvalFnc == nil && b.EvalFnc == nil {
826+
ea, eb := a.Values, b.Values
827+
res, _ := arrayOp(ea, eb)
828+
829+
return &BoolEvaluator{
830+
Value: res,
831+
Weight: a.Weight + InArrayWeight*len(eb),
832+
isDeterministic: isDc,
833+
}, nil
834+
}
835+
836+
if a.EvalFnc != nil {
837+
ea, eb := a.EvalFnc, b.Values
838+
839+
evalFnc := func(ctx *Context) bool {
840+
va, vb := ea(ctx), eb
841+
res, vm := arrayOp(va, vb)
842+
if res {
843+
ctx.AddMatchingSubExpr(MatchingValue{Field: a.Field, Value: va, Offset: a.Offset}, MatchingValue{Value: vm, Offset: b.Offset})
844+
}
845+
return res
846+
}
847+
848+
return &BoolEvaluator{
849+
EvalFnc: evalFnc,
850+
Weight: a.Weight + InArrayWeight*len(eb),
851+
isDeterministic: isDc,
852+
}, nil
853+
}
854+
855+
ea, eb := a.Values, b.EvalFnc
856+
857+
evalFnc := func(ctx *Context) bool {
858+
va, vb := ea, eb(ctx)
859+
res, vm := arrayOp(va, vb)
860+
if res {
861+
ctx.AddMatchingSubExpr(MatchingValue{Field: a.Field, Value: va, Offset: a.Offset}, MatchingValue{Field: b.Field, Value: vm, Offset: b.Offset})
862+
}
863+
return res
864+
}
865+
866+
return &BoolEvaluator{
867+
EvalFnc: evalFnc,
868+
Weight: b.Weight,
869+
isDeterministic: isDc,
870+
}, nil
871+
}
872+
783873
// IntArrayMatches weak comparison, a least one element of a should be in b
784874
func IntArrayMatches(a *IntArrayEvaluator, b *IntArrayEvaluator, state *State) (*BoolEvaluator, error) {
785875
isDc := isArithmDeterministic(a, b, state)

0 commit comments

Comments
 (0)