Skip to content
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
18 changes: 18 additions & 0 deletions bpf/lib/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,24 @@ struct {
__type(value, struct msg_execve_event);
} execve_msg_heap_map SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct binary);
} tg_binary_heap SEC(".maps");

// Parent binaries map is used for saving actual immediate parents
// for processes to get check them in matchParentBinaries selector.
// If multiple execs are called in same process without fork, the map
// stores process binary itself instead of its parent binary.
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct binary);
} tg_parents_bin SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
Expand Down
5 changes: 4 additions & 1 deletion bpf/process/bpf_execve_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ event_execve(struct exec_ctx_struct *ctx)
parent = event_find_parent();
if (parent) {
event->parent = parent->key;
update_mb_task(parent);
update_mb_task(parent, &parent->bin);
event->parent_flags = 0;
} else {
event_minimal_parent(event, task);
Expand Down Expand Up @@ -414,6 +414,9 @@ execve_send(struct exec_ctx_struct *ctx __arg_ctx)
curr->caps.inheritable = event->creds.caps.inheritable;
}
#endif

update_parents_map(event, curr);

/* zero out previous paths in ->bin */
binary_reset(&curr->bin);
#ifdef __LARGE_BPF_PROG
Expand Down
1 change: 1 addition & 0 deletions bpf/process/bpf_exit.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ FUNC_INLINE void event_exit_send(void *ctx, __u32 tgid)
event_output_metric(ctx, MSG_OP_EXIT, exit, size);
}
execve_map_delete(tgid);
map_delete_elem(&tg_parents_bin, &enter->key.pid);
}

#endif /* __EXIT_H__ */
16 changes: 8 additions & 8 deletions bpf/process/bpf_mbset.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void update_mb_bitset(struct binary *bin)
#ifdef __V511_BPF_PROG

FUNC_INLINE
struct execve_map_value *__update_mb_task(struct execve_map_value *task)
struct execve_map_value *__update_mb_task(struct execve_map_value *task, struct binary *bin)
{
struct execve_map_value *parent;
__u64 *bitsetp;
Expand All @@ -58,12 +58,12 @@ struct execve_map_value *__update_mb_task(struct execve_map_value *task)
return NULL;
bitsetp = map_lookup_elem(&tg_mbset_map, parent->bin.path);
if (bitsetp && *bitsetp)
lock_or(&task->bin.mb_bitset, *bitsetp);
lock_or(&bin->mb_bitset, *bitsetp);
return parent;
}

FUNC_INLINE
void update_mb_task(struct execve_map_value *task)
void update_mb_task(struct execve_map_value *task, struct binary *bin)
{
struct execve_map_value *last = NULL, *parent = task;
__u32 idx = 0;
Expand All @@ -72,29 +72,29 @@ void update_mb_task(struct execve_map_value *task)
gen = map_lookup_elem(&tg_mbset_gen, &idx);
if (!gen)
return;
if (*gen == task->bin.mb_gen)
if (*gen == bin->mb_gen)
return;

if (CONFIG(ITER_NUM)) {
bpf_repeat(100000)
{
parent = __update_mb_task(task);
parent = __update_mb_task(task, bin);
if (!parent || parent == last)
break;
last = parent;
}
} else {
for (int i = 0; i < 1024; i++) {
parent = __update_mb_task(task);
parent = __update_mb_task(task, bin);
if (!parent || parent == last)
break;
last = parent;
}
}

task->bin.mb_gen = *gen;
bin->mb_gen = *gen;
}
#else
#define update_mb_task(task)
#define update_mb_task(task, bin)
#endif /* __V511_BPF_PROG */
#endif /* __BPF_MBSET_H__ */
30 changes: 30 additions & 0 deletions bpf/process/bpf_process_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,34 @@ event_find_curr_probe(struct msg_generic_kprobe *msg)
return NULL;
}
#endif

#ifdef __LARGE_BPF_PROG
volatile const __u8 PARENTS_MAP_ENABLED;

FUNC_INLINE void update_parents_map(struct msg_execve_event *event, struct execve_map_value *curr)
{
if (PARENTS_MAP_ENABLED) {
__u32 zero = 0;
struct binary *bin = map_lookup_elem(&tg_binary_heap, &zero);

if (bin) {
// use current binary as parent binary if exec events is not preceded
// by clone, i.e. exec call was invoked in the same process.
if (!(event->process.flags & EVENT_CLONE)) {
memcpy(bin, &curr->bin, sizeof(curr->bin));
map_update_elem(&tg_parents_bin, &curr->key.pid, bin, BPF_ANY);
} else {
struct execve_map_value *parent = event_find_parent();

if (parent)
map_update_elem(&tg_parents_bin, &curr->key.pid, &parent->bin, BPF_ANY);
}
}
}
}
#else
FUNC_INLINE void update_parents_map(struct msg_execve_event *event, struct execve_map_value *curr)
{
}
#endif
#endif
17 changes: 15 additions & 2 deletions bpf/process/pfilter.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef __PFILTER_H__
#define __PFILTER_H__

#include "bpf_process_event.h"

