Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Prefix and NotPrefix operators to matchBinaries #1732

Merged
merged 6 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,7 @@ static inline __attribute__((always_inline)) size_t type_to_min_size(int type,

struct match_binaries_sel_opts {
__u32 op;
__u32 map_id;
};

// This map is used by the matchBinaries selectors to retrieve their options
Expand Down Expand Up @@ -1513,6 +1514,10 @@ static inline __attribute__((always_inline)) int match_binaries(__u32 selidx)
bool walker, match = 0;
void *path_map;
__u8 *found_key;
#ifdef __LARGE_BPF_PROG
struct string_prefix_lpm_trie prefix_key;
long ret;
#endif /* __LARGE_BPF_PROG */

struct match_binaries_sel_opts *selector_options;

Expand Down Expand Up @@ -1541,13 +1546,28 @@ static inline __attribute__((always_inline)) int match_binaries(__u32 selidx)
if (!path_map)
return 0;
found_key = map_lookup_elem(path_map, current->bin.path);
match = !!found_key;
break;
#ifdef __LARGE_BPF_PROG
case op_filter_str_prefix:
case op_filter_str_notprefix:
path_map = map_lookup_elem(&string_prefix_maps, &selector_options->map_id);
if (!path_map)
return 0;
// prepare the key on the stack to perform lookup in the LPM_TRIE
memset(&prefix_key, 0, sizeof(prefix_key));
prefix_key.prefixlen = current->bin.path_length * 8; // prefixlen is in bits
ret = probe_read(prefix_key.data, current->bin.path_length & (STRING_PREFIX_MAX_LENGTH - 1), current->bin.path);
if (ret < 0)
return 0;
found_key = map_lookup_elem(path_map, &prefix_key);
break;
#endif /* __LARGE_BPF_PROG */
default:
// should not happen
return 0;
}

match = !!found_key;
return is_not_operator(selector_options->op) ? !match : match;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down Expand Up @@ -897,6 +899,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down Expand Up @@ -1270,6 +1274,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down Expand Up @@ -897,6 +899,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down Expand Up @@ -1270,6 +1274,8 @@ spec:
enum:
- In
- NotIn
- Prefix
- NotPrefix
type: string
values:
description: Value to compare the argument against.
Expand Down
2 changes: 1 addition & 1 deletion pkg/k8s/apis/cilium.io/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type KProbeArg struct {
}

type BinarySelector struct {
// +kubebuilder:validation:Enum=In;NotIn
// +kubebuilder:validation:Enum=In;NotIn;Prefix;NotPrefix
// Filter operation.
Operator string `json:"operator"`
// Value to compare the argument against.
Expand Down
2 changes: 1 addition & 1 deletion pkg/k8s/apis/cilium.io/v1alpha1/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ package v1alpha1
// Used to determine if CRD needs to be updated in cluster
//
// Developers: Bump patch for each change in the CRD schema.
const CustomResourceDefinitionSchemaVersion = "1.1.0"
const CustomResourceDefinitionSchemaVersion = "1.1.1"
27 changes: 24 additions & 3 deletions pkg/selectors/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,18 +726,31 @@ func writeMatchStrings(k *KernelSelectorState, values []string, ty uint32) error
return nil
}

