-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add unit test for bpf function selector_match in pid match filter. Signed-off-by: arthur-zhang <[email protected]>
- Loading branch information
1 parent
b27d2f8
commit 2adc409
Showing
3 changed files
with
205 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// 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/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"); | ||
|
||
__attribute__((section("raw_tracepoint/test"), used)) int | ||
test_pid_match() | ||
{ | ||
__u32 *f; | ||
int zero = 0, index = 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, | ||
}; | ||
|
||
return selector_match(f, &sel, enter, NULL, &process_filter_pid); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// 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 | ||
} | ||
|
||
// setupTest loads the test program and returns a test context. | ||
func setupTest(t *testing.T) (*testContext, error) { | ||
ctx := &testContext{} | ||
// load test program | ||
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") | ||
|
||
return ctx, nil | ||
} | ||
|
||
func (ctx *testContext) cleanup() { | ||
if ctx.coll != nil { | ||
ctx.coll.Close() | ||
} | ||
} | ||
|
||
// runProg runs the test program with the given PID and returns the result. | ||
func (ctx *testContext) runProg(selfPid uint32) (uint32, error) { | ||
err := ctx.execvMap.Update(uint32(0), &execvemap.ExecveValue{ | ||
Process: processapi.MsgExecveKey{Pid: selfPid}, | ||
}, 0) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to update execve map: %w", err) | ||
} | ||
res, err := ctx.prog.Run(&ebpf.RunOptions{}) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to run program: %w", err) | ||
} | ||
return res, nil | ||
} | ||
|
||
// initKernelStateData initializes the kernel state data with the given PIDs. | ||
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 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 uint32 | ||
}{ | ||
// Test case where the test PID is the only PID to match. | ||
{ | ||
name: "Match_1_PID", | ||
pids: []uint32{1}, | ||
testPid: 1, | ||
expected: 1, | ||
}, | ||
// Test case where the test PID is in the list of 2 PIDs to match. | ||
{ | ||
name: "Match_2_PID", | ||
pids: []uint32{1, 2}, | ||
testPid: 2, | ||
expected: 1, | ||
}, | ||
// Test case where the test PID is not in the list of 2 PIDs to match. | ||
{ | ||
name: "Match_2_PID_NOT_IN_LIST", | ||
pids: []uint32{1, 2}, | ||
testPid: 3, | ||
expected: 0, | ||
}, | ||
// Test case where the test PID is in the list of 4 PIDs to match. | ||
{ | ||
name: "Match_4_PID", | ||
pids: []uint32{1, 2, 3, 4}, | ||
testPid: 4, | ||
expected: 1, | ||
}, | ||
// Test case where the test PID is not in the list of 4 PIDs to match. | ||
{ | ||
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.initKernelStateData(tt.pids)) | ||
result, err := ctx.runProg(tt.testPid) | ||
require.NoError(t, err) | ||
assert.Equal(t, tt.expected, result) | ||
}) | ||
} | ||
} |