/**
* Process filters (see generic_process_filter)
*/
Expand Down Expand Up @@ -425,10 +427,21 @@ selector_process_filter(__u32 *f, __u32 index, struct execve_map_value *enter,
__u32 len;
__u64 i;

/* Do binary filter first for selector index */
if (!match_binaries(index, enter))
/* Do binary and parent filter first for selector index */
if (!match_binaries(index, enter, &enter->bin))
return 0;

#ifdef __LARGE_BPF_PROG
if (PARENTS_MAP_ENABLED) {
struct binary *parent_bin = map_lookup_elem(&tg_parents_bin, &enter->key.pid);

if (parent_bin)
/* matchParentBinaries key is in range [MAX_SELECTORS; MAX_SELECTORS * 2) */
if (!match_binaries(index + MAX_SELECTORS, enter, parent_bin))
return 0;
}
#endif

/* Find selector offset byte index */
index *= 4;
index += 4;
Expand Down
49 changes: 30 additions & 19 deletions bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -1849,17 +1849,21 @@ struct match_binaries_sel_opts {
__u32 mbset_id;
};

// This map is used by the matchBinaries selectors to retrieve their options
// We need data for:
// - matchBinaries, keys [0, MAX_SELECTORS)
// - matchParentBinaries, keys [MAX_SELECTORS, MAX_SELECTORS * 2)
#define MB_MAX_ENTRIES (MAX_SELECTORS * 2)

struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, MAX_SELECTORS);
__uint(max_entries, MB_MAX_ENTRIES);
__type(key, __u32); /* selector id */
__type(value, struct match_binaries_sel_opts);
} tg_mb_sel_opts SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, MAX_SELECTORS); // only one matchBinaries per selector
__uint(max_entries, MB_MAX_ENTRIES);
__type(key, __u32);
__array(
values, struct {
Expand All @@ -1870,7 +1874,7 @@ struct {
});
} tg_mb_paths SEC(".maps");