func writePrefixStrings(k *KernelSelectorState, values []string) error {
func writePrefix(k *KernelSelectorState, values []string, selector string) (uint32, error) {
mid, m := k.newStringPrefixMap()
for _, v := range values {
value, size := ArgSelectorValue(v)
if size > StringPrefixMaxLength {
return fmt.Errorf("MatchArgs value %s invalid: string is longer than %d characters", v, StringPrefixMaxLength)
return 0, fmt.Errorf("%s value %s invalid: string is longer than %d characters", selector, v, StringPrefixMaxLength)
}
val := KernelLPMTrieStringPrefix{prefixLen: size * 8} // prefix is in bits, but size is in bytes
copy(val.data[:], value)
m[val] = struct{}{}
}
return mid, nil
// write the map id into the selector

}

func writePrefixBinaries(k *KernelSelectorState, values []string) (uint32, error) {
return writePrefix(k, values, "MatchBinaries")
}

func writePrefixStrings(k *KernelSelectorState, values []string) error {
mid, err := writePrefix(k, values, "MatchArgs")
if err != nil {
return err
}
WriteSelectorUint32(&k.data, mid)
return nil
}
Expand Down Expand Up @@ -1191,8 +1204,16 @@ func ParseMatchBinary(k *KernelSelectorState, b *v1alpha1.BinarySelector, selIdx
}
k.WriteMatchBinariesPath(selIdx, s)
}
case SelectorOpPrefix, SelectorOpNotPrefix:
if !kernels.EnableLargeProgs() {
return fmt.Errorf("matchBinary error: \"Prefix\" and \"NotPrefix\" operators need large BPF progs (kernel>5.3)")
}
sel.MapID, err = writePrefixBinaries(k, b.Values)
if err != nil {
return fmt.Errorf("failed to write the prefix operator for the matchBinaries selector: %w", err)
}
default:
return fmt.Errorf("matchBinary error: Only \"In\" and \"NotIn\" operators are supported")
return fmt.Errorf("matchBinary error: Only \"In\", \"NotIn\", \"Prefix\" and \"NotPrefix\" operators are supported")
}

k.AddMatchBinaries(selIdx, sel)
Expand Down
3 changes: 2 additions & 1 deletion pkg/selectors/selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ type KernelSelectorMaps struct {
}

type MatchBinariesSelectorOptions struct {
Op uint32
Op uint32
MapID uint32
}

