From eaf952ab8db7e94bcf11825abdb644d9206c950b Mon Sep 17 00:00:00 2001 From: Alon Zivony Date: Tue, 7 May 2024 13:27:15 +0300 Subject: [PATCH] chore(tests): use existing functions for dependencies tests Use integration tests existing functions instead of self implemented. --- pkg/ebpf/c/common/common.h | 2 +- pkg/ebpf/c/tracee.bpf.c | 26 +++-- pkg/ebpf/c/types.h | 2 +- pkg/ebpf/probes/probe_group.go | 2 +- pkg/ebpf/probes/probes.go | 2 +- pkg/events/core.go | 14 +-- tests/integration/dependencies_test.go | 138 +++++++++++------------- tests/integration/event_filters_test.go | 113 ++++++++++++------- 8 files changed, 158 insertions(+), 141 deletions(-) diff --git a/pkg/ebpf/c/common/common.h b/pkg/ebpf/c/common/common.h index b2f16661a660..a101ea92b8eb 100644 --- a/pkg/ebpf/c/common/common.h +++ b/pkg/ebpf/c/common/common.h @@ -32,7 +32,7 @@ statfunc const char *get_device_name(struct device *dev) #define has_prefix(p, s, n) \ ({ \ - int rc = 0; \ + int rc = 1; \ char *pre = p, *str = s; \ _Pragma("unroll") for (int z = 0; z < n; pre++, str++, z++) \ { \ diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 3d8b32e5e5dc..ec951c7cd3fa 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -6771,17 +6771,20 @@ int BPF_KPROBE(empty_probe) return 0; } -bool did_submit = false; - -SEC("raw_tracepoint/submit_once") -int tracepoint__submit_once(struct bpf_raw_tracepoint_args *ctx) +SEC("raw_tracepoint/exec_test") +int tracepoint__exec_test(struct bpf_raw_tracepoint_args *ctx) { - if (likely(did_submit)) { + // Check if test file was executed + struct linux_binprm *bprm = (struct linux_binprm *) ctx->args[2]; + if (bprm == NULL) + return -1; + struct file *file = get_file_ptr_from_bprm(bprm); + void *file_path = get_path_str(__builtin_preserve_access_index(&file->f_path)); + if (file_path == NULL || !has_prefix("/tmp/test", file_path, 9)) return 0; - } + // Submit all test events int ret = 0; - program_data_t p = {}; if (!init_program_data(&p, ctx, NO_EVENT_SUBMIT)) return 0; @@ -6789,7 +6792,7 @@ int tracepoint__submit_once(struct bpf_raw_tracepoint_args *ctx) if (!evaluate_scope_filters(&p)) return 0; - if (!reset_event(p.event, TEST_SUBMIT_ONCE)) + if (!reset_event(p.event, EXEC_TEST)) return 0; if (evaluate_scope_filters(&p)) ret |= events_perf_submit(&p, 0); @@ -6804,10 +6807,5 @@ int tracepoint__submit_once(struct bpf_raw_tracepoint_args *ctx) if (evaluate_scope_filters(&p)) ret |= events_perf_submit(&p, 0); - if (ret == 0) - // This is not a guarantee that the event will be submitted once, but it is good enough for - // tests as the purpose is to not create too much of a load. - did_submit = true; - return 0; -} \ No newline at end of file +} diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index aa558b650717..236e70b40177 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -131,7 +131,7 @@ enum event_id_e NO_EVENT_SUBMIT, // Test events IDs - TEST_SUBMIT_ONCE = 9000, + EXEC_TEST = 9000, TEST_MISSING_KSYMBOLS, TEST_FAILED_ATTACH, }; diff --git a/pkg/ebpf/probes/probe_group.go b/pkg/ebpf/probes/probe_group.go index 563d266a8d48..246a92d0db34 100644 --- a/pkg/ebpf/probes/probe_group.go +++ b/pkg/ebpf/probes/probe_group.go @@ -234,7 +234,7 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool, kSyms *helpers.Ke ExecuteAtFinished: NewTraceProbe(Tracepoint, "syscalls:sys_exit_execveat", "execute_finished"), TestUnavailableHook: NewTraceProbe(KProbe, "non_existing_func", "empty_kprobe"), - TestSubmitOnce: NewTraceProbe(RawTracepoint, "raw_syscalls:sys_enter", "tracepoint__submit_once"), + ExecTest: NewTraceProbe(RawTracepoint, "raw_syscalls:sched_process_exec", "tracepoint__exec_test"), } if !netEnabled { diff --git a/pkg/ebpf/probes/probes.go b/pkg/ebpf/probes/probes.go index 458956755af9..0ffaa90cb110 100644 --- a/pkg/ebpf/probes/probes.go +++ b/pkg/ebpf/probes/probes.go @@ -146,5 +146,5 @@ const ( // Test probe handles const ( TestUnavailableHook = 1000 + iota - TestSubmitOnce + ExecTest ) diff --git a/pkg/events/core.go b/pkg/events/core.go index f4c76ac9c561..d8f8e9af3550 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -173,7 +173,7 @@ const ( // Test events const ( - SubmitOnce ID = 9000 + iota + ExecTest ID = 9000 + iota MissingKsymbol FailedAttach ) @@ -13589,19 +13589,19 @@ var CoreEvents = map[ID]Definition{ }, // Test Events - SubmitOnce: { - id: SubmitOnce, + ExecTest: { + id: ExecTest, id32Bit: Sys32Undefined, - name: "submit_once", + name: "exec_test", version: NewVersion(1, 0, 0), syscall: false, sets: []string{"tests", "dependencies"}, - params: []trace.ArgMeta{}, dependencies: Dependencies{ probes: []Probe{ - {handle: probes.TestSubmitOnce, required: true}, + {handle: probes.ExecTest, required: true}, }, }, + params: []trace.ArgMeta{}, }, MissingKsymbol: { id: MissingKsymbol, @@ -13616,7 +13616,7 @@ var CoreEvents = map[ID]Definition{ {symbol: "non_existing_symbol", required: true}, }, probes: []Probe{ - {handle: probes.TestSubmitOnce, required: true}, + {handle: probes.ExecTest, required: true}, }, }, }, diff --git a/tests/integration/dependencies_test.go b/tests/integration/dependencies_test.go index 4f6cd3df8010..c420d8c84321 100644 --- a/tests/integration/dependencies_test.go +++ b/tests/integration/dependencies_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/goleak" "github.com/aquasecurity/tracee/pkg/config" @@ -33,33 +34,63 @@ func Test_EventsDependencies(t *testing.T) { }{ { name: "sanity of submit once test event", - events: []events.ID{events.SubmitOnce}, - expectedEvents: []events.ID{events.SubmitOnce}, + events: []events.ID{events.ExecTest}, + expectedEvents: []events.ID{events.ExecTest}, }, { name: "non existing ksymbol dependency", - events: []events.ID{events.MissingKsymbol, events.SubmitOnce}, + events: []events.ID{events.MissingKsymbol, events.ExecTest}, expectedLogs: []string{ "Event canceled because of missing kernel symbol dependency", "Remove event from state", }, unexpectedEvents: []events.ID{events.MissingKsymbol}, - expectedEvents: []events.ID{events.SubmitOnce}, + expectedEvents: []events.ID{events.ExecTest}, }, { name: "non existing probe function", - events: []events.ID{events.FailedAttach, events.SubmitOnce}, + events: []events.ID{events.FailedAttach, events.ExecTest}, expectedLogs: []string{ "Cancelling event and its dependencies because of a missing probe", "Remove event from state", }, unexpectedEvents: []events.ID{events.FailedAttach}, - expectedEvents: []events.ID{events.SubmitOnce}, + expectedEvents: []events.ID{events.ExecTest}, }, } logOutChan, restoreLogger := testutils.SetTestLogger(logger.DebugLevel) + // Each test will run a test binary that triggers the "exec_test" event. + // Upon its execution, which events are evicted and which not will be tested + createCmdEvents := func(expectedEventsIDs []events.ID, unexpectedEventsIDs []events.ID) []cmdEvents { + expectedEvents := make([]trace.Event, len(expectedEventsIDs)) + for i, eventId := range expectedEventsIDs { + expectedEvents[i] = createGenericEventForCmdEvents(eventId) + } + unexpectedEvents := make([]trace.Event, len(unexpectedEventsIDs)) + for i, eventId := range unexpectedEventsIDs { + unexpectedEvents[i] = createGenericEventForCmdEvents(eventId) + } + return []cmdEvents{ + { + runCmd: "cp /bin/ls /tmp/test", + timeout: time.Second, + }, + { + runCmd: "/tmp/test", + waitFor: time.Second, + timeout: 3 * time.Second, + expectedEvents: expectedEvents, + unexpectedEvents: unexpectedEvents, + }, + { + runCmd: "rm /tmp/test", + timeout: time.Second, + }, + } + } + for _, testCaseInst := range testCases { t.Run( testCaseInst.name, func(t *testing.T) { @@ -86,13 +117,6 @@ func Test_EventsDependencies(t *testing.T) { stream := trc.SubscribeAll() defer trc.Unsubscribe(stream) - eventsResultChan := testEvents( - t, - testCaseInst.expectedEvents, - testCaseInst.unexpectedEvents, - stream.ReceiveEvents(), - done, - ) err = waitForTraceeStart(trc) if err != nil { @@ -100,8 +124,22 @@ func Test_EventsDependencies(t *testing.T) { t.Fatal(err) } - // Give the events a chance to be received and pass the pipeline - time.Sleep(3 * time.Second) + // start a goroutine to read events from the channel into the buffer + buf := newEventBuffer() + go func(ctx context.Context, buf *eventBuffer) { + for { + select { + case <-ctx.Done(): + return + case evt := <-stream.ReceiveEvents(): + buf.addEvent(evt) + } + } + }(ctx, buf) + + // Test events + testCmdEvents := createCmdEvents(testCaseInst.expectedEvents, testCaseInst.unexpectedEvents) + require.NoError(t, ExpectAtLeastOneForEach(t, testCmdEvents, buf, false)) cancel() errStop := waitForTraceeStop(trc) @@ -113,72 +151,20 @@ func Test_EventsDependencies(t *testing.T) { close(done) assert.True(t, <-logsResultChan) - assert.True(t, <-eventsResultChan) }, ) } restoreLogger() } -// testEvents searches for the given events in the events channel, and when the -// run is completed test whether the given events have been found. -// It gives the option for searching for event that should and shouldn't be -// found to enable precise tests. -func testEvents( - t *testing.T, - expectedEvents []events.ID, - unexpectedEvents []events.ID, - eventsOutput <-chan trace.Event, - done <-chan struct{}, -) <-chan bool { - expectedResults := make(map[events.ID]bool, len(expectedEvents)) - for _, eventID := range expectedEvents { - expectedResults[eventID] = false - } - unexpectedResults := make(map[events.ID]bool, len(unexpectedEvents)) - for _, eventID := range unexpectedEvents { - unexpectedResults[eventID] = false +func createGenericEventForCmdEvents(eventId events.ID) trace.Event { + return trace.Event{ + HostName: anyHost, + ProcessName: anyComm, + ProcessorID: anyProcessorID, + ProcessID: anyPID, + UserID: anyUID, + EventID: int(eventId), + MatchedPoliciesUser: anyPolicy, } - outChan := make(chan bool) - - go func() { - Loop: - for { - select { - case receivedEvent, ok := <-eventsOutput: - if !ok { - break Loop - } - if _, ok := expectedResults[events.ID(receivedEvent.EventID)]; ok { - expectedResults[events.ID(receivedEvent.EventID)] = true - } - if _, ok := unexpectedResults[events.ID(receivedEvent.EventID)]; ok { - unexpectedResults[events.ID(receivedEvent.EventID)] = true - } - case <-done: - break Loop - } - } - - for expectedEvent, found := range expectedResults { - assert.True(t, found, expectedEvent) - if !found { - outChan <- false - close(outChan) - return - } - } - for unexpectedEvent, found := range unexpectedResults { - assert.False(t, found, unexpectedEvent) - if found { - outChan <- false - close(outChan) - return - } - } - outChan <- true - close(outChan) - }() - - return outChan } diff --git a/tests/integration/event_filters_test.go b/tests/integration/event_filters_test.go index 617b9ff7acac..0b89cdb59e01 100644 --- a/tests/integration/event_filters_test.go +++ b/tests/integration/event_filters_test.go @@ -1795,21 +1795,22 @@ type testCase struct { } type cmdEvents struct { - runCmd string - waitFor time.Duration // time to wait before collecting events - timeout time.Duration // timeout for the command to run - evts []trace.Event - sets []string + runCmd string + waitFor time.Duration // time to wait before collecting events + timeout time.Duration // timeout for the command to run + expectedEvents []trace.Event + unexpectedEvents []trace.Event + sets []string } // newCmdEvents is a helper function to create a cmdEvents func newCmdEvents(runCmd string, waitFor, timeout time.Duration, evts []trace.Event, sets []string) cmdEvents { return cmdEvents{ - runCmd: runCmd, - waitFor: waitFor, - timeout: timeout, - evts: evts, - sets: sets, + runCmd: runCmd, + waitFor: waitFor, + timeout: timeout, + expectedEvents: evts, + sets: sets, } } @@ -1929,8 +1930,8 @@ func runCmds(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, useSyscal return nil, 0, err } - procs = append(procs, proc{pid, len(cmd.evts)}) - expectedEvts += len(cmd.evts) + procs = append(procs, proc{pid, len(cmd.expectedEvents)}) + expectedEvts += len(cmd.expectedEvents) waitForAverage += cmd.waitFor } if waitForAverage > 0 { @@ -1950,7 +1951,7 @@ func formatCmdEvents(cmd *cmdEvents) { syscallerAbsPath := filepath.Join("..", "..", "dist", "syscaller") cmd.runCmd = fmt.Sprintf("%s %s", syscallerAbsPath, cmd.runCmd) - for _, evt := range cmd.evts { + for _, evt := range cmd.expectedEvents { cmd.runCmd = fmt.Sprintf("%s %d", cmd.runCmd, evt.EventID) } } @@ -2056,19 +2057,21 @@ func ExpectAtLeastOneForEach(t *testing.T, cmdEvents []cmdEvents, actual *eventB actual.clear() // first stage: run commands - proc, err := runCmd(t, cmd, len(cmd.evts), actual, useSyscaller, true) + proc, err := runCmd(t, cmd, len(cmd.expectedEvents), actual, useSyscaller, true) if err != nil { return err } - if len(cmd.evts) == 0 && proc.expectedEvts > 0 { - return fmt.Errorf("expected no events for command %s, but got %d", cmd.runCmd, proc.expectedEvts) + if len(cmd.expectedEvents) == 0 && proc.expectedEvts > 0 { + return fmt.Errorf( + "expected no events for command %s, but got %d", + cmd.runCmd, + proc.expectedEvts, + ) } actEvtsCopy := actual.getCopy() - // second stage: validate events - for _, expEvt := range cmd.evts { - found := false + findEventInResults := func(expEvt trace.Event) (bool, error) { checkHost := expEvt.HostName != anyHost checkComm := expEvt.ProcessName != anyComm checkProcessorID := expEvt.ProcessorID != anyProcessorID @@ -2078,10 +2081,6 @@ func ExpectAtLeastOneForEach(t *testing.T, cmdEvents []cmdEvents, actual *eventB checkPolicy := expEvt.MatchedPoliciesUser != anyPolicy checkPolicyName := len(expEvt.MatchedPolicies) > 0 && expEvt.MatchedPolicies[0] != anyPolicyName - if len(cmd.evts) > 0 && proc.expectedEvts == 0 { - return fmt.Errorf("expected events for command %s, but got none", cmd.runCmd) - } - for _, actEvt := range actEvtsCopy { if checkSets && !isInSets(actEvt.EventName, syscallsInSets) { continue @@ -2132,9 +2131,13 @@ func ExpectAtLeastOneForEach(t *testing.T, cmdEvents []cmdEvents, actual *eventB // check args for _, expArg := range expEvt.Args { - actArg, err := helpers.GetTraceeArgumentByName(actEvt, expArg.Name, helpers.GetArgOps{DefaultArgs: false}) + actArg, err := helpers.GetTraceeArgumentByName( + actEvt, + expArg.Name, + helpers.GetArgOps{DefaultArgs: false}, + ) if err != nil { - return err + return false, err } switch v := expArg.Value.(type) { case string: @@ -2155,14 +2158,44 @@ func ExpectAtLeastOneForEach(t *testing.T, cmdEvents []cmdEvents, actual *eventB } } } - // if we got here, it means we found a match and can stop searching - found = true - break + return true, nil + } + return false, nil + } + + // second stage: validate events + for _, expEvt := range cmd.expectedEvents { + if len(cmd.expectedEvents) > 0 && proc.expectedEvts == 0 { + return fmt.Errorf("expected events for command %s, but got none", cmd.runCmd) + } + found, err := findEventInResults(expEvt) + if err != nil { + return err } - // evaluate found if !found { - return fmt.Errorf("Event %+v:\nnot found in actual output:\n%+v", expEvt, actEvtsCopy) + return fmt.Errorf( + "Event %+v:\nnot found in actual output:\n%+v", + expEvt, + actEvtsCopy, + ) + } + } + + for _, expEvt := range cmd.unexpectedEvents { + if len(cmd.expectedEvents) > 0 && proc.expectedEvts == 0 { + return fmt.Errorf("expected events for command %s, but got none", cmd.runCmd) + } + found, err := findEventInResults(expEvt) + if err != nil { + return err + } + if found { + return fmt.Errorf( + "Event %+v:\nfound in actual output but was not expected:\n%+v", + expEvt, + actEvtsCopy, + ) } } } @@ -2180,7 +2213,7 @@ func ExpectAtLeastOneForEach(t *testing.T, cmdEvents []cmdEvents, actual *eventB // and want to confirm that at least one of them happened. func ExpectAnyOfEvts(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, useSyscaller bool) error { for _, cmd := range cmdEvents { - if len(cmd.evts) <= 1 { + if len(cmd.expectedEvents) <= 1 { return fmt.Errorf("ExpectAnyOfEvts test requires at least 2 expected events for command %s", cmd.runCmd) } @@ -2201,7 +2234,7 @@ func ExpectAnyOfEvts(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, u // second stage: validate events found := false - for _, expEvt := range cmd.evts { + for _, expEvt := range cmd.expectedEvents { checkHost := expEvt.HostName != anyHost checkComm := expEvt.ProcessName != anyComm checkProcessorID := expEvt.ProcessorID != anyProcessorID @@ -2211,7 +2244,7 @@ func ExpectAnyOfEvts(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, u checkPolicy := expEvt.MatchedPoliciesUser != anyPolicy checkPolicyName := len(expEvt.MatchedPolicies) > 0 && expEvt.MatchedPolicies[0] != anyPolicyName - if len(cmd.evts) > 0 && proc.expectedEvts == 0 { + if len(cmd.expectedEvents) > 0 && proc.expectedEvts == 0 { return fmt.Errorf("expected events for command %s, but got none", cmd.runCmd) } @@ -2301,7 +2334,7 @@ func ExpectAnyOfEvts(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, u // evaluate found if !found { - return fmt.Errorf("none of the expected events\n%+v\nare in the actual output\n%+v", cmd.evts, actEvtsCopy) + return fmt.Errorf("none of the expected events\n%+v\nare in the actual output\n%+v", cmd.expectedEvents, actEvtsCopy) } } @@ -2316,13 +2349,13 @@ func ExpectAnyOfEvts(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, u // specific event, and all commands should match their respective events. func ExpectAllEvtsEqualToOne(t *testing.T, cmdEvents []cmdEvents, actual *eventBuffer, useSyscaller bool) error { for _, cmd := range cmdEvents { - if len(cmd.evts) != 1 { - return fmt.Errorf("ExpectAllEvtsEqualToOne test requires exactly one event per command, but got %d events for command %s", len(cmd.evts), cmd.runCmd) + if len(cmd.expectedEvents) != 1 { + return fmt.Errorf("ExpectAllEvtsEqualToOne test requires exactly one event per command, but got %d events for command %s", len(cmd.expectedEvents), cmd.runCmd) } actual.clear() // first stage: run commands - proc, err := runCmd(t, cmd, len(cmd.evts), actual, useSyscaller, true) + proc, err := runCmd(t, cmd, len(cmd.expectedEvents), actual, useSyscaller, true) if err != nil { return err } @@ -2339,7 +2372,7 @@ func ExpectAllEvtsEqualToOne(t *testing.T, cmdEvents []cmdEvents, actual *eventB } // second stage: validate events - for _, expEvt := range cmd.evts { + for _, expEvt := range cmd.expectedEvents { checkHost := expEvt.HostName != anyHost checkComm := expEvt.ProcessName != anyComm checkProcessorID := expEvt.ProcessorID != anyProcessorID @@ -2438,8 +2471,8 @@ func ExpectAllInOrderSequentially(t *testing.T, cmdEvents []cmdEvents, actual *e } // compare the expected events with the actual events in the same order - for evtIdx, expEvt := range cmd.evts { - actEvt := actEvtsCopy[cmdIdx*len(cmd.evts)+evtIdx] + for evtIdx, expEvt := range cmd.expectedEvents { + actEvt := actEvtsCopy[cmdIdx*len(cmd.expectedEvents)+evtIdx] if checkSets && !isInSets(actEvt.EventName, syscallsInSets) { return fmt.Errorf("Event %s not found in sets %v", actEvt.EventName, cmd.sets)