FUNC_INLINE int match_binaries(__u32 selidx, struct execve_map_value *current)
FUNC_INLINE int match_binaries(__u32 key, struct execve_map_value *current, struct binary *bin)
{
bool match = 0;
void *path_map;
Expand All @@ -1886,36 +1890,43 @@ FUNC_INLINE int match_binaries(__u32 selidx, struct execve_map_value *current)

// retrieve the selector_options for the matchBinaries, if it's NULL it
// means there is not matchBinaries in this selector.
selector_options = map_lookup_elem(&tg_mb_sel_opts, &selidx);
selector_options = map_lookup_elem(&tg_mb_sel_opts, &key);

// if we failed to get process or its binary (i.e. one of them is NULL),
// we have match only if there are no selector options or if selector
// operator is none.
if (!current || !bin)
return !selector_options || selector_options->op == op_filter_none;

if (selector_options) {
if (selector_options->op == op_filter_none)
return 1; // matchBinaries selector is empty <=> match

if (current->bin.path_length < 0) {
if (bin->path_length < 0) {
// something wrong happened when copying the filename to execve_map
return 0;
}

switch (selector_options->op) {
case op_filter_in:
case op_filter_notin:
update_mb_task(current);
update_mb_task(current, bin);

/* Check if we match the selector's bit in ->mb_bitset, which means that the
* process matches a matchBinaries section with a followChidren:true
* attribute either because the binary matches or because the binary of a
* parent matched.
*/
if (selector_options->mbset_id != MBSET_INVALID_ID &&
(current->bin.mb_bitset & (1UL << selector_options->mbset_id))) {
(bin->mb_bitset & (1UL << selector_options->mbset_id))) {
found_key = (u8 *)0xbadc0ffee;
break;
}

path_map = map_lookup_elem(&tg_mb_paths, &selidx);
path_map = map_lookup_elem(&tg_mb_paths, &key);
if (!path_map)
return 0;
found_key = map_lookup_elem(path_map, current->bin.path);
found_key = map_lookup_elem(path_map, bin->path);
break;
#ifdef __LARGE_BPF_PROG
case op_filter_str_prefix:
Expand All @@ -1928,8 +1939,8 @@ FUNC_INLINE int match_binaries(__u32 selidx, struct execve_map_value *current)
if (!prefix_key)
return 0;
memset(prefix_key, 0, sizeof(*prefix_key));
prefix_key->prefixlen = current->bin.path_length * 8; // prefixlen is in bits
if (probe_read(prefix_key->data, current->bin.path_length & (STRING_PREFIX_MAX_LENGTH - 1), current->bin.path) < 0)
prefix_key->prefixlen = bin->path_length * 8; // prefixlen is in bits
if (probe_read(prefix_key->data, bin->path_length & (STRING_PREFIX_MAX_LENGTH - 1), bin->path) < 0)
return 0;
found_key = map_lookup_elem(path_map, prefix_key);
break;
Expand All @@ -1938,18 +1949,18 @@ FUNC_INLINE int match_binaries(__u32 selidx, struct execve_map_value *current)
path_map = map_lookup_elem(&string_postfix_maps, &selector_options->map_id);
if (!path_map)
return 0;
if (current->bin.path_length < STRING_POSTFIX_MAX_MATCH_LENGTH)
postfix_len = current->bin.path_length;
if (bin->path_length < STRING_POSTFIX_MAX_MATCH_LENGTH)
postfix_len = bin->path_length;
postfix_key = (struct string_postfix_lpm_trie *)map_lookup_elem(&string_postfix_maps_heap, &zero);
if (!postfix_key)
return 0;
postfix_key->prefixlen = postfix_len * 8; // prefixlen is in bits
if (!current->bin.reversed) {
file_copy_reverse((__u8 *)current->bin.end_r, postfix_len, (__u8 *)current->bin.end, current->bin.path_length - postfix_len);
current->bin.reversed = true;
if (!bin->reversed) {
file_copy_reverse((__u8 *)bin->end_r, postfix_len, (__u8 *)bin->end, bin->path_length - postfix_len);
bin->reversed = true;
}
if (postfix_len < STRING_POSTFIX_MAX_MATCH_LENGTH)
if (probe_read(postfix_key->data, postfix_len, current->bin.end_r) < 0)
if (probe_read(postfix_key->data, postfix_len, bin->end_r) < 0)
return 0;
found_key = map_lookup_elem(path_map, postfix_key);
break;
Expand All @@ -1963,7 +1974,7 @@ FUNC_INLINE int match_binaries(__u32 selidx, struct execve_map_value *current)
return is_not_operator(selector_options->op) ? !match : match;
}

// no matchBinaries selector <=> match
// no selector <=> match
return 1;
}

Expand Down
4 changes: 4 additions & 0 deletions cmd/tetragon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ func tetragonExecuteCtx(ctx context.Context, cancel context.CancelFunc, ready fu
logger.Fatal(log, "Can't specify --execve-map-entries and --execve-map-size together")
}

if option.Config.ParentsMapEntries != 0 && len(option.Config.ParentsMapSize) != 0 {
logger.Fatal(log, "Can't specify --parents-map-entries and --parents-map-size together")
}

if option.Config.EnableProcessEnvironmentVariables && !config.EnableLargeProgs() {
logger.Fatal(log, "Can't specify --enable-process-environment-variables on early kernels (<v5.3)")
}
Expand Down
60 changes: 59 additions & 1 deletion docs/content/en/docs/concepts/tracing-policy/selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Each selector comprises a set of filters:
- [`matchReturnArgs`](#return-args-filter): filter on the return value.
- [`matchPIDs`](#pids-filter): filter on PID.
- [`matchBinaries`](#binaries-filter): filter on binary path.
- [`matchParentBinaries`](#parent-binaries-filter): filter on parent binary path.
- [`matchNamespaces`](#namespaces-filter): filter on Linux namespaces.
- [`matchCapabilities`](#capabilities-filter): filter on Linux capabilities.
- [`matchNamespaceChanges`](#namespace-changes-filter): filter on Linux namespaces changes.
Expand Down Expand Up @@ -264,7 +265,7 @@ is `followForks: true`, so all the child processes are followed.

### Follow children

the `matchBinaries` filter can be configured to also apply to children of matching processes. To do
The `matchBinaries` filter can be configured to also apply to children of matching processes. To do
this, set `followChildren` to `true`. For example:

```yaml
Expand Down Expand Up @@ -327,6 +328,63 @@ while the whole `kprobe` call is the following:
- "3"
```

## Parent binaries filter

{{< warning >}}
`matchParentBinaries` selector can be used only with BPF map `parents_map` enabled (option `--parents-map-enabled`), which adds
additional memory overhead.
{{< /warning >}}

Parent binaries filter provides filtering based on current process parent
binary path, which works similarly to the `matchBinaries` filter. It can be
specified with the `matchParentBinaries` field. For instance, the following
`matchParentBinaries` selector will match only if binary `cat` was executed
from interactive shell like `zsh`, `bash`, `sh`:

```yaml
- matchParentBinaries:
- operator: "In"
values:
- "/usr/bin/bash"
- "/usr/bin/sh"
- "/usr/bin/zsh"
matchBinaries:
- operator: "In"
values:
- "/usr/bin/cat"
```

The available operators for `matchParentBinaries` are:
- `In`
- `NotIn`
- `Prefix`
- `NotPrefix`
- `Postfix`
- `NotPostfix`

The `values` field has to be a map of `strings`. The default behaviour
is `followForks: true`, so all the child processes are followed.

### Follow children

The `matchParentBinaries` filter can be configured to also apply to children of
matching parent processes. To do this, set `followChildren` to `true`. For example:

```yaml
- matchParentBinaries:
- operator: "In"
values:
- "/usr/bin/bash"
followChildren: true
```

This policy will match any process, which direct or transitive parent process binary is `bash`.

There are a number of limitations when using `followChildren`:
- Children created before the policy was installed will not be matched.
- The number of `matchParentBinaries` sections with `followChildren: true` cannot exceed 64.
- Operators other than `In/NotIn` are not supported.

## Namespaces filter

Namespaces filters can be specified under the `matchNamespaces` field and
Expand Down
Loading
Loading