diff --git a/bpf/process/pfilter.h b/bpf/process/pfilter.h index 4bd630d05a3..ed35fe9871c 100644 --- a/bpf/process/pfilter.h +++ b/bpf/process/pfilter.h @@ -342,15 +342,19 @@ selector_match(__u32 *f, struct selector_filter *sel, four: res4 = process_filter(sel, f, enter, &msg->ns, &msg->caps); index = next_pid_value(index, f, ty); + sel->index = index; three: res3 = process_filter(sel, f, enter, &msg->ns, &msg->caps); index = next_pid_value(index, f, ty); + sel->index = index; two: res2 = process_filter(sel, f, enter, &msg->ns, &msg->caps); index = next_pid_value(index, f, ty); + sel->index = index; one: res1 = process_filter(sel, f, enter, &msg->ns, &msg->caps); index = next_pid_value(index, f, ty); + sel->index = index; if (ty == op_filter_notin) return res1 & res2 & res3 & res4; diff --git a/bpf/tests/Makefile b/bpf/tests/Makefile index 53447ee06ca..7c9db843f69 100644 --- a/bpf/tests/Makefile +++ b/bpf/tests/Makefile @@ -1,6 +1,6 @@ include ../Makefile.defs -TESTS = prepend_name_test.o +TESTS = prepend_name_test.o pid_match_test.o OBJSDIR := objs/ OBJS := $(addprefix $(OBJSDIR),$(TESTS)) diff --git a/bpf/tests/pid_match_test.c b/bpf/tests/pid_match_test.c new file mode 100644 index 00000000000..daeff03ca47 --- /dev/null +++ b/bpf/tests/pid_match_test.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* Copyright Authors of Cilium */ + +//go:build ignore + +#include "vmlinux.h" +#include "compiler.h" +#include "bpf_core_read.h" +#include "bpf_helpers.h" +#include "process/retprobe_map.h" + +#include "process/types/basic.h" +#include "process/generic_calls.h" +#include "process/pfilter.h" + +char _license[] __attribute__((section("license"), used)) = "Dual BSD/GPL"; + +struct filter_map_value { + unsigned char buf[FILTER_SIZE]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct filter_map_value); +} test_filter_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, int); +} test_result_map SEC(".maps"); + +__attribute__((section("raw_tracepoint/test"), used)) int +test_pid_match() +{ + __u32 *f; + int zero = 0, index = 0, res = -1, err = 0; + struct pid_filter *pid; + struct execve_map_value *enter; + + f = map_lookup_elem(&test_filter_map, &zero); + + if (!f) { + return 0; + } + pid = (struct pid_filter *)((u64)f + index); + index += sizeof(struct pid_filter); + + enter = map_lookup_elem(&execve_map, &zero); + if (!enter) { + return 0; + } + + struct selector_filter sel = { + .index = index, + .ty = pid->op, + .flags = pid->flags, + .len = pid->len, + }; + res = selector_match(f, &sel, enter, NULL, &process_filter_pid); + + err = map_update_elem(&test_result_map, &zero, &res, BPF_ANY); + if (err) { + return 0; + } + + return 0; +} diff --git a/bpf/tests/pid_match_test.go b/bpf/tests/pid_match_test.go new file mode 100644 index 00000000000..061a5fd9f1c --- /dev/null +++ b/bpf/tests/pid_match_test.go @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +package bpf + +import ( + "errors" + "fmt" + "testing" + + "github.com/cilium/ebpf" + "github.com/cilium/tetragon/pkg/api/processapi" + "github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1" + "github.com/cilium/tetragon/pkg/selectors" + "github.com/cilium/tetragon/pkg/sensors/exec/execvemap" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const programNamePidMatch = "test_pid_match" + +type testContext struct { + coll *ebpf.Collection + prog *ebpf.Program + execvMap *ebpf.Map + resultMap *ebpf.Map + t *testing.T +} + +func setupTest(t *testing.T) (*testContext, error) { + ctx := &testContext{t: t} + + coll, err := ebpf.LoadCollection("objs/pid_match_test.o") + if err != nil { + var ve *ebpf.VerifierError + if errors.As(err, &ve) { + return nil, fmt.Errorf("verifier error: %+v", ve) + } + return nil, err + } + ctx.coll = coll + + var ok bool + ctx.prog, ok = coll.Programs[programNamePidMatch] + require.True(t, ok, "program %s not found", programNamePidMatch) + + ctx.execvMap, ok = coll.Maps["execve_map"] + require.True(t, ok, "execve_map not found") + + ctx.resultMap, ok = coll.Maps["test_result_map"] + require.True(t, ok, "test_result_map not found") + + return ctx, nil +} + +func (ctx *testContext) cleanup() { + if ctx.coll != nil { + ctx.coll.Close() + } +} + +func (ctx *testContext) runProg(selfPid uint32) error { + err := ctx.execvMap.Update(uint32(0), &execvemap.ExecveValue{ + Process: processapi.MsgExecveKey{Pid: selfPid}, + }, 0) + if err != nil { + return fmt.Errorf("failed to update execve map: %w", err) + } + _, err = ctx.prog.Run(&ebpf.RunOptions{}) + return err +} + +func (ctx *testContext) getResult() (int32, error) { + var res int32 + err := ctx.resultMap.Lookup(uint32(0), &res) + return res, err +} + +func (ctx *testContext) initKernelStatedata(pids []uint32) error { + k := &selectors.KernelSelectorState{} + err := selectors.ParseMatchPid(k, &v1alpha1.PIDSelector{ + Operator: "In", + Values: pids, + IsNamespacePID: false, + FollowForks: false, + }) + if err != nil { + return fmt.Errorf("failed to parse PID selector: %w", err) + } + + filterMap, ok := ctx.coll.Maps["test_filter_map"] + if !ok { + return errors.New("test_filter_map not found") + } + + return filterMap.Update(uint32(0), k.Buffer(), 0) +} + +func (ctx *testContext) resetResult() error { + return ctx.resultMap.Update(uint32(0), int32(-1), 0) +} + +func Test_PidMatch(t *testing.T) { + ctx, err := setupTest(t) + require.NoError(t, err) + defer ctx.cleanup() + + tests := []struct { + name string + pids []uint32 + testPid uint32 + expected int32 + }{ + { + name: "Match_1_PID", + pids: []uint32{1}, + testPid: 1, + expected: 1, + }, + { + name: "Match_2_PID", + pids: []uint32{1, 2}, + testPid: 2, + expected: 1, + }, + { + name: "Match_2_PID_NOT_IN_LIST", + pids: []uint32{1, 2}, + testPid: 3, + expected: 0, + }, + { + name: "Match_4_PID", + pids: []uint32{1, 2, 3, 4}, + testPid: 4, + expected: 1, + }, + { + name: "Match_4_PID_NOT_IN_LIST", + pids: []uint32{1, 2, 3, 4}, + testPid: 5, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.NoError(t, ctx.resetResult()) + require.NoError(t, ctx.initKernelStatedata(tt.pids)) + require.NoError(t, ctx.runProg(tt.testPid)) + + result, err := ctx.getResult() + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +}