github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/rules/ruleset_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the Apache License Version 2.0.
     3  // This product includes software developed at Datadog (https://www.datadoghq.com/).
     4  // Copyright 2016-present Datadog, Inc.
     5  
     6  //go:build linux
     7  
     8  // Package rules holds rules related files
     9  package rules
    10  
    11  import (
    12  	"fmt"
    13  	"reflect"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  
    18  	"github.com/DataDog/datadog-agent/pkg/security/secl/compiler/ast"
    19  	"github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval"
    20  	"github.com/DataDog/datadog-agent/pkg/security/secl/model"
    21  )
    22  
    23  type testFieldValues map[string][]interface{}
    24  
    25  type testHandler struct {
    26  	filters map[string]testFieldValues
    27  }
    28  
    29  // SetRuleSetTag sets the value of the "ruleset" tag, which is the tag of the rules that belong in this rule set. This method is only used for testing.
    30  func (rs *RuleSet) setRuleSetTagValue(value eval.RuleSetTagValue) error {
    31  	if len(rs.GetRules()) > 0 {
    32  		return ErrCannotChangeTagAfterLoading
    33  	}
    34  	if _, ok := rs.opts.RuleSetTag[RuleSetTagKey]; !ok {
    35  		rs.opts.RuleSetTag = map[string]eval.RuleSetTagValue{RuleSetTagKey: ""}
    36  	}
    37  	rs.opts.RuleSetTag[RuleSetTagKey] = value
    38  
    39  	return nil
    40  }
    41  
    42  func (f *testHandler) RuleMatch(_ *Rule, _ eval.Event) bool {
    43  	return true
    44  }
    45  
    46  func (f *testHandler) EventDiscarderFound(_ *RuleSet, event eval.Event, field string, _ eval.EventType) {
    47  	values, ok := f.filters[event.GetType()]
    48  	if !ok {
    49  		values = make(testFieldValues)
    50  		f.filters[event.GetType()] = values
    51  	}
    52  
    53  	discarders, ok := values[field]
    54  	if !ok {
    55  		discarders = []interface{}{}
    56  	}
    57  
    58  	var m model.Model
    59  	evaluator, _ := m.GetEvaluator(field, "")
    60  
    61  	ctx := eval.NewContext(event)
    62  
    63  	value := evaluator.Eval(ctx)
    64  
    65  	found := false
    66  	for _, d := range discarders {
    67  		if d == value {
    68  			found = true
    69  		}
    70  	}
    71  
    72  	if !found {
    73  		discarders = append(discarders, evaluator.Eval(ctx))
    74  	}
    75  	values[field] = discarders
    76  }
    77  
    78  func addRuleExpr(t *testing.T, rs *RuleSet, exprs ...string) {
    79  	var ruleDefs []*RuleDefinition
    80  
    81  	for i, expr := range exprs {
    82  		ruleDef := &RuleDefinition{
    83  			ID:         fmt.Sprintf("ID%d", i),
    84  			Expression: expr,
    85  			Tags:       make(map[string]string),
    86  		}
    87  		ruleDefs = append(ruleDefs, ruleDef)
    88  	}
    89  
    90  	pc := ast.NewParsingContext()
    91  
    92  	if err := rs.AddRules(pc, ruleDefs); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  }
    96  
    97  func newFakeEvent() eval.Event {
    98  	return model.NewFakeEvent()
    99  }
   100  
   101  func newRuleSet() *RuleSet {
   102  	ruleOpts, evalOpts := NewBothOpts(map[eval.EventType]bool{"*": true})
   103  	return NewRuleSet(&model.Model{}, newFakeEvent, ruleOpts, evalOpts)
   104  }
   105  
   106  func TestRuleBuckets(t *testing.T) {
   107  	exprs := []string{
   108  		`(open.filename =~ "/sbin/*" || open.filename =~ "/usr/sbin/*") && process.uid != 0 && open.flags & O_CREAT > 0`,
   109  		`(mkdir.filename =~ "/sbin/*" || mkdir.filename =~ "/usr/sbin/*") && process.uid != 0`,
   110  	}
   111  
   112  	rs := newRuleSet()
   113  	addRuleExpr(t, rs, exprs...)
   114  
   115  	if bucket, ok := rs.eventRuleBuckets["open"]; !ok || len(bucket.rules) != 1 {
   116  		t.Fatal("unable to find `open` rules or incorrect number of rules")
   117  	}
   118  	if bucket, ok := rs.eventRuleBuckets["mkdir"]; !ok || len(bucket.rules) != 1 {
   119  		t.Fatal("unable to find `mkdir` rules or incorrect number of rules")
   120  	}
   121  	for _, bucket := range rs.eventRuleBuckets {
   122  		for _, rule := range bucket.rules {
   123  			if rule.GetPartialEval("process.uid") == nil {
   124  				t.Fatal("failed to initialize partials")
   125  			}
   126  		}
   127  	}
   128  }
   129  
   130  func TestRuleSetDiscarders(t *testing.T) {
   131  	handler := &testHandler{
   132  		filters: make(map[string]testFieldValues),
   133  	}
   134  
   135  	rs := newRuleSet()
   136  	rs.AddListener(handler)
   137  
   138  	exprs := []string{
   139  		`open.file.path == "/etc/passwd" && process.uid != 0`,
   140  		`(open.file.path =~ "/sbin/*" || open.file.path =~ "/usr/sbin/*") && process.uid != 0 && open.flags & O_CREAT > 0`,
   141  		`(open.file.path =~ "/var/run/*") && open.flags & O_CREAT > 0 && process.uid != 0`,
   142  		`(mkdir.file.path =~ "/var/run/*") && process.uid != 0`,
   143  	}
   144  
   145  	addRuleExpr(t, rs, exprs...)
   146  
   147  	ev1 := model.NewFakeEvent()
   148  	ev1.Type = uint32(model.FileOpenEventType)
   149  	ev1.SetFieldValue("open.file.path", "/usr/local/bin/rootkit")
   150  	ev1.SetFieldValue("open.flags", syscall.O_RDONLY)
   151  	ev1.SetFieldValue("process.uid", 0)
   152  
   153  	ev2 := model.NewFakeEvent()
   154  	ev2.Type = uint32(model.FileMkdirEventType)
   155  	ev2.SetFieldValue("mkdir.file.path", "/usr/local/bin/rootkit")
   156  	ev2.SetFieldValue("mkdir.mode", 0777)
   157  	ev2.SetFieldValue("process.uid", 0)
   158  
   159  	if !rs.Evaluate(ev1) {
   160  		rs.EvaluateDiscarders(ev1)
   161  	}
   162  	if !rs.Evaluate(ev2) {
   163  		rs.EvaluateDiscarders(ev2)
   164  	}
   165  
   166  	expected := map[string]testFieldValues{
   167  		"open": {
   168  			"open.file.path": []interface{}{
   169  				"/usr/local/bin/rootkit",
   170  			},
   171  			"process.uid": []interface{}{
   172  				0,
   173  			},
   174  		},
   175  		"mkdir": {
   176  			"mkdir.file.path": []interface{}{
   177  				"/usr/local/bin/rootkit",
   178  			},
   179  			"process.uid": []interface{}{
   180  				0,
   181  			},
   182  		},
   183  	}
   184  
   185  	if !reflect.DeepEqual(expected, handler.filters) {
   186  		t.Fatalf("unable to find expected discarders, expected: `%v`, got: `%v`", expected, handler.filters)
   187  	}
   188  }
   189  
   190  func TestRuleSetApprovers1(t *testing.T) {
   191  	rs := newRuleSet()
   192  	addRuleExpr(t, rs, `open.file.path in ["/etc/passwd", "/etc/shadow"] && (process.uid == 0 && process.gid == 0)`)
   193  
   194  	caps := FieldCapabilities{
   195  		{
   196  			Field:        "process.uid",
   197  			Types:        eval.ScalarValueType,
   198  			FilterWeight: 1,
   199  		},
   200  		{
   201  			Field:        "process.gid",
   202  			Types:        eval.ScalarValueType,
   203  			FilterWeight: 2,
   204  		},
   205  	}
   206  
   207  	approvers, _ := rs.GetEventApprovers("open", caps)
   208  	if len(approvers) == 0 {
   209  		t.Fatal("should get an approver")
   210  	}
   211  
   212  	if values, exists := approvers["process.gid"]; !exists || len(values) != 1 {
   213  		t.Fatal("expected approver not found")
   214  	}
   215  
   216  	if _, exists := approvers["process.uid"]; exists {
   217  		t.Fatal("unexpected approver found")
   218  	}
   219  
   220  	if _, exists := approvers["open.file.path"]; exists {
   221  		t.Fatal("unexpected approver found")
   222  	}
   223  
   224  	caps = FieldCapabilities{
   225  		{
   226  			Field: "open.file.path",
   227  			Types: eval.ScalarValueType,
   228  		},
   229  	}
   230  
   231  	approvers, _ = rs.GetEventApprovers("open", caps)
   232  	if len(approvers) == 0 {
   233  		t.Fatal("should get an approver")
   234  	}
   235  
   236  	if values, exists := approvers["open.file.path"]; !exists || len(values) != 2 {
   237  		t.Fatal("expected approver not found")
   238  	}
   239  }
   240  
   241  func TestRuleSetApprovers2(t *testing.T) {
   242  	exprs := []string{
   243  		`open.file.path in ["/etc/passwd", "/etc/shadow"] && process.uid == 0`,
   244  		`open.flags & O_CREAT > 0 && process.uid == 0`,
   245  	}
   246  
   247  	rs := newRuleSet()
   248  	addRuleExpr(t, rs, exprs...)
   249  
   250  	caps := FieldCapabilities{
   251  		{
   252  			Field: "open.file.path",
   253  			Types: eval.ScalarValueType,
   254  		},
   255  	}
   256  
   257  	approvers, _ := rs.GetEventApprovers("open", caps)
   258  	if len(approvers) != 0 {
   259  		t.Fatal("shouldn't get any approver")
   260  	}
   261  
   262  	caps = FieldCapabilities{
   263  		{
   264  			Field:        "open.file.path",
   265  			Types:        eval.ScalarValueType,
   266  			FilterWeight: 3,
   267  		},
   268  		{
   269  			Field:        "process.uid",
   270  			Types:        eval.ScalarValueType,
   271  			FilterWeight: 2,
   272  		},
   273  	}
   274  
   275  	approvers, _ = rs.GetEventApprovers("open", caps)
   276  	if len(approvers) != 2 {
   277  		t.Fatal("should get 2 field approvers")
   278  	}
   279  
   280  	if values, exists := approvers["open.file.path"]; !exists || len(values) != 2 {
   281  		t.Fatalf("expected approver not found: %+v", values)
   282  	}
   283  
   284  	if _, exists := approvers["process.uid"]; !exists {
   285  		t.Fatal("expected approver not found")
   286  	}
   287  }
   288  
   289  func TestRuleSetApprovers3(t *testing.T) {
   290  	rs := newRuleSet()
   291  	addRuleExpr(t, rs, `open.file.path in ["/etc/passwd", "/etc/shadow"] && (process.uid == process.gid)`)
   292  
   293  	caps := FieldCapabilities{
   294  		{
   295  			Field: "open.file.path",
   296  			Types: eval.ScalarValueType,
   297  		},
   298  	}
   299  
   300  	approvers, _ := rs.GetEventApprovers("open", caps)
   301  	if len(approvers) != 1 {
   302  		t.Fatal("should get only one field approver")
   303  	}
   304  
   305  	if values, exists := approvers["open.file.path"]; !exists || len(values) != 2 {
   306  		t.Fatal("expected approver not found")
   307  	}
   308  }
   309  
   310  func TestRuleSetApprovers4(t *testing.T) {
   311  	rs := newRuleSet()
   312  	addRuleExpr(t, rs, `open.file.path =~ "/etc/passwd" && process.uid == 0`)
   313  
   314  	caps := FieldCapabilities{
   315  		{
   316  			Field: "open.file.path",
   317  			Types: eval.ScalarValueType,
   318  		},
   319  	}
   320  
   321  	if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) != 0 {
   322  		t.Fatalf("shouldn't get any approver, got: %+v", approvers)
   323  	}
   324  
   325  	caps = FieldCapabilities{
   326  		{
   327  			Field: "open.file.path",
   328  			Types: eval.ScalarValueType | eval.GlobValueType,
   329  		},
   330  	}
   331  
   332  	if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) == 0 {
   333  		t.Fatal("expected approver not found")
   334  	}
   335  }
   336  
   337  func TestRuleSetApprovers5(t *testing.T) {
   338  	rs := newRuleSet()
   339  	addRuleExpr(t, rs, `(open.flags & O_CREAT > 0 || open.flags & O_EXCL > 0) && open.flags & O_RDWR > 0`)
   340  
   341  	caps := FieldCapabilities{
   342  		{
   343  			Field: "open.flags",
   344  			Types: eval.ScalarValueType | eval.BitmaskValueType,
   345  		},
   346  	}
   347  
   348  	approvers, _ := rs.GetEventApprovers("open", caps)
   349  	if len(approvers) == 0 {
   350  		t.Fatal("expected approver not found")
   351  	}
   352  
   353  	for _, value := range approvers["open.flags"] {
   354  		if value.Value.(int)&syscall.O_RDWR == 0 {
   355  			t.Fatal("expected approver not found")
   356  		}
   357  	}
   358  }
   359  
   360  func TestRuleSetApprovers6(t *testing.T) {
   361  	rs := newRuleSet()
   362  	addRuleExpr(t, rs, `open.file.name == "123456"`)
   363  
   364  	caps := FieldCapabilities{
   365  		{
   366  			Field: "open.file.name",
   367  			Types: eval.ScalarValueType,
   368  			ValidateFnc: func(value FilterValue) bool {
   369  				return strings.HasSuffix(value.Value.(string), "456")
   370  			},
   371  		},
   372  	}
   373  
   374  	if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) == 0 {
   375  		t.Fatal("expected approver not found")
   376  	}
   377  
   378  	caps = FieldCapabilities{
   379  		{
   380  			Field: "open.file.name",
   381  			Types: eval.ScalarValueType,
   382  			ValidateFnc: func(value FilterValue) bool {
   383  				return strings.HasSuffix(value.Value.(string), "777")
   384  			},
   385  		},
   386  	}
   387  
   388  	if approvers, _ := rs.GetEventApprovers("open", caps); len(approvers) > 0 {
   389  		t.Fatal("shouldn't get any approver")
   390  	}
   391  }
   392  
   393  func TestRuleSetApprovers7(t *testing.T) {
   394  	rs := newRuleSet()
   395  	addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT`)
   396  
   397  	caps := FieldCapabilities{
   398  		{
   399  			Field: "open.flags",
   400  			Types: eval.ScalarValueType | eval.BitmaskValueType,
   401  		},
   402  	}
   403  
   404  	approvers, _ := rs.GetEventApprovers("open", caps)
   405  	if len(approvers) == 0 {
   406  		t.Fatal("expected approver not found")
   407  	}
   408  
   409  	if len(approvers["open.flags"]) != 1 || approvers["open.flags"][0].Value.(int)&syscall.O_CREAT == 0 {
   410  		t.Fatal("expected approver not found")
   411  	}
   412  }
   413  
   414  func TestRuleSetApprovers8(t *testing.T) {
   415  	rs := newRuleSet()
   416  	addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT && open.file.path in ["/etc/passwd", "/etc/shadow"]`)
   417  
   418  	caps := FieldCapabilities{
   419  		{
   420  			Field: "open.flags",
   421  			Types: eval.ScalarValueType,
   422  		},
   423  		{
   424  			Field:        "open.file.path",
   425  			Types:        eval.ScalarValueType,
   426  			FilterWeight: 3,
   427  		},
   428  	}
   429  
   430  	approvers, _ := rs.GetEventApprovers("open", caps)
   431  	if len(approvers) == 0 {
   432  		t.Fatal("expected approver not found")
   433  	}
   434  
   435  	if values, exists := approvers["open.file.path"]; !exists || len(values) != 2 {
   436  		t.Fatal("expected approver not found")
   437  	}
   438  
   439  	if _, exists := approvers["open.flags"]; exists {
   440  		t.Fatal("shouldn't get an approver for `open.flags`")
   441  	}
   442  }
   443  
   444  func TestRuleSetApprovers9(t *testing.T) {
   445  	rs := newRuleSet()
   446  	addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_CREAT && open.file.path not in ["/etc/passwd", "/etc/shadow"]`)
   447  
   448  	caps := FieldCapabilities{
   449  		{
   450  			Field: "open.flags",
   451  			Types: eval.ScalarValueType,
   452  		},
   453  		{
   454  			Field:        "open.file.path",
   455  			Types:        eval.ScalarValueType,
   456  			FilterWeight: 3,
   457  		},
   458  	}
   459  
   460  	approvers, _ := rs.GetEventApprovers("open", caps)
   461  	if len(approvers) == 0 {
   462  		t.Fatal("expected approver not found")
   463  	}
   464  
   465  	if _, exists := approvers["open.file.path"]; exists {
   466  		t.Fatal("shouldn't get an approver for `open.file.path`")
   467  	}
   468  
   469  	if _, exists := approvers["open.flags"]; !exists {
   470  		t.Fatal("expected approver not found")
   471  	}
   472  }
   473  
   474  func TestRuleSetApprovers10(t *testing.T) {
   475  	rs := newRuleSet()
   476  	addRuleExpr(t, rs, `open.file.path in [~"/etc/passwd", "/etc/shadow"]`)
   477  
   478  	caps := FieldCapabilities{
   479  		{
   480  			Field:        "open.file.path",
   481  			Types:        eval.ScalarValueType,
   482  			FilterWeight: 3,
   483  		},
   484  	}
   485  
   486  	approvers, _ := rs.GetEventApprovers("open", caps)
   487  	if len(approvers) != 0 {
   488  		t.Fatal("shouldn't get an approver for `open.file.path`")
   489  	}
   490  }
   491  
   492  func TestRuleSetApprovers11(t *testing.T) {
   493  	rs := newRuleSet()
   494  	addRuleExpr(t, rs, `open.file.path in [~"/etc/passwd", "/etc/shadow"]`)
   495  
   496  	caps := FieldCapabilities{
   497  		{
   498  			Field:        "open.file.path",
   499  			Types:        eval.ScalarValueType | eval.GlobValueType,
   500  			FilterWeight: 3,
   501  		},
   502  	}
   503  
   504  	approvers, _ := rs.GetEventApprovers("open", caps)
   505  	if len(approvers) == 0 {
   506  		t.Fatal("expected approver not found")
   507  	}
   508  }
   509  
   510  func TestRuleSetApprovers12(t *testing.T) {
   511  	exprs := []string{
   512  		`open.file.path in ["/etc/passwd", "/etc/shadow"]`,
   513  		`open.file.path in [~"/etc/httpd", "/etc/nginx"]`,
   514  	}
   515  
   516  	rs := newRuleSet()
   517  	addRuleExpr(t, rs, exprs...)
   518  
   519  	caps := FieldCapabilities{
   520  		{
   521  			Field:        "open.file.path",
   522  			Types:        eval.ScalarValueType,
   523  			FilterWeight: 3,
   524  		},
   525  	}
   526  
   527  	approvers, _ := rs.GetEventApprovers("open", caps)
   528  	if len(approvers) != 0 {
   529  		t.Fatal("shouldn't get an approver for `open.file.path`")
   530  	}
   531  }
   532  
   533  func TestRuleSetApprovers13(t *testing.T) {
   534  	rs := newRuleSet()
   535  	addRuleExpr(t, rs, `open.flags & (O_CREAT | O_EXCL) == O_RDWR`)
   536  
   537  	caps := FieldCapabilities{
   538  		{
   539  			Field: "open.flags",
   540  			Types: eval.ScalarValueType | eval.BitmaskValueType,
   541  		},
   542  	}
   543  
   544  	approvers, _ := rs.GetEventApprovers("open", caps)
   545  	if len(approvers) != 0 {
   546  		t.Fatal("shouldn't get an approver for `open.file.flags`")
   547  	}
   548  }
   549  
   550  func TestRuleSetApprovers14(t *testing.T) {
   551  	exprs := []string{
   552  		`open.file.path == "/etc/passwd"`,
   553  		`open.file.path =~ "/etc/*/httpd"`,
   554  	}
   555  
   556  	rs := newRuleSet()
   557  	addRuleExpr(t, rs, exprs...)
   558  
   559  	caps := FieldCapabilities{
   560  		{
   561  			Field:        "open.file.path",
   562  			Types:        eval.ScalarValueType | eval.GlobValueType,
   563  			FilterWeight: 3,
   564  		},
   565  	}
   566  
   567  	approvers, _ := rs.GetEventApprovers("open", caps)
   568  	if len(approvers) != 1 || len(approvers["open.file.path"]) != 2 {
   569  		t.Fatalf("shouldn't get an approver for `open.file.path`: %v", approvers)
   570  	}
   571  }
   572  
   573  func TestGetRuleEventType(t *testing.T) {
   574  	rule := eval.NewRule("aaa", `open.file.name == "test"`, &eval.Opts{})
   575  
   576  	pc := ast.NewParsingContext()
   577  
   578  	if err := rule.GenEvaluator(&model.Model{}, pc); err != nil {
   579  		t.Fatal(err)
   580  	}
   581  
   582  	eventType, err := GetRuleEventType(rule)
   583  	if err != nil {
   584  		t.Fatalf("should get an event type: %s", err)
   585  	}
   586  
   587  	event := model.NewFakeEvent()
   588  	fieldEventType, err := event.GetFieldEventType("open.file.name")
   589  	if err != nil {
   590  		t.Fatal("should get a field event type")
   591  	}
   592  
   593  	if eventType != fieldEventType {
   594  		t.Fatal("unexpected event type")
   595  	}
   596  }