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 }