type KernelSelectorData struct {
Expand Down
144 changes: 139 additions & 5 deletions pkg/sensors/tracing/kprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3663,18 +3663,32 @@ func matchBinariesTest(t *testing.T, operator string, values []string, kpChecker
assert.NoError(t, err)
}

const skipMatchBinariesPrefix = "kernels without large progs do not support matchBinaries Prefix/NotPrefix"

func TestKprobeMatchBinaries(t *testing.T) {
t.Run("In", func(t *testing.T) {
matchBinariesTest(t, "In", []string{"/usr/bin/tail"}, createBinariesChecker("/usr/bin/tail", "/etc/passwd"))
})
t.Run("NotIn", func(t *testing.T) {
matchBinariesTest(t, "NotIn", []string{"/usr/bin/tail"}, createBinariesChecker("/usr/bin/head", "/etc/passwd"))
})
t.Run("Prefix", func(t *testing.T) {
if !kernels.EnableLargeProgs() {
t.Skip(skipMatchBinariesPrefix)
}
matchBinariesTest(t, "Prefix", []string{"/usr/bin/t"}, createBinariesChecker("/usr/bin/tail", "/etc/passwd"))
})
t.Run("NotPrefix", func(t *testing.T) {
if !kernels.EnableLargeProgs() {
t.Skip(skipMatchBinariesPrefix)
}
matchBinariesTest(t, "NotPrefix", []string{"/usr/bin/t"}, createBinariesChecker("/usr/bin/head", "/etc/passwd"))
})
}

// TestKprobeMatchBinariesPerfring checks that the matchBinaries with "In" do
// correctly filter the events i.e. it checks that no other events appear.
func TestKprobeMatchBinariesPerfring(t *testing.T) {
// matchBinariesPerfringTest checks that the matchBinaries do correctly
// filter the events i.e. it checks that no other events appear.
func matchBinariesPerfringTest(t *testing.T, operator string, values []string) {
testutils.CaptureLog(t, logger.GetLogger().(*logrus.Logger))
ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime)
defer cancel()
Expand All @@ -3700,8 +3714,8 @@ func TestKprobeMatchBinariesPerfring(t *testing.T) {
{
MatchBinaries: []v1alpha1.BinarySelector{
{
Operator: "In",
Values: []string{"/usr/bin/tail"},
Operator: operator,
Values: values,
},
},
},
Expand Down Expand Up @@ -3751,6 +3765,18 @@ func TestKprobeMatchBinariesPerfring(t *testing.T) {
}
}

func TestKprobeMatchBinariesPerfring(t *testing.T) {
t.Run("In", func(t *testing.T) {
matchBinariesPerfringTest(t, "In", []string{"/usr/bin/tail"})
})
t.Run("Prefix", func(t *testing.T) {
if !kernels.EnableLargeProgs() {
t.Skip(skipMatchBinariesPrefix)
}
matchBinariesPerfringTest(t, "Prefix", []string{"/usr/bin/t"})
})
}

// TestKprobeMatchBinariesEarlyExec checks that the matchBinaries can filter
// events triggered by process started before Tetragon.
func TestKprobeMatchBinariesEarlyExec(t *testing.T) {
Expand Down Expand Up @@ -3822,6 +3848,114 @@ func TestKprobeMatchBinariesEarlyExec(t *testing.T) {
t.Error("events triggered by process executed before Tetragon should not be ignored because of matchBinaries")
}

// TestKprobeMatchBinariesPrefixMatchArgs makes sure that the prefix of
// matchBinaries works well with the prefix of matchArgs since its reusing some
// of its machinery.
func TestKprobeMatchBinariesPrefixMatchArgs(t *testing.T) {
if !kernels.EnableLargeProgs() {
t.Skip(skipMatchBinariesPrefix)
}

testutils.CaptureLog(t, logger.GetLogger().(*logrus.Logger))
ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime)
defer cancel()

if err := observer.InitDataCache(1024); err != nil {
t.Fatalf("observertesthelper.InitDataCache: %s", err)
}

option.Config.HubbleLib = tus.Conf().TetragonLib
tus.LoadSensor(t, base.GetInitialSensor())
tus.LoadSensor(t, testsensor.GetTestSensor())
sm := tus.GetTestSensorManager(ctx, t)

matchBinariesTracingPolicy := tracingpolicy.GenericTracingPolicy{
Metadata: v1.ObjectMeta{
Name: "match-binaries",
},
Spec: v1alpha1.TracingPolicySpec{
KProbes: []v1alpha1.KProbeSpec{
{
Call: "sys_openat",
Syscall: true,
Args: []v1alpha1.KProbeArg{
{
Index: 1,
Type: "string",
},
},
Selectors: []v1alpha1.KProbeSelector{
{
MatchBinaries: []v1alpha1.BinarySelector{
{
Operator: "Prefix",
Values: []string{"/usr/bin/ta"},
},
},
MatchArgs: []v1alpha1.ArgSelector{
{
Index: 1,
Operator: "Prefix",
Values: []string{"/etc/pass"}, // not just /etc because of /etc/ld.so.cache
},
},
},
},
},
},
},
}

err := sm.Manager.AddTracingPolicy(ctx, &matchBinariesTracingPolicy)
assert.NoError(t, err)

var tailEtcPID, tailProcPID, headPID int
ops := func() {
tailEtcCmd := exec.Command("/usr/bin/tail", "/etc/passwd")
tailProcCmd := exec.Command("/usr/bin/tail", "/proc/uptime")
headCmd := exec.Command("/usr/bin/head", "/etc/passwd")

err := tailEtcCmd.Start()
assert.NoError(t, err)
tailEtcPID = tailEtcCmd.Process.Pid
err = tailProcCmd.Start()
assert.NoError(t, err)
tailProcPID = tailProcCmd.Process.Pid
err = headCmd.Start()
assert.NoError(t, err)
headPID = headCmd.Process.Pid

err = tailEtcCmd.Wait()
assert.NoError(t, err)
err = tailProcCmd.Wait()
assert.NoError(t, err)
err = headCmd.Wait()
assert.NoError(t, err)
}
events := perfring.RunTestEvents(t, ctx, ops)

tailEventExist := false
for _, ev := range events {
if kprobe, ok := ev.(*tracing.MsgGenericKprobeUnix); ok {
if int(kprobe.ProcessKey.Pid) == tailEtcPID {
tailEventExist = true
continue
}
if int(kprobe.ProcessKey.Pid) == tailProcPID {
t.Error("kprobe event triggered by \"/usr/bin/tail /proc/uptime\" should be filtered by the matchArgs selector")
break
}
if int(kprobe.ProcessKey.Pid) == headPID {
t.Error("kprobe event triggered by /usr/bin/head should be filtered by the matchBinaries selector")
break
}
}
}
if !tailEventExist {
t.Error("kprobe event triggered by /usr/bin/tail should be present, unfiltered by the matchBinaries selector")
}
}

func loadTestCrd() error {
testHook := `apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading