Skip to content
Draft
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
4 changes: 4 additions & 0 deletions bpf/process/generic_calls.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ generic_start_process_filter(void *ctx, struct bpf_map_def *calls)
return 0;
if (!policy_filter_check(config->policy_id))
return 0;
// todo: this should replace the policy filter check above
if (config->cgroup_filter && !get_policy_from_cgroup())
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a duplicate logic of the above policy_filter_check(). Instead of hardcoding a policy value inside the policy config and using it as a key for a global BPF_MAP_TYPE_HASH_OF_MAPS, we could just use a simple hashmap BPF_MAP_TYPE_HASH and configure at runtime the policy_id associated with the groups.

If we deploy a k8s-aware policy (without bindings) and we assign it a policy_id=4

kind: TracingPolicy
metadata:
  name: "lseek-podfilter"
spec:
  podSelector:
    matchLabels:
      app: "lseek-test"
  kprobes:
  - call: "sys_lseek"
    syscall: true
    args:
    - index: 0
      type: "int"
    selectors:
    - matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "-1"
      matchActions:
      - action: Sigkill

We can translate the pod selectors into a simple hash map identical to the one introduced in this patch

cgroup_id1 -> 4
cgroup_id2 -> 4

So instead of starting from the policy_id to get a HashMap like

cgroup_id1 -> 1
cgroup_id2 -> 1

We immediately jump inside the hash map with the cgroup-id, and we can recover the policy_id number from there.
WDYT?

return 0;
// bpf_printk("passed the check on the cgroup");
msg->func_id = config->func_id;
msg->retprobe_id = 0;

Expand Down
41 changes: 35 additions & 6 deletions bpf/process/policy_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,34 @@ struct {
});
} policy_filter_cgroup_maps SEC(".maps");

#define CGROUP_TO_POLICY_MAX_ENTRIES 1
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, CGROUP_TO_POLICY_MAX_ENTRIES);
__uint(map_flags, BPF_F_NO_PREALLOC);
__type(key, __u64); /* Key is the cgrpid */
__type(value, __u32); /* Value is the policy id */
} cg_to_policy_map SEC(".maps");

// policy_filter_check checks whether the policy applies on the current process.
// Returns true if it does, false otherwise.

FUNC_INLINE __u64 get_cgroup_id_from_curr_task()
{
__u64 cgroupid = tg_get_current_cgroup_id();
if (!cgroupid)
return 0;

__u64 trackerid = cgrp_get_tracker_id(cgroupid);
if (trackerid)
cgroupid = trackerid;

return cgroupid;
}

FUNC_INLINE bool policy_filter_check(u32 policy_id)
{
void *policy_map;
__u64 cgroupid, trackerid;

if (!policy_id)
return true;
Expand All @@ -63,15 +84,23 @@ FUNC_INLINE bool policy_filter_check(u32 policy_id)
if (!policy_map)
return false;

cgroupid = tg_get_current_cgroup_id();
__u64 cgroupid = get_cgroup_id_from_curr_task();
if (!cgroupid)
return false;

trackerid = cgrp_get_tracker_id(cgroupid);
if (trackerid)
cgroupid = trackerid;

return map_lookup_elem(policy_map, &cgroupid);
}

// cgroup_filter_check checks whether the current cgroup has a policy applied.
// Returns policy_id if it does, 0 otherwise.
FUNC_INLINE __u32 get_policy_from_cgroup()
{
__u64 cgroupid = get_cgroup_id_from_curr_task();
if (!cgroupid)
return 0;

__u32* policy_id = (__u32*)map_lookup_elem(&cg_to_policy_map, &cgroupid);
return policy_id ? *policy_id : 0;
}

#endif /* POLICY_FILTER_MAPS_H__ */
33 changes: 33 additions & 0 deletions bpf/process/string_maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,39 @@ DEFINE_ARRAY_OF_STRING_MAPS(9)
DEFINE_ARRAY_OF_STRING_MAPS(10)
#endif

#define POLICY_STR_OUTER_MAX_ENTRIES 1
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw some discussion here, #1408 about having one single map instead of 11 different maps. We can also accept the performance loss and use just one unique shared map to simplify things

#define POLICY_STR_INNER_MAX_ENTRIES 1

#define DEFINE_POLICY_STR_HASH_OF_MAPS(N) \
struct { \
__uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); \
__uint(max_entries, POLICY_STR_OUTER_MAX_ENTRIES); \
__uint(map_flags, BPF_F_NO_PREALLOC); \
__type(key, __u32); \
__array( \
values, struct { \
__uint(type, BPF_MAP_TYPE_HASH); \
__uint(max_entries, POLICY_STR_INNER_MAX_ENTRIES); \
__type(key, __u8[STRING_MAPS_SIZE_##N]); \
__type(value, __u8); \
}); \
} pol_str_maps_##N SEC(".maps");

DEFINE_POLICY_STR_HASH_OF_MAPS(0)
DEFINE_POLICY_STR_HASH_OF_MAPS(1)
DEFINE_POLICY_STR_HASH_OF_MAPS(2)
DEFINE_POLICY_STR_HASH_OF_MAPS(3)
DEFINE_POLICY_STR_HASH_OF_MAPS(4)
DEFINE_POLICY_STR_HASH_OF_MAPS(5)
DEFINE_POLICY_STR_HASH_OF_MAPS(6)
DEFINE_POLICY_STR_HASH_OF_MAPS(7)

#ifdef __V511_BPF_PROG
DEFINE_POLICY_STR_HASH_OF_MAPS(8)
DEFINE_POLICY_STR_HASH_OF_MAPS(9)
DEFINE_POLICY_STR_HASH_OF_MAPS(10)
#endif

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
Expand Down
74 changes: 64 additions & 10 deletions bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "process/heap.h"
#include "../bpf_mbset.h"
#include "bpf_ktime.h"
#include "../policy_filter.h"

/* Type IDs form API with user space generickprobe.go */
enum {
Expand Down Expand Up @@ -209,7 +210,7 @@ struct event_config {
*/
__u32 policy_id;
__u32 flags;
__u32 pad;
__u32 cgroup_filter;
struct config_btf_arg btf_arg[EVENT_CONFIG_MAX_ARG][MAX_BTF_ARG_DEPTH];
struct config_usdt_arg usdt_arg[EVENT_CONFIG_MAX_USDT_ARG];
} __attribute__((packed));
Expand Down Expand Up @@ -691,14 +692,54 @@ FUNC_INLINE void *get_string_map(int index, __u32 map_idx)
return 0;
}

FUNC_INLINE void *get_policy_string_map(int index, __u32* map_idx)
{
switch (index) {
case 0:
return map_lookup_elem(&pol_str_maps_0, map_idx);
case 1:
return map_lookup_elem(&pol_str_maps_1, map_idx);
case 2:
return map_lookup_elem(&pol_str_maps_2, map_idx);
case 3:
return map_lookup_elem(&pol_str_maps_3, map_idx);
case 4:
return map_lookup_elem(&pol_str_maps_4, map_idx);
case 5:
return map_lookup_elem(&pol_str_maps_5, map_idx);
#ifdef __LARGE_BPF_PROG
case 6:
return map_lookup_elem(&pol_str_maps_6, map_idx);
case 7:
return map_lookup_elem(&pol_str_maps_7, map_idx);
#ifdef __V511_BPF_PROG
case 8:
return map_lookup_elem(&pol_str_maps_8, map_idx);
case 9:
return map_lookup_elem(&pol_str_maps_9, map_idx);
case 10:
return map_lookup_elem(&pol_str_maps_10, map_idx);
#endif
#endif
}
return 0;
}

// This the value that `filter->vallen` will have when the filter doesn't use
// static values but dynamic ones. 8 because it is the number of bytes required
// to skip the the filter header.
// [index:u32]
// [op:u32]
// [section_len:u32] -> 4 bytes
// [type:u32] -> 4 bytes
// so starting after `op` we need to skip 8 bytes to skip the filter header.
#define HAS_DYNAMIC_VALUES 8

FUNC_LOCAL long
filter_char_buf_equal(struct selector_arg_filter *filter, char *arg_str, uint orig_len)
{
__u32 *map_ids = (__u32 *)&filter->value;
char *heap, *zero_heap;
void *string_map;
__u16 padded_len;
__u32 map_idx;
int zero = 0;
__u16 len;
int index;
Expand All @@ -723,9 +764,6 @@ filter_char_buf_equal(struct selector_arg_filter *filter, char *arg_str, uint or
// Check if we have entries for this padded length.
// Do this before we copy data for efficiency.
index = string_map_index(padded_len);
map_idx = map_ids[index & 0xf];
if (map_idx == 0xffffffff)
return 0;

heap = (char *)map_lookup_elem(&string_maps_heap, &zero);
zero_heap = (char *)map_lookup_elem(&heap_ro_zero, &zero);
Expand Down Expand Up @@ -769,14 +807,30 @@ filter_char_buf_equal(struct selector_arg_filter *filter, char *arg_str, uint or
probe_read(heap + len + 1, (padded_len - len) & STRING_MAPS_COPY_MASK, zero_heap);
#endif
}
void *string_map = 0;
// todo: ideally we should prune this code in the verifier with some ebpf constants.
if (filter->vallen == HAS_DYNAMIC_VALUES) {
__u32 policy_id = get_policy_from_cgroup();
if (!policy_id) {
// this should never happen since we check it before
return 0;
}
// bpf_printk("Dynamic value for policy %d, index %d", policy_id, index);
string_map = get_policy_string_map(index, &policy_id);
} else {
__u32 *map_ids = (__u32 *)&filter->value;
__u32 map_idx = map_ids[index & 0xf];
if (map_idx == 0xffffffff)
return 0;
// Get map for this string length
string_map = get_string_map(index, map_idx);
}

// Get map for this string length
string_map = get_string_map(index, map_idx);
if (!string_map)
return 0;

__u8 *pass = map_lookup_elem(string_map, heap);

// bpf_printk("lookup for string %s, pass %d", heap, !!pass);
return !!pass;
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/tracingapi/client_kprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ type EventConfig struct {
ArgReturnAction int32 `align:"argreturnaction"`
PolicyID uint32 `align:"policy_id"`
Flags uint32 `align:"flags"`
Pad uint32 `align:"pad"`
CgroupFilter uint32 `align:"cgroup_filter"`
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just an hack to enable/disable the new logic

BTFArg [EventConfigMaxArgs][MaxBTFArgDepth]ConfigBTFArg `align:"btf_arg"`
UsdtArg [EventConfigMaxUsdtArgs]ConfigUsdtArg `align:"usdt_arg"`
}
68 changes: 68 additions & 0 deletions pkg/policyfilter/base_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package policyfilter

import (
"github.com/cilium/tetragon/pkg/labels"
)

type basePolicy struct {
id PolicyID
namespace string
containerSelector labels.Selector
podSelector labels.Selector
}

func (b *basePolicy) setID(polID PolicyID) {
b.id = polID
}

func (b *basePolicy) setFilters(namespace string, podSelector labels.Selector, containerSelector labels.Selector) {
b.namespace = namespace
b.podSelector = podSelector
b.containerSelector = containerSelector
}

func (b *basePolicy) getID() PolicyID {
return b.id
}

func (b *basePolicy) podInfoMatches(pod *podInfo) bool {
if pod == nil {
return false
}
return b.podMatches(pod.namespace, pod.labels)
}

func (b *basePolicy) podMatches(podNs string, podLabels labels.Labels) bool {
if b.namespace != "" && podNs != b.namespace {
return false
}
podLabels1 := make(labels.Labels)
if podLabels != nil {
podLabels1 = podLabels
}
if _, ok := podLabels1[labels.K8sPodNamespace]; !ok {
podLabels1[labels.K8sPodNamespace] = podNs
}
return b.podSelector.Match(podLabels1)
}

func (b *basePolicy) containerMatches(container *containerInfo) bool {
if container == nil {
return false
}
filter := labels.Labels{
"name": container.name,
"repo": container.repo,
}
return b.containerSelector.Match(filter)
}

func (b *basePolicy) matchingContainersCgroupIDs(containers []containerInfo) []CgroupID {
var ids []CgroupID
for i := range containers {
if b.containerMatches(&containers[i]) {
ids = append(ids, containers[i].cgID)
}
}
return ids
}
Loading
Loading