From 903dddbefd163f4929c4e76a7fdfd22686bd15bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Wed, 10 Jul 2024 18:04:57 -0300 Subject: [PATCH] chore: inject deps manager into policy manager It makes the access to event flags be done through the policy manager, which is responsible for setting up the dependencies manager. This also: - Turn the event flags access thread-safe. - Renames manager and config types. --- pkg/ebpf/events_pipeline.go | 6 +- pkg/ebpf/hidden_kernel_module.go | 2 +- pkg/ebpf/hooked_syscall_table.go | 2 +- pkg/ebpf/ksymbols.go | 2 +- pkg/ebpf/processor_funcs.go | 10 +- pkg/ebpf/signature_engine.go | 5 +- pkg/ebpf/tracee.go | 327 +++--------- pkg/events/definition_group.go | 12 +- pkg/events/derive/symbols_collision.go | 2 +- pkg/events/derive/symbols_collision_test.go | 11 +- pkg/events/derive/symbols_loaded.go | 2 +- pkg/policy/ebpf.go | 10 +- pkg/policy/event_flags.go | 10 + pkg/policy/policy_manager.go | 518 +++++++++++++++++--- pkg/policy/policy_manager_test.go | 77 ++- tests/integration/dependencies_test.go | 8 +- 16 files changed, 622 insertions(+), 382 deletions(-) diff --git a/pkg/ebpf/events_pipeline.go b/pkg/ebpf/events_pipeline.go index 7af714af3c3e..484b8366c99b 100644 --- a/pkg/ebpf/events_pipeline.go +++ b/pkg/ebpf/events_pipeline.go @@ -268,9 +268,9 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch // thus the need to continue with those within the pipeline. if t.matchPolicies(evt) == 0 { _, hasDerivation := t.eventDerivations[eventId] - _, hasSignature := t.eventSignatures[eventId] + isSignature := t.policyManager.IsSignature(eventId) - if !hasDerivation && !hasSignature { + if !hasDerivation && !isSignature { _ = t.stats.EventsFiltered.Increment() t.eventsPool.Put(evt) continue @@ -598,7 +598,7 @@ func (t *Tracee) sinkEvents(ctx context.Context, in <-chan *trace.Event) <-chan // Only emit events requested by the user and matched by at least one policy. id := events.ID(event.EventID) - event.MatchedPoliciesUser &= t.eventsState[id].Emit + event.MatchedPoliciesUser = t.policyManager.MatchEvent(id, event.MatchedPoliciesUser) if event.MatchedPoliciesUser == 0 { t.eventsPool.Put(event) continue diff --git a/pkg/ebpf/hidden_kernel_module.go b/pkg/ebpf/hidden_kernel_module.go index 47b28147c8b3..15a62fcfe0d7 100644 --- a/pkg/ebpf/hidden_kernel_module.go +++ b/pkg/ebpf/hidden_kernel_module.go @@ -28,7 +28,7 @@ func (t *Tracee) lkmSeekerRoutine(ctx gocontext.Context) { logger.Debugw("Starting lkmSeekerRoutine goroutine") defer logger.Debugw("Stopped lkmSeekerRoutine goroutine") - if t.eventsState[events.HiddenKernelModule].Emit == 0 { + if !t.policyManager.IsEventToEmit(events.HiddenKernelModule) { return } diff --git a/pkg/ebpf/hooked_syscall_table.go b/pkg/ebpf/hooked_syscall_table.go index 4ef19f503f92..226e1efadb10 100644 --- a/pkg/ebpf/hooked_syscall_table.go +++ b/pkg/ebpf/hooked_syscall_table.go @@ -25,7 +25,7 @@ func (t *Tracee) hookedSyscallTableRoutine(ctx gocontext.Context) { logger.Debugw("Starting hookedSyscallTable goroutine") defer logger.Debugw("Stopped hookedSyscallTable goroutine") - if t.eventsState[events.HookedSyscall].Submit == 0 { + if !t.policyManager.IsEventToSubmit(events.HookedSyscall) { return } diff --git a/pkg/ebpf/ksymbols.go b/pkg/ebpf/ksymbols.go index 8b7738e4a3b3..64fa239a696a 100644 --- a/pkg/ebpf/ksymbols.go +++ b/pkg/ebpf/ksymbols.go @@ -36,7 +36,7 @@ func (t *Tracee) UpdateKallsyms() error { var allReqSymbols []string - for evtID := range t.eventsState { + for _, evtID := range t.policyManager.EventsSelected() { for _, symDep := range evtDefSymDeps(evtID) { allReqSymbols = append(allReqSymbols, symDep.GetSymbolName()) } diff --git a/pkg/ebpf/processor_funcs.go b/pkg/ebpf/processor_funcs.go index 68dfad204af9..733d5f67401b 100644 --- a/pkg/ebpf/processor_funcs.go +++ b/pkg/ebpf/processor_funcs.go @@ -214,11 +214,11 @@ func (t *Tracee) processSchedProcessExec(event *trace.Event) error { // processDoFinitModule handles a do_finit_module event and triggers other hooking detection logic. func (t *Tracee) processDoInitModule(event *trace.Event) error { // Check if related events are being traced. - _, okSyscalls := t.eventsState[events.HookedSyscall] - _, okSeqOps := t.eventsState[events.HookedSeqOps] - _, okProcFops := t.eventsState[events.HookedProcFops] - _, okMemDump := t.eventsState[events.PrintMemDump] - _, okFtrace := t.eventsState[events.FtraceHook] + okSyscalls := t.policyManager.IsEventSelected(events.HookedSyscall) + okSeqOps := t.policyManager.IsEventSelected(events.HookedSeqOps) + okProcFops := t.policyManager.IsEventSelected(events.HookedProcFops) + okMemDump := t.policyManager.IsEventSelected(events.PrintMemDump) + okFtrace := t.policyManager.IsEventSelected(events.FtraceHook) if !okSyscalls && !okSeqOps && !okProcFops && !okMemDump && !okFtrace { return nil diff --git a/pkg/ebpf/signature_engine.go b/pkg/ebpf/signature_engine.go index 0b783db5fc6f..b1b74c92ae4f 100644 --- a/pkg/ebpf/signature_engine.go +++ b/pkg/ebpf/signature_engine.go @@ -29,8 +29,7 @@ func (t *Tracee) engineEvents(ctx context.Context, in <-chan *trace.Event) (<-ch // Share event states (by reference) t.config.EngineConfig.ShouldDispatchEvent = func(eventIdInt32 int32) bool { - _, ok := t.eventsState[events.ID(eventIdInt32)] - return ok + return t.policyManager.IsEventSelected(events.ID(eventIdInt32)) } sigEngine, err := engine.NewEngine(t.config.EngineConfig, source, engineOutput) @@ -62,7 +61,7 @@ func (t *Tracee) engineEvents(ctx context.Context, in <-chan *trace.Event) (<-ch id := events.ID(event.EventID) // if the event is marked as submit, we pass it to the engine - if t.eventsState[id].Submit > 0 { + if t.policyManager.IsEventToSubmit(id) { err := t.parseArguments(event) if err != nil { t.handleError(err) diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 91cd8dad83e2..1e5ed33a2b5a 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -65,15 +65,12 @@ type Tracee struct { OutDir *os.File // use utils.XXX functions to create or write to this file stats metrics.Stats sigEngine *engine.Engine - // Events States - eventsState map[events.ID]events.EventState // Events eventsSorter *sorting.EventsChronologicalSorter eventsPool *sync.Pool eventsParamTypes map[events.ID][]bufferdecoder.ArgType eventProcessor map[events.ID][]func(evt *trace.Event) error eventDerivations derive.Table - eventSignatures map[events.ID]bool // Artifacts fileHashes *filehash.Cache capturedFiles map[string]int64 @@ -121,7 +118,7 @@ type Tracee struct { // Streams streamsManager *streams.StreamsManager // policyManager manages policy state - policyManager *policy.PolicyManager + policyManager *policy.Manager // The dependencies of events used by Tracee eventsDependencies *dependencies.Manager // Ksymbols needed to be kept alive in table. @@ -140,94 +137,37 @@ func (t *Tracee) Engine() *engine.Engine { return t.sigEngine } -// GetCaptureEventsList sets events used to capture data. -func GetCaptureEventsList(cfg config.Config) map[events.ID]events.EventState { - captureEvents := make(map[events.ID]events.EventState) - - // INFO: All capture events should be placed, at least for now, to all matched policies, or else - // the event won't be set to matched policy in eBPF and should_submit() won't submit the capture - // event to userland. - - if cfg.Capture.Exec { - captureEvents[events.CaptureExec] = policy.AlwaysSubmit - } - if cfg.Capture.FileWrite.Capture { - captureEvents[events.CaptureFileWrite] = policy.AlwaysSubmit - } - if cfg.Capture.FileRead.Capture { - captureEvents[events.CaptureFileRead] = policy.AlwaysSubmit - } - if cfg.Capture.Module { - captureEvents[events.CaptureModule] = policy.AlwaysSubmit - } - if cfg.Capture.Mem { - captureEvents[events.CaptureMem] = policy.AlwaysSubmit - } - if cfg.Capture.Bpf { - captureEvents[events.CaptureBpf] = policy.AlwaysSubmit - } - if pcaps.PcapsEnabled(cfg.Capture.Net) { - captureEvents[events.CaptureNetPacket] = policy.AlwaysSubmit +// New creates a new Tracee instance based on a given valid Config. It is expected that it won't +// cause external system side effects (reads, writes, etc). +func New(cfg config.Config) (*Tracee, error) { + err := cfg.Validate() + if err != nil { + return nil, errfmt.Errorf("validation error: %v", err) } - return captureEvents -} - -func (t *Tracee) addEventState(eventID events.ID, chosenState events.EventState) { - currentState := t.eventsState[eventID] - currentState.Submit |= chosenState.Submit - currentState.Emit |= chosenState.Emit - t.eventsState[eventID] = currentState -} + // Initialize capabilities rings soon -func (t *Tracee) addDependenciesToStateRecursive(eventNode *dependencies.EventNode) { - eventID := eventNode.GetID() - for _, dependencyEventID := range eventNode.GetDependencies().GetIDs() { - t.addDependencyEventToState(dependencyEventID, []events.ID{eventID}) - dependencyNode, err := t.eventsDependencies.GetEvent(dependencyEventID) - if err == nil { - t.addDependenciesToStateRecursive(dependencyNode) - } + useBaseEbpf := func(cfg config.Config) bool { + return cfg.Output.StackAddresses } -} -func (t *Tracee) selectEvent(eventID events.ID, chosenState events.EventState) { - t.addEventState(eventID, chosenState) - eventNode, err := t.eventsDependencies.SelectEvent(eventID) + err = capabilities.Initialize( + capabilities.Config{ + Bypass: cfg.Capabilities.BypassCaps, + BaseEbpf: useBaseEbpf(cfg), + }, + ) if err != nil { - logger.Errorw("Event selection failed", - "event", events.Core.GetDefinitionByID(eventID).GetName()) - return - } - t.addDependenciesToStateRecursive(eventNode) -} - -// addDependencyEventToState adds to tracee's state an event that is a dependency of other events. -// The difference from chosen events is that it doesn't affect its eviction. -func (t *Tracee) addDependencyEventToState(evtID events.ID, dependentEvts []events.ID) { - newState := events.EventState{} - for _, dependentEvent := range dependentEvts { - newState.Submit |= t.eventsState[dependentEvent].Submit + return nil, errfmt.WrapError(err) } - t.addEventState(evtID, newState) - if events.Core.GetDefinitionByID(evtID).IsSignature() { - t.eventSignatures[evtID] = true - } -} + caps := capabilities.GetInstance() -func (t *Tracee) removeEventFromState(evtID events.ID) { - logger.Debugw("Remove event from state", "event", events.Core.GetDefinitionByID(evtID).GetName()) - delete(t.eventsState, evtID) - delete(t.eventSignatures, evtID) -} + // Initialize Dependencies Manager -// New creates a new Tracee instance based on a given valid Config. It is expected that it won't -// cause external system side effects (reads, writes, etc). -func New(cfg config.Config) (*Tracee, error) { - err := cfg.Validate() - if err != nil { - return nil, errfmt.Errorf("validation error: %v", err) - } + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) // Initialize Policy Manager @@ -240,8 +180,8 @@ func New(cfg config.Config) (*Tracee, error) { initialPolicies = append(initialPolicies, p) } - // NOTE: This deep copy ensures that the inner slices are not shared between - // the original and the injected config. + // NOTE: This deep copy is a *reminder* that the inner slices are not shared + // between the original and the injected config. // TODO: This logic should be removed when config changes in runtime is a thing. getNewCaptureConfig := func() config.CaptureConfig { fileReadPathFilter := make([]string, 0, len(cfg.Capture.FileRead.PathFilter)) @@ -276,165 +216,29 @@ func New(cfg config.Config) (*Tracee, error) { ProcTreeConfig: cfg.ProcTree, CaptureConfig: getNewCaptureConfig(), } - pm := policy.NewPolicyManager(pmCfg, initialPolicies...) + pm, err := policy.NewManager(pmCfg, depsManager, initialPolicies...) + if err != nil { + return nil, errfmt.WrapError(err) + } // Create Tracee t := &Tracee{ - config: cfg, - done: make(chan struct{}), - writtenFiles: make(map[string]string), - readFiles: make(map[string]string), - capturedFiles: make(map[string]int64), - eventsState: make(map[events.ID]events.EventState), - eventSignatures: make(map[events.ID]bool), - streamsManager: streams.NewStreamsManager(), - policyManager: pm, - eventsDependencies: dependencies.NewDependenciesManager( - func(id events.ID) events.Dependencies { - return events.Core.GetDefinitionByID(id).GetDependencies() - }), - requiredKsyms: []string{}, + config: cfg, + done: make(chan struct{}), + writtenFiles: make(map[string]string), + readFiles: make(map[string]string), + capturedFiles: make(map[string]int64), + streamsManager: streams.NewStreamsManager(), + policyManager: pm, + eventsDependencies: depsManager, + requiredKsyms: []string{}, } // clear initial policies to avoid wrong references initialPolicies = nil t.config.InitialPolicies = nil - // TODO: As dynamic event addition or removal becomes a thing, we should subscribe all the watchers - // before selecting them. There is no reason to select the event in the New function anyhow. - t.eventsDependencies.SubscribeAdd( - dependencies.EventNodeType, - func(node interface{}) []dependencies.Action { - eventNode, ok := node.(*dependencies.EventNode) - if !ok { - logger.Errorw("Got node from type not requested") - return nil - } - t.addDependencyEventToState(eventNode.GetID(), eventNode.GetDependents()) - return nil - }) - t.eventsDependencies.SubscribeRemove( - dependencies.EventNodeType, - func(node interface{}) []dependencies.Action { - eventNode, ok := node.(*dependencies.EventNode) - if !ok { - logger.Errorw("Got node from type not requested") - return nil - } - t.removeEventFromState(eventNode.GetID()) - return nil - }) - - // Initialize capabilities rings soon - - useBaseEbpf := func(cfg config.Config) bool { - return cfg.Output.StackAddresses - } - - err = capabilities.Initialize( - capabilities.Config{ - Bypass: t.config.Capabilities.BypassCaps, - BaseEbpf: useBaseEbpf(t.config), - }, - ) - if err != nil { - return t, errfmt.WrapError(err) - } - caps := capabilities.GetInstance() - - // Initialize events state with mandatory events (TODO: review this need for sched exec) - - t.selectEvent(events.SchedProcessFork, events.EventState{}) - t.selectEvent(events.SchedProcessExec, events.EventState{}) - t.selectEvent(events.SchedProcessExit, events.EventState{}) - - // Control Plane Events - - t.selectEvent(events.SignalCgroupMkdir, policy.AlwaysSubmit) - t.selectEvent(events.SignalCgroupRmdir, policy.AlwaysSubmit) - - // Control Plane Process Tree Events - - pipeEvts := func() { - t.selectEvent(events.SchedProcessFork, policy.AlwaysSubmit) - t.selectEvent(events.SchedProcessExec, policy.AlwaysSubmit) - t.selectEvent(events.SchedProcessExit, policy.AlwaysSubmit) - } - signalEvts := func() { - t.selectEvent(events.SignalSchedProcessFork, policy.AlwaysSubmit) - t.selectEvent(events.SignalSchedProcessExec, policy.AlwaysSubmit) - t.selectEvent(events.SignalSchedProcessExit, policy.AlwaysSubmit) - } - - // DNS Cache events - - if t.config.DNSCacheConfig.Enable { - t.selectEvent(events.NetPacketDNS, policy.AlwaysSubmit) - } - - switch t.config.ProcTree.Source { - case proctree.SourceBoth: - pipeEvts() - signalEvts() - case proctree.SourceSignals: - signalEvts() - case proctree.SourceEvents: - pipeEvts() - } - - // Pseudo events added by capture (if enabled by the user) - - for eventID, eCfg := range GetCaptureEventsList(cfg) { - t.selectEvent(eventID, eCfg) - } - - // Events chosen by the user - - // TODO: extract this to a function to be called from here and from - // policies changes. - for it := t.policyManager.CreateAllIterator(); it.HasNext(); { - p := it.Next() - for e := range p.EventsToTrace { - var submit, emit uint64 - if _, ok := t.eventsState[e]; ok { - submit = t.eventsState[e].Submit - emit = t.eventsState[e].Emit - } - utils.SetBit(&submit, uint(p.ID)) - utils.SetBit(&emit, uint(p.ID)) - t.selectEvent(e, events.EventState{Submit: submit, Emit: emit}) - - err := t.policyManager.EnableRule(p.ID, e) - if err != nil { - logger.Errorw("Failed to enable rule", "policy", p.ID, "event", e, "error", err) - } - } - } - - // Update capabilities rings with all events dependencies - - // TODO: extract this to a function to be called from here and from - // policies changes. - for id := range t.eventsState { - if !events.Core.IsDefined(id) { - return t, errfmt.Errorf("event %d is not defined", id) - } - depsNode, err := t.eventsDependencies.GetEvent(id) - if err == nil { - deps := depsNode.GetDependencies() - evtCaps := deps.GetCapabilities() - err = caps.BaseRingAdd(evtCaps.GetBase()...) - if err != nil { - return t, errfmt.WrapError(err) - } - err = caps.BaseRingAdd(evtCaps.GetEBPF()...) - if err != nil { - return t, errfmt.WrapError(err) - } - } - } - // Add/Drop capabilities to/from the Base ring (always effective) capsToAdd, err := capabilities.ReqByString(t.config.Capabilities.AddCaps...) @@ -774,7 +578,7 @@ func (t *Tracee) initTailCall(tailCall events.TailCall) error { // derived and the corresponding function to derive into that Event. func (t *Tracee) initDerivationTable() error { shouldSubmit := func(id events.ID) func() bool { - return func() bool { return t.eventsState[id].Submit > 0 } + return func() bool { return t.policyManager.IsEventToSubmit(id) } } symbolsCollisions := derive.SymbolsCollision(t.contSymbolsLoader, t.policyManager) @@ -1010,7 +814,7 @@ func (t *Tracee) initKsymTableRequiredSyms() error { // Get all required symbols needed in the table // 1. all event ksym dependencies // 2. specific cases (hooked_seq_ops, hooked_symbols, print_mem_dump) - for id := range t.eventsState { + for _, id := range t.policyManager.EventsSelected() { if !events.Core.IsDefined(id) { return errfmt.Errorf("event %d is not defined", id) } @@ -1046,12 +850,12 @@ func (t *Tracee) initKsymTableRequiredSyms() error { } // Specific cases - if _, ok := t.eventsState[events.HookedSeqOps]; ok { + if t.policyManager.IsEventSelected(events.HookedSeqOps) { for _, seqName := range derive.NetSeqOps { t.requiredKsyms = append(t.requiredKsyms, seqName) } } - if _, ok := t.eventsState[events.HookedSyscall]; ok { + if t.policyManager.IsEventSelected(events.HookedSyscall) { t.requiredKsyms = append(t.requiredKsyms, events.SyscallPrefix+"ni_syscall", "sys_ni_syscall") for i, kernelRestrictionArr := range events.SyscallSymbolNames { syscallName := t.getSyscallNameByKerVer(kernelRestrictionArr) @@ -1063,7 +867,7 @@ func (t *Tracee) initKsymTableRequiredSyms() error { t.requiredKsyms = append(t.requiredKsyms, events.SyscallPrefix+syscallName) } } - if _, ok := t.eventsState[events.PrintMemDump]; ok { + if t.policyManager.IsEventSelected(events.PrintMemDump) { for it := t.policyManager.CreateAllIterator(); it.HasNext(); { p := it.Next() // This might break in the future if PrintMemDump will become a dependency of another event. @@ -1177,7 +981,7 @@ func (t *Tracee) validateKallsymsDependencies() { return nil }) - for eventId := range t.eventsState { + for _, eventId := range t.policyManager.EventsSelected() { if !validateEvent(eventId) { // Cancel the event, its dependencies and its dependent events err := t.eventsDependencies.RemoveEvent(eventId) @@ -1308,7 +1112,8 @@ func (t *Tracee) populateBPFMaps() error { } // Initialize tail call dependencies - tailCalls := events.Core.GetTailCalls(t.eventsState) + eventsToSubmit := t.policyManager.EventsToSubmit() + tailCalls := events.Core.GetTailCalls(eventsToSubmit) for _, tailCall := range tailCalls { err := t.initTailCall(tailCall) if err != nil { @@ -1324,7 +1129,6 @@ func (t *Tracee) populateFilterMaps(updateProcTree bool) error { polCfg, err := t.policyManager.UpdateBPF( t.bpfModule, t.containers, - t.eventsState, t.eventsParamTypes, true, updateProcTree, @@ -1413,7 +1217,7 @@ func (t *Tracee) attachProbes() error { }) // Attach probes to their respective eBPF programs or cancel events if a required probe is missing. - for eventID := range t.eventsState { + for _, eventID := range t.policyManager.EventsSelected() { err := t.attachEvent(eventID) if err != nil { err := t.eventsDependencies.RemoveEvent(eventID) @@ -1484,7 +1288,7 @@ func (t *Tracee) initBPF() error { } // returned PoliciesConfig is not used here, therefore it's discarded - _, err = t.policyManager.UpdateBPF(t.bpfModule, t.containers, t.eventsState, t.eventsParamTypes, false, true) + _, err = t.policyManager.UpdateBPF(t.bpfModule, t.containers, t.eventsParamTypes, false, true) if err != nil { return errfmt.WrapError(err) } @@ -1755,7 +1559,7 @@ func (t *Tracee) getSelfLoadedPrograms(kprobesOnly bool) map[string]int { // The symbol is do_init_module: kprobe with the program trace_do_init_module, kretprobe with the program trace_ret_do_init_module uniqueHooksMap := map[probeMapKey]struct{}{} - for tr := range t.eventsState { + for _, tr := range t.policyManager.EventsSelected() { if !events.Core.IsDefined(tr) { continue } @@ -1817,43 +1621,43 @@ func (t *Tracee) getSelfLoadedPrograms(kprobesOnly bool) map[string]int { func (t *Tracee) invokeInitEvents(out chan *trace.Event) { var matchedPolicies uint64 - setMatchedPolicies := func(event *trace.Event, matchedPolicies uint64, pManager *policy.PolicyManager) { + setMatchedPolicies := func(event *trace.Event, matchedPolicies uint64) { event.PoliciesVersion = 1 // version will be removed soon event.MatchedPoliciesKernel = matchedPolicies event.MatchedPoliciesUser = matchedPolicies - event.MatchedPolicies = pManager.MatchedNames(matchedPolicies) + event.MatchedPolicies = t.policyManager.MatchedNames(matchedPolicies) } - policiesMatch := func(state events.EventState) uint64 { - return state.Emit | state.Submit + policiesMatch := func(id events.ID) uint64 { + return t.policyManager.MatchEventInAnyPolicy(id) } // Initial namespace events - matchedPolicies = policiesMatch(t.eventsState[events.TraceeInfo]) + matchedPolicies = policiesMatch(events.TraceeInfo) if matchedPolicies > 0 { traceeDataEvent := events.TraceeInfoEvent(t.bootTime, t.startTime) - setMatchedPolicies(&traceeDataEvent, matchedPolicies, t.policyManager) + setMatchedPolicies(&traceeDataEvent, matchedPolicies) out <- &traceeDataEvent _ = t.stats.EventCount.Increment() } - matchedPolicies = policiesMatch(t.eventsState[events.InitNamespaces]) + matchedPolicies = policiesMatch(events.InitNamespaces) if matchedPolicies > 0 { systemInfoEvent := events.InitNamespacesEvent() - setMatchedPolicies(&systemInfoEvent, matchedPolicies, t.policyManager) + setMatchedPolicies(&systemInfoEvent, matchedPolicies) out <- &systemInfoEvent _ = t.stats.EventCount.Increment() } // Initial existing containers events (1 event per container) - matchedPolicies = policiesMatch(t.eventsState[events.ExistingContainer]) + matchedPolicies = policiesMatch(events.ExistingContainer) if matchedPolicies > 0 { existingContainerEvents := events.ExistingContainersEvents(t.containers, t.config.NoContainersEnrich) for i := range existingContainerEvents { event := &(existingContainerEvents[i]) - setMatchedPolicies(event, matchedPolicies, t.policyManager) + setMatchedPolicies(event, matchedPolicies) out <- event _ = t.stats.EventCount.Increment() } @@ -1861,10 +1665,10 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { // Ftrace hook event - matchedPolicies = policiesMatch(t.eventsState[events.FtraceHook]) + matchedPolicies = policiesMatch(events.FtraceHook) if matchedPolicies > 0 { ftraceBaseEvent := events.GetFtraceBaseEvent() - setMatchedPolicies(ftraceBaseEvent, matchedPolicies, t.policyManager) + setMatchedPolicies(ftraceBaseEvent, matchedPolicies) logger.Debugw("started ftraceHook goroutine") // TODO: Ideally, this should be inside the goroutine and be computed before each run, @@ -1881,8 +1685,8 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { // netEnabled returns true if any base network event is to be traced func (t *Tracee) netEnabled() bool { - for k := range t.eventsState { - if k >= events.NetPacketBase && k <= events.MaxNetID { + for _, id := range t.policyManager.EventsSelected() { + if id >= events.NetPacketBase && id <= events.MaxNetID { return true } } @@ -1898,8 +1702,7 @@ func (t *Tracee) netEnabled() bool { // triggerSeqOpsIntegrityCheck is used by a Uprobe to trigger an eBPF program // that prints the seq ops pointers func (t *Tracee) triggerSeqOpsIntegrityCheck(event trace.Event) { - _, ok := t.eventsState[events.HookedSeqOps] - if !ok { + if !t.policyManager.IsEventSelected(events.HookedSeqOps) { return } var seqOpsPointers [len(derive.NetSeqOps)]uint64 @@ -1927,7 +1730,7 @@ func (t *Tracee) triggerSeqOpsIntegrityCheckCall( // triggerMemDump is used by a Uprobe to trigger an eBPF program // that prints the first bytes of requested symbols or addresses func (t *Tracee) triggerMemDump(event trace.Event) []error { - if _, ok := t.eventsState[events.PrintMemDump]; !ok { + if !t.policyManager.IsEventSelected(events.PrintMemDump) { return nil } diff --git a/pkg/events/definition_group.go b/pkg/events/definition_group.go index 5f413504078f..41eaf96f56c8 100644 --- a/pkg/events/definition_group.go +++ b/pkg/events/definition_group.go @@ -181,16 +181,20 @@ func (d *DefinitionGroup) IDs32ToIDs() map[ID]ID { } // GetTailCalls returns a list of tailcalls of all definitions in the group (for initialization). -func (d *DefinitionGroup) GetTailCalls(state map[ID]EventState) []TailCall { +func (d *DefinitionGroup) GetTailCalls(evtsToSubmit []ID) []TailCall { d.mutex.RLock() defer d.mutex.RUnlock() var tailCalls []TailCall - for evtDefID, evtDef := range d.definitions { - if state[evtDefID].Submit > 0 { // only traced events to provide their tailcalls - tailCalls = append(tailCalls, evtDef.GetDependencies().GetTailCalls()...) + for _, id := range evtsToSubmit { + def, ok := d.definitions[id] + if !ok { + logger.Errorw("definition not found", "id", id) + continue } + + tailCalls = append(tailCalls, def.GetDependencies().GetTailCalls()...) } return tailCalls diff --git a/pkg/events/derive/symbols_collision.go b/pkg/events/derive/symbols_collision.go index f3661544f53f..ad77d709e084 100644 --- a/pkg/events/derive/symbols_collision.go +++ b/pkg/events/derive/symbols_collision.go @@ -28,7 +28,7 @@ import ( func SymbolsCollision( soLoader sharedobjs.DynamicSymbolsLoader, - pManager *policy.PolicyManager, + pManager *policy.Manager, ) DeriveFunction { symbolsCollisionFilters := map[string]filters.Filter[*filters.StringFilter]{} diff --git a/pkg/events/derive/symbols_collision_test.go b/pkg/events/derive/symbols_collision_test.go index c4ec581dd7ef..7aa0ffc7f589 100644 --- a/pkg/events/derive/symbols_collision_test.go +++ b/pkg/events/derive/symbols_collision_test.go @@ -9,7 +9,9 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/events/dependencies" "github.com/aquasecurity/tracee/pkg/events/parse" + "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/pkg/policy" "github.com/aquasecurity/tracee/pkg/utils/sharedobjs" ) @@ -465,6 +467,8 @@ func TestSymbolsCollision(t *testing.T) { testCases := getSymbolsCollisionTestCases() pid := 1 + logger.Init(logger.NewDefaultLoggingConfig()) + for _, testCase := range testCases { testCase := testCase @@ -492,7 +496,12 @@ func TestSymbolsCollision(t *testing.T) { require.NoError(t, err) } - pManager := policy.NewPolicyManager(policy.ManagerConfig{}, p) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + pManager, err := policy.NewManager(policy.ManagerConfig{}, depsManager, p) + require.NoError(t, err) // Pick derive function from mocked tests deriveFunc := SymbolsCollision(mockLoader, pManager) diff --git a/pkg/events/derive/symbols_loaded.go b/pkg/events/derive/symbols_loaded.go index c9c3ac11c2ae..8e872d7cc3d2 100644 --- a/pkg/events/derive/symbols_loaded.go +++ b/pkg/events/derive/symbols_loaded.go @@ -20,7 +20,7 @@ import ( func SymbolsLoaded( soLoader sharedobjs.DynamicSymbolsLoader, - pManager *policy.PolicyManager, + pManager *policy.Manager, ) DeriveFunction { symbolsLoadedFilters := map[string]filters.Filter[*filters.StringFilter]{} diff --git a/pkg/policy/ebpf.go b/pkg/policy/ebpf.go index 05911aec96e5..a732cb6099d2 100644 --- a/pkg/policy/ebpf.go +++ b/pkg/policy/ebpf.go @@ -158,7 +158,7 @@ func (ps *policies) createNewFilterMapsVersion(bpfModule *bpf.Module) error { // createNewEventsMapVersion creates a new version of the events map. func (ps *policies) createNewEventsMapVersion( bpfModule *bpf.Module, - eventsState map[events.ID]events.EventState, + rules map[events.ID]*eventFlags, eventsParams map[events.ID][]bufferdecoder.ArgType, ) error { polsVersion := ps.version() @@ -179,11 +179,11 @@ func (ps *policies) createNewEventsMapVersion( // store pointer to the new inner map version ps.bpfInnerMaps[innerMapName] = newInnerMap - for id, ecfg := range eventsState { + for id, ecfg := range rules { eventConfigVal := make([]byte, 16) // bitmap of policies that require this event to be submitted - binary.LittleEndian.PutUint64(eventConfigVal[0:8], ecfg.Submit) + binary.LittleEndian.PutUint64(eventConfigVal[0:8], ecfg.policiesSubmit) // encoded event's parameter types var paramTypes uint64 @@ -450,14 +450,14 @@ func populateProcInfoMap(bpfModule *bpf.Module, binEqualities map[filters.NSBina func (ps *policies) updateBPF( bpfModule *bpf.Module, cts *containers.Containers, - eventsState map[events.ID]events.EventState, + rules map[events.ID]*eventFlags, eventsParams map[events.ID][]bufferdecoder.ArgType, createNewMaps bool, updateProcTree bool, ) (*PoliciesConfig, error) { if createNewMaps { // Create new events map version - if err := ps.createNewEventsMapVersion(bpfModule, eventsState, eventsParams); err != nil { + if err := ps.createNewEventsMapVersion(bpfModule, rules, eventsParams); err != nil { return nil, errfmt.WrapError(err) } } diff --git a/pkg/policy/event_flags.go b/pkg/policy/event_flags.go index 35e5b6fa0f5d..96e69146de99 100644 --- a/pkg/policy/event_flags.go +++ b/pkg/policy/event_flags.go @@ -14,6 +14,9 @@ type eventFlags struct { // It is computed on policies updates. policiesEmit uint64 + // signature indicates if the event is a signature event. + signature bool + // enabled indicates if the event is enabled. // It is *NOT* computed on policies updates, so its value remains the same // until changed via the API. @@ -38,6 +41,12 @@ func eventFlagsWithEmit(emit uint64) eventFlagsOption { } } +func eventFlagsWithSignature(signature bool) eventFlagsOption { + return func(es *eventFlags) { + es.signature = signature + } +} + func eventFlagsWithEnabled(enabled bool) eventFlagsOption { return func(es *eventFlags) { es.enabled = enabled @@ -49,6 +58,7 @@ func newEventFlags(options ...eventFlagsOption) *eventFlags { ef := &eventFlags{ policiesSubmit: 0, policiesEmit: 0, + signature: false, enabled: false, } diff --git a/pkg/policy/policy_manager.go b/pkg/policy/policy_manager.go index 79db40a6f9f5..32aeaf1837fe 100644 --- a/pkg/policy/policy_manager.go +++ b/pkg/policy/policy_manager.go @@ -6,11 +6,15 @@ import ( bpf "github.com/aquasecurity/libbpfgo" "github.com/aquasecurity/tracee/pkg/bufferdecoder" + "github.com/aquasecurity/tracee/pkg/capabilities" "github.com/aquasecurity/tracee/pkg/config" "github.com/aquasecurity/tracee/pkg/containers" "github.com/aquasecurity/tracee/pkg/dnscache" + "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/events/dependencies" "github.com/aquasecurity/tracee/pkg/logger" + "github.com/aquasecurity/tracee/pkg/pcaps" "github.com/aquasecurity/tracee/pkg/proctree" "github.com/aquasecurity/tracee/pkg/utils" ) @@ -21,15 +25,24 @@ type ManagerConfig struct { CaptureConfig config.CaptureConfig } -// PolicyManager is a thread-safe struct that manages the enabled policies for each rule -type PolicyManager struct { - mu sync.RWMutex - cfg ManagerConfig - ps *policies - rules map[events.ID]*eventFlags +// Manager is a thread-safe struct that manages the enabled policies for each rule +type Manager struct { + mu sync.RWMutex + cfg ManagerConfig + evtsDepsManager *dependencies.Manager + ps *policies + rules map[events.ID]*eventFlags } -func NewPolicyManager(cfg ManagerConfig, initialPolicies ...*Policy) *PolicyManager { +func NewManager( + cfg ManagerConfig, + depsManager *dependencies.Manager, + initialPolicies ...*Policy, +) (*Manager, error) { + if depsManager == nil { + panic("evtDepsManager is nil") + } + ps := NewPolicies() for _, p := range initialPolicies { if err := ps.set(p); err != nil { @@ -37,38 +50,288 @@ func NewPolicyManager(cfg ManagerConfig, initialPolicies ...*Policy) *PolicyMana } } - return &PolicyManager{ - mu: sync.RWMutex{}, - cfg: cfg, - ps: ps, - rules: make(map[events.ID]*eventFlags), + m := &Manager{ + mu: sync.RWMutex{}, + cfg: cfg, + evtsDepsManager: depsManager, + ps: ps, + rules: make(map[events.ID]*eventFlags), } + + if err := m.initialize(); err != nil { + return nil, errfmt.Errorf("failed to initialize policy manager: %s", err) + } + + return m, nil +} + +func (m *Manager) subscribeDependencyHandlers() { + // TODO: As dynamic event addition or removal becomes a thing, we should subscribe all the watchers + // before selecting them. There is no reason to select the event in the New function anyhow. + m.evtsDepsManager.SubscribeAdd( + dependencies.EventNodeType, + func(node interface{}) []dependencies.Action { + eventNode, ok := node.(*dependencies.EventNode) + if !ok { + logger.Errorw("Got node from type not requested") + return nil + } + + m.addDependencyEventToRules(eventNode.GetID(), eventNode.GetDependents()) + + return nil + }) + m.evtsDepsManager.SubscribeRemove( + dependencies.EventNodeType, + func(node interface{}) []dependencies.Action { + eventNode, ok := node.(*dependencies.EventNode) + if !ok { + logger.Errorw("Got node from type not requested") + return nil + } + + m.removeEventFromRules(eventNode.GetID()) + + return nil + }) +} + +// AddDependencyEventToRules adds for management an event that is a dependency of other events. +// The difference from chosen events is that it doesn't affect its eviction. +func (m *Manager) addDependencyEventToRules(evtID events.ID, dependentEvts []events.ID) { + var newSubmit uint64 + + for _, dependentEvent := range dependentEvts { + currentFlags, ok := m.rules[dependentEvent] + if ok { + newSubmit |= currentFlags.policiesSubmit + } + } + + m.addEventFlags( + evtID, + newEventFlags( + eventFlagsWithSubmit(newSubmit), + eventFlagsWithSignature(events.Core.GetDefinitionByID(evtID).IsSignature()), + eventFlagsWithEnabled(true), + ), + ) +} + +func (m *Manager) addEventFlags(id events.ID, chosenFlags *eventFlags) { + currentFlags, ok := m.rules[id] + if ok { + currentFlags.policiesSubmit |= chosenFlags.policiesSubmit + currentFlags.policiesEmit |= chosenFlags.policiesEmit + currentFlags.signature = chosenFlags.signature + currentFlags.enabled = chosenFlags.enabled + return + } + + m.rules[id] = newEventFlags( + eventFlagsWithSubmit(chosenFlags.policiesSubmit), + eventFlagsWithEmit(chosenFlags.policiesEmit), + eventFlagsWithSignature(events.Core.GetDefinitionByID(id).IsSignature()), + eventFlagsWithEnabled(chosenFlags.enabled), + ) +} + +func (m *Manager) addDependenciesToRulesRecursive(eventNode *dependencies.EventNode) { + eventID := eventNode.GetID() + for _, dependencyEventID := range eventNode.GetDependencies().GetIDs() { + m.addDependencyEventToRules(dependencyEventID, []events.ID{eventID}) + dependencyNode, err := m.evtsDepsManager.GetEvent(dependencyEventID) + if err == nil { + m.addDependenciesToRulesRecursive(dependencyNode) + } + } +} + +func (m *Manager) selectEvent(eventID events.ID, chosenState *eventFlags) { + m.addEventFlags(eventID, chosenState) + eventNode, err := m.evtsDepsManager.SelectEvent(eventID) + if err != nil { + logger.Errorw("Event selection failed", + "event", events.Core.GetDefinitionByID(eventID).GetName()) + return + } + + m.addDependenciesToRulesRecursive(eventNode) +} + +func (m *Manager) removeEventFromRules(evtID events.ID) { + logger.Debugw("Remove event from rules", "event", events.Core.GetDefinitionByID(evtID).GetName()) + delete(m.rules, evtID) +} + +func (m *Manager) selectMandatoryEvents() { + // Initialize events state with mandatory events (TODO: review this need for sched exec) + + m.selectEvent(events.SchedProcessFork, newEventFlags()) + m.selectEvent(events.SchedProcessExec, newEventFlags()) + m.selectEvent(events.SchedProcessExit, newEventFlags()) + + // Control Plane Events + + m.selectEvent(events.SignalCgroupMkdir, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + m.selectEvent(events.SignalCgroupRmdir, newEventFlags(eventFlagsWithSubmit(PolicyAll))) +} + +func (m *Manager) selectConfiguredEvents() { + // Control Plane Process Tree Events + + pipeEvts := func() { + m.selectEvent(events.SchedProcessFork, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + m.selectEvent(events.SchedProcessExec, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + m.selectEvent(events.SchedProcessExit, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + } + signalEvts := func() { + m.selectEvent(events.SignalSchedProcessFork, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + m.selectEvent(events.SignalSchedProcessExec, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + m.selectEvent(events.SignalSchedProcessExit, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + } + + switch m.cfg.ProcTreeConfig.Source { + case proctree.SourceBoth: + pipeEvts() + signalEvts() + case proctree.SourceSignals: + signalEvts() + case proctree.SourceEvents: + pipeEvts() + } + + // DNS Cache events + + if m.cfg.DNSCacheConfig.Enable { + m.selectEvent(events.NetPacketDNS, newEventFlags(eventFlagsWithSubmit(PolicyAll))) + } + + // Pseudo events added by capture (if enabled by the user) + + getCaptureEventsFlags := func(cfg config.CaptureConfig) map[events.ID]*eventFlags { + captureEvents := make(map[events.ID]*eventFlags) + + // INFO: All capture events should be placed, at least for now, to all matched policies, or else + // the event won't be set to matched policy in eBPF and should_submit() won't submit the capture + // event to userland. + + if cfg.Exec { + captureEvents[events.CaptureExec] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if cfg.FileWrite.Capture { + captureEvents[events.CaptureFileWrite] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if cfg.FileRead.Capture { + captureEvents[events.CaptureFileRead] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if cfg.Module { + captureEvents[events.CaptureModule] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if cfg.Mem { + captureEvents[events.CaptureMem] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if cfg.Bpf { + captureEvents[events.CaptureBpf] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + if pcaps.PcapsEnabled(cfg.Net) { + captureEvents[events.CaptureNetPacket] = newEventFlags(eventFlagsWithSubmit(PolicyAll)) + } + + return captureEvents + } + + for id, flags := range getCaptureEventsFlags(m.cfg.CaptureConfig) { + m.selectEvent(id, flags) + } +} + +func (m *Manager) selectUserEvents() { + // Events chosen by the user + userEvents := make(map[events.ID]*eventFlags) + + for _, p := range m.ps.policiesList { + pId := p.ID + for eId := range p.EventsToTrace { + ef, ok := userEvents[eId] + if !ok { + ef = newEventFlags(eventFlagsWithEnabled(true)) + userEvents[eId] = ef + } + + ef.enableEmission(pId) + ef.enableSubmission(pId) + } + } + + for id, flags := range userEvents { + m.selectEvent(id, flags) + } +} + +func (m *Manager) updateCapsForSelectedEvents() error { + // Update capabilities rings with all events dependencies + + caps := capabilities.GetInstance() + for id := range m.rules { + if !events.Core.IsDefined(id) { + return errfmt.Errorf("event %d is not defined", id) + } + depsNode, err := m.evtsDepsManager.GetEvent(id) + if err == nil { + deps := depsNode.GetDependencies() + evtCaps := deps.GetCapabilities() + err = caps.BaseRingAdd(evtCaps.GetBase()...) + if err != nil { + return errfmt.WrapError(err) + } + err = caps.BaseRingAdd(evtCaps.GetEBPF()...) + if err != nil { + return errfmt.WrapError(err) + } + } + } + + return nil +} + +func (m *Manager) initialize() error { + m.subscribeDependencyHandlers() + m.selectMandatoryEvents() + m.selectConfiguredEvents() + m.selectUserEvents() + err := m.updateCapsForSelectedEvents() + if err != nil { + return errfmt.WrapError(err) + } + + return nil } // IsEnabled tests if a event, or a policy per event is enabled (in the future it will also check if a policy is enabled) // TODO: add metrics about an event being enabled/disabled, or a policy being enabled/disabled? -func (pm *PolicyManager) IsEnabled(matchedPolicies uint64, id events.ID) bool { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) IsEnabled(matchedPolicies uint64, id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() - if !pm.isEventEnabled(id) { + if !m.isEventEnabled(id) { return false } - return pm.isRuleEnabled(matchedPolicies, id) + return m.isRuleEnabled(matchedPolicies, id) } // IsRuleEnabled returns true if a given event policy is enabled for a given rule -func (pm *PolicyManager) IsRuleEnabled(matchedPolicies uint64, id events.ID) bool { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) IsRuleEnabled(matchedPolicies uint64, id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() - return pm.isRuleEnabled(matchedPolicies, id) + return m.isRuleEnabled(matchedPolicies, id) } // not synchronized, use IsRuleEnabled instead -func (pm *PolicyManager) isRuleEnabled(matchedPolicies uint64, id events.ID) bool { - flags, ok := pm.rules[id] +func (m *Manager) isRuleEnabled(matchedPolicies uint64, id events.ID) bool { + flags, ok := m.rules[id] if !ok { return false } @@ -77,16 +340,16 @@ func (pm *PolicyManager) isRuleEnabled(matchedPolicies uint64, id events.ID) boo } // IsEventEnabled returns true if a given event policy is enabled for a given rule -func (pm *PolicyManager) IsEventEnabled(id events.ID) bool { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) IsEventEnabled(id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() - return pm.isEventEnabled(id) + return m.isEventEnabled(id) } // not synchronized, use IsEventEnabled instead -func (pm *PolicyManager) isEventEnabled(id events.ID) bool { - flags, ok := pm.rules[id] +func (m *Manager) isEventEnabled(id events.ID) bool { + flags, ok := m.rules[id] if !ok { return false } @@ -95,22 +358,22 @@ func (pm *PolicyManager) isEventEnabled(id events.ID) bool { } // EnableRule enables a rule for a given event policy -func (pm *PolicyManager) EnableRule(policyId int, id events.ID) error { +func (m *Manager) EnableRule(policyId int, id events.ID) error { if !isIDInRange(policyId) { return PoliciesOutOfRangeError(policyId) } - pm.mu.Lock() - defer pm.mu.Unlock() + m.mu.Lock() + defer m.mu.Unlock() - flags, ok := pm.rules[id] + flags, ok := m.rules[id] if !ok { // if you enabling/disabling a rule for an event that // was not enabled/disabled yet, we assume the event should be enabled flags = newEventFlags( eventFlagsWithEnabled(true), ) - pm.rules[id] = flags + m.rules[id] = flags } flags.enableEmission(policyId) @@ -119,22 +382,22 @@ func (pm *PolicyManager) EnableRule(policyId int, id events.ID) error { } // DisableRule disables a rule for a given event policy -func (pm *PolicyManager) DisableRule(policyId int, id events.ID) error { +func (m *Manager) DisableRule(policyId int, id events.ID) error { if !isIDInRange(policyId) { return PoliciesOutOfRangeError(policyId) } - pm.mu.Lock() - defer pm.mu.Unlock() + m.mu.Lock() + defer m.mu.Unlock() - flags, ok := pm.rules[id] + flags, ok := m.rules[id] if !ok { // if you enabling/disabling a rule for an event that // was not enabled/disabled yet, we assume the event should be enabled flags = newEventFlags( eventFlagsWithEnabled(true), ) - pm.rules[id] = flags + m.rules[id] = flags } flags.disableEmission(policyId) @@ -143,13 +406,13 @@ func (pm *PolicyManager) DisableRule(policyId int, id events.ID) error { } // EnableEvent enables a given event -func (pm *PolicyManager) EnableEvent(id events.ID) { - pm.mu.Lock() - defer pm.mu.Unlock() +func (m *Manager) EnableEvent(id events.ID) { + m.mu.Lock() + defer m.mu.Unlock() - flags, ok := pm.rules[id] + flags, ok := m.rules[id] if !ok { - pm.rules[id] = newEventFlags( + m.rules[id] = newEventFlags( eventFlagsWithEnabled(true), ) return @@ -159,13 +422,13 @@ func (pm *PolicyManager) EnableEvent(id events.ID) { } // DisableEvent disables a given event -func (pm *PolicyManager) DisableEvent(id events.ID) { - pm.mu.Lock() - defer pm.mu.Unlock() +func (m *Manager) DisableEvent(id events.ID) { + m.mu.Lock() + defer m.mu.Unlock() - flags, ok := pm.rules[id] + flags, ok := m.rules[id] if !ok { - pm.rules[id] = newEventFlags( + m.rules[id] = newEventFlags( eventFlagsWithEnabled(false), ) return @@ -175,68 +438,165 @@ func (pm *PolicyManager) DisableEvent(id events.ID) { } // -// Policies methods made available by PolicyManager. +// Rules +// + +func (m *Manager) IsSignature(id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + flags, ok := m.rules[id] + if !ok { + return false + } + + return flags.signature +} + +func (m *Manager) MatchEvent(id events.ID, matched uint64) uint64 { + m.mu.RLock() + defer m.mu.RUnlock() + + flags, ok := m.rules[id] + if !ok { + return 0 + } + + return flags.policiesEmit & matched +} + +func (m *Manager) MatchEventInAnyPolicy(id events.ID) uint64 { + m.mu.RLock() + defer m.mu.RUnlock() + + flags, ok := m.rules[id] + if !ok { + return 0 + } + + return (flags.policiesEmit | flags.policiesSubmit) & PolicyAll +} + +func (m *Manager) EventsSelected() []events.ID { + m.mu.RLock() + defer m.mu.RUnlock() + + eventsSelected := make([]events.ID, 0, len(m.rules)) + for evt := range m.rules { + eventsSelected = append(eventsSelected, evt) + } + + return eventsSelected +} + +func (m *Manager) IsEventSelected(id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + _, ok := m.rules[id] + return ok +} + +func (m *Manager) EventsToSubmit() []events.ID { + m.mu.RLock() + defer m.mu.RUnlock() + + eventsToSubmit := []events.ID{} + for evt, flags := range m.rules { + if flags.policiesSubmit != 0 { + eventsToSubmit = append(eventsToSubmit, evt) + } + } + + return eventsToSubmit +} + +func (m *Manager) IsEventToEmit(id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + flags, ok := m.rules[id] + if !ok { + return false + } + + return flags.policiesEmit != 0 +} + +func (m *Manager) IsEventToSubmit(id events.ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + flags, ok := m.rules[id] + if !ok { + return false + } + + return flags.policiesSubmit != 0 +} + +// +// Policies methods made available by Manager. // Some are transitive (tidying), some are not. // -func (pm *PolicyManager) CreateUserlandIterator() utils.Iterator[*Policy] { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) CreateUserlandIterator() utils.Iterator[*Policy] { + m.mu.RLock() + defer m.mu.RUnlock() // The returned iterator is not thread-safe since its underlying data is not a copy. // A possible solution would be to use the snapshot mechanism with timestamps instead // of version numbers. - return pm.ps.createUserlandIterator() + return m.ps.createUserlandIterator() } -func (pm *PolicyManager) CreateAllIterator() utils.Iterator[*Policy] { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) CreateAllIterator() utils.Iterator[*Policy] { + m.mu.RLock() + defer m.mu.RUnlock() // The returned iterator is not thread-safe since its underlying data is not a copy. // A possible solution would be to use the snapshot mechanism with timestamps instead // of version numbers. - return pm.ps.createAllIterator() + return m.ps.createAllIterator() } -func (pm *PolicyManager) FilterableInUserland(bitmap uint64) bool { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) FilterableInUserland(bitmap uint64) bool { + m.mu.RLock() + defer m.mu.RUnlock() - return (bitmap & pm.ps.filterInUserland()) != 0 + return (bitmap & m.ps.filterInUserland()) != 0 } -func (pm *PolicyManager) WithContainerFilterEnabled() uint64 { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) WithContainerFilterEnabled() uint64 { + m.mu.RLock() + defer m.mu.RUnlock() - return pm.ps.withContainerFilterEnabled() + return m.ps.withContainerFilterEnabled() } -func (pm *PolicyManager) MatchedNames(matched uint64) []string { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) MatchedNames(matched uint64) []string { + m.mu.RLock() + defer m.mu.RUnlock() - return pm.ps.matchedNames(matched) + return m.ps.matchedNames(matched) } -func (pm *PolicyManager) LookupByName(name string) (*Policy, error) { - pm.mu.RLock() - defer pm.mu.RUnlock() +func (m *Manager) LookupByName(name string) (*Policy, error) { + m.mu.RLock() + defer m.mu.RUnlock() - return pm.ps.lookupByName(name) + return m.ps.lookupByName(name) } -func (pm *PolicyManager) UpdateBPF( +func (m *Manager) UpdateBPF( bpfModule *bpf.Module, cts *containers.Containers, - eventsState map[events.ID]events.EventState, eventsParams map[events.ID][]bufferdecoder.ArgType, createNewMaps bool, updateProcTree bool, ) (*PoliciesConfig, error) { - pm.mu.Lock() - defer pm.mu.Unlock() + m.mu.Lock() + defer m.mu.Unlock() - return pm.ps.updateBPF(bpfModule, cts, eventsState, eventsParams, createNewMaps, updateProcTree) + return m.ps.updateBPF(bpfModule, cts, m.rules, eventsParams, createNewMaps, updateProcTree) } diff --git a/pkg/policy/policy_manager_test.go b/pkg/policy/policy_manager_test.go index e127c7d02cb0..7c514b084a86 100644 --- a/pkg/policy/policy_manager_test.go +++ b/pkg/policy/policy_manager_test.go @@ -7,12 +7,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/events/dependencies" ) func TestPolicyManagerEnableRule(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) policy1Mached := uint64(0b10) policy2Mached := uint64(0b100) @@ -22,7 +29,7 @@ func TestPolicyManagerEnableRule(t *testing.T) { assert.False(t, policyManager.IsRuleEnabled(policy2Mached, events.SecurityBPF)) assert.False(t, policyManager.IsRuleEnabled(policy1And2Mached, events.SecurityBPF)) - err := policyManager.EnableRule(1, events.SecurityBPF) + err = policyManager.EnableRule(1, events.SecurityBPF) assert.NoError(t, err) assert.True(t, policyManager.IsRuleEnabled(policy1Mached, events.SecurityBPF)) @@ -43,13 +50,19 @@ func TestPolicyManagerEnableRule(t *testing.T) { func TestPolicyManagerDisableRule(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) policy1Mached := uint64(0b10) policy2Mached := uint64(0b100) policy1And2Mached := uint64(0b110) - err := policyManager.EnableRule(1, events.SecurityBPF) + err = policyManager.EnableRule(1, events.SecurityBPF) assert.NoError(t, err) assert.True(t, policyManager.IsRuleEnabled(policy1Mached, events.SecurityBPF)) @@ -86,7 +99,13 @@ func TestPolicyManagerEnableAndDisableRuleConcurrent(t *testing.T) { events.FileModification, } - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) var wg sync.WaitGroup @@ -125,7 +144,13 @@ func TestPolicyManagerEnableAndDisableRuleConcurrent(t *testing.T) { func TestPolicyManagerEnableEvent(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) assert.False(t, policyManager.isEventEnabled(events.SecurityBPF)) assert.False(t, policyManager.isEventEnabled(events.SecurityFileOpen)) @@ -143,7 +168,13 @@ func TestPolicyManagerEnableEvent(t *testing.T) { func TestPolicyManagerDisableEvent(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) policyManager.EnableEvent(events.SecurityBPF) policyManager.EnableEvent(events.SecurityFileOpen) @@ -180,7 +211,13 @@ func TestPolicyManagerEnableAndDisableEventConcurrent(t *testing.T) { events.FileModification, } - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) // activate events for _, e := range eventsToDisable { @@ -224,7 +261,13 @@ func TestPolicyManagerEnableAndDisableEventConcurrent(t *testing.T) { func TestEnableRuleAlsoEnableEvent(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) assert.False(t, policyManager.IsEventEnabled(events.SecurityBPF)) @@ -236,7 +279,13 @@ func TestEnableRuleAlsoEnableEvent(t *testing.T) { func TestDisableRuleAlsoEnableEvent(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) assert.False(t, policyManager.IsEventEnabled(events.SecurityFileOpen)) @@ -248,7 +297,13 @@ func TestDisableRuleAlsoEnableEvent(t *testing.T) { func TestPolicyManagerIsEnabled(t *testing.T) { t.Parallel() - policyManager := NewPolicyManager(ManagerConfig{}) + depsManager := dependencies.NewDependenciesManager( + func(id events.ID) events.Dependencies { + return events.Core.GetDefinitionByID(id).GetDependencies() + }) + + policyManager, err := NewManager(ManagerConfig{}, depsManager) + assert.NoError(t, err) policy1Mached := uint64(0b10) policy2Mached := uint64(0b100) diff --git a/tests/integration/dependencies_test.go b/tests/integration/dependencies_test.go index b819b4410a48..8b674c1e1d6a 100644 --- a/tests/integration/dependencies_test.go +++ b/tests/integration/dependencies_test.go @@ -46,7 +46,7 @@ func Test_EventsDependencies(t *testing.T) { events: []events.ID{events.MissingKsymbol}, expectedLogs: []string{ "Event canceled because of missing kernel symbol dependency", - "Remove event from state", + "Remove event from rules", }, unexpectedEvents: []events.ID{events.MissingKsymbol}, unexpectedKprobes: []string{"security_bprm_check"}, @@ -56,7 +56,7 @@ func Test_EventsDependencies(t *testing.T) { events: []events.ID{events.MissingKsymbol, events.ExecTest}, expectedLogs: []string{ "Event canceled because of missing kernel symbol dependency", - "Remove event from state", + "Remove event from rules", }, unexpectedEvents: []events.ID{events.MissingKsymbol}, expectedEvents: []events.ID{events.ExecTest}, @@ -67,7 +67,7 @@ func Test_EventsDependencies(t *testing.T) { events: []events.ID{events.FailedAttach}, expectedLogs: []string{ "Cancelling event and its dependencies because of a missing probe", - "Remove event from state", + "Remove event from rules", }, unexpectedEvents: []events.ID{events.FailedAttach}, unexpectedKprobes: []string{"security_bprm_check"}, @@ -77,7 +77,7 @@ func Test_EventsDependencies(t *testing.T) { events: []events.ID{events.FailedAttach, events.ExecTest}, expectedLogs: []string{ "Cancelling event and its dependencies because of a missing probe", - "Remove event from state", + "Remove event from rules", }, unexpectedEvents: []events.ID{events.FailedAttach}, expectedEvents: []events.ID{events.ExecTest},