Skip to content

Commit

Permalink
new(ebpf): use longer iterations when bpf_loop available
Browse files Browse the repository at this point in the history
The LOOP macro now takes an extra `max_unroll` parameter, which defines
the maximum loop unroll when bpf_loop is missing.
This allows in `get_path_str` to have a MAX_PATH_COMPONENTS of 100, but
unroll the loop only up to 20, since a bigger value would be rejected by
the verifier.
  • Loading branch information
MatteoNardi committed Apr 5, 2023
1 parent 53373e3 commit 7097fd5
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 25 deletions.
76 changes: 55 additions & 21 deletions crates/bpf-builder/include/get_path.bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,26 @@
// };
//

#define MAX_PATH_COMPONENTS 20
#define MAX_PATH_COMPONENTS 100
#define MAX_PATH_UNROLL 20

// Array of path components which is kept inside `struct get_path_ctx`.
// bpf_loop requires `get_path_ctx` to be a stack variable, but that prevents
// us to store MAX_PATH_COMPONENTS items inside, as that's more than the allowed
// stack space for eBPF programs. For this reason we allocate them on a
// temporary PERCPU_ARRAY map.
struct components {
u32 total_components;
const unsigned char *component_name[MAX_PATH_COMPONENTS];
u32 component_len[MAX_PATH_COMPONENTS];
};

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct components);
__uint(max_entries, 1);
} components_map SEC(".maps");

struct get_path_ctx {
// Output of get_path_str
Expand All @@ -52,26 +71,35 @@ struct get_path_ctx {
struct path current;

// Internal list of path components, from dentry to the root
const unsigned char *component_name[MAX_PATH_COMPONENTS];
u32 component_len[MAX_PATH_COMPONENTS];
struct components *components;
};

static __always_inline long get_dentry_name(u32 i, void *callback_ctx) {
struct get_path_ctx *c = callback_ctx;
// NOTE: we need to store fields in temporary variables because a call like
// the the following results in a relocation error in kernel 5.5 x86:
// BPF_CORE_READ(c->current.dentry, d_parent);
// > applying relocation `FieldByteOffset` missing target BTF info for type
// `909` at instruction #472
// This happens only in `sched_process_exec`, which uses raw tracepoints,
// while it doesn't happen in file-system-monitor, which uses lsm/kprobe
// programs.
struct dentry *current_dentry = c->current.dentry;
struct vfsmount *current_mnt = c->current.mnt;
struct dentry *parent_dentry = BPF_CORE_READ(c->current.dentry, d_parent);

// Get root dentry of the file-system mount point.
struct dentry *root_dentry = BPF_CORE_READ(c->current.mnt, mnt_root);
struct dentry *root_dentry = BPF_CORE_READ(current_mnt, mnt_root);

// If a dentry is the parent of itself, we should have reached to root.
// The topmost dentry must match root_dentry.
if (c->current.dentry == parent_dentry && c->current.dentry != root_dentry) {
if (current_dentry == parent_dentry && current_dentry != root_dentry) {
LOG_ERROR("We reached root, but not mount root");
return LOOP_STOP;
}

// If we've successfully reached the top of our file-system
if (c->current.dentry == root_dentry) {
if (current_dentry == root_dentry) {
struct mount *current_mount =
container_of(c->current.mnt, struct mount, mnt);
struct mount *parent_mount = BPF_CORE_READ(current_mount, mnt_parent);
Expand All @@ -89,10 +117,12 @@ static __always_inline long get_dentry_name(u32 i, void *callback_ctx) {
}

// Add this dentry name to path
struct qstr entry = BPF_CORE_READ(c->current.dentry, d_name);
if (i < MAX_PATH_COMPONENTS) {
c->component_len[i] = entry.len;
c->component_name[i] = entry.name;
struct qstr entry = BPF_CORE_READ(current_dentry, d_name);
u32 next = c->components->total_components;
if (next < MAX_PATH_COMPONENTS) {
c->components->component_len[next] = entry.len;
c->components->component_name[next] = entry.name;
c->components->total_components++;
}
c->current.dentry = parent_dentry;
return LOOP_CONTINUE;
Expand All @@ -103,13 +133,11 @@ static __always_inline long get_dentry_name(u32 i, void *callback_ctx) {
// because the first component will always be the initial dentry.
static __always_inline long append_path_component(u32 i, void *callback_ctx) {
struct get_path_ctx *c = callback_ctx;
int t = MAX_PATH_COMPONENTS - i - 1;
int t = c->components->total_components - i - 1;
if (t < 0 || t >= MAX_PATH_COMPONENTS)
return LOOP_STOP;
char *name = (char *)c->component_name[t];
int len = c->component_len[t];
if (len == 0)
return LOOP_CONTINUE;
char *name = (char *)c->components->component_name[t];
int len = c->components->component_len[t];
buffer_append_str(c->buffer, c->index, "/", 1);
buffer_append_str(c->buffer, c->index, name, len);
return LOOP_CONTINUE;
Expand All @@ -118,16 +146,22 @@ static __always_inline long append_path_component(u32 i, void *callback_ctx) {
// Copy to buffer/index the path of the file pointed by dentry/path.
static void get_path_str(struct path *path, struct buffer *buffer,
struct buffer_index *index) {

u32 key = 0;
struct get_path_ctx c;
struct components *components = bpf_map_lookup_elem(&components_map, &key);
if (!components) {
LOG_ERROR("can't get context memory");
return;
}
c.components = components;
components->total_components = 0;
c.index = index;
c.buffer = buffer;
__builtin_memset(c.component_name, 0, sizeof(c.component_name));
__builtin_memset(c.component_len, 0, sizeof(c.component_len));
c.current.dentry = path->dentry;
c.current.mnt = path->mnt;
buffer_index_init(buffer, index);
LOOP(MAX_PATH_COMPONENTS, get_dentry_name, &c);
LOOP(MAX_PATH_COMPONENTS, append_path_component, &c);
return;
// TODO: use bpf_d_path when available
LOOP(MAX_PATH_COMPONENTS, MAX_PATH_UNROLL, get_dentry_name, &c);
LOOP(MAX_PATH_COMPONENTS, MAX_PATH_UNROLL, append_path_component, &c);
// TODO: use bpf_d_path when available
}
8 changes: 4 additions & 4 deletions crates/bpf-builder/include/loop.bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
#ifdef FEATURE_NO_FN_POINTERS
// On kernel <= 5.13 taking the address of a function results in a verifier
// error, even if inside a dead-code elimination branch.
#define LOOP(max_iterations, callback_fn, ctx) \
_Pragma("unroll") for (int i = 0; i < max_iterations; i++) { \
#define LOOP(max_iterations, max_unroll, callback_fn, ctx) \
_Pragma("unroll") for (int i = 0; i < 10; i++) { \
if (callback_fn(i, ctx) == LOOP_STOP) \
break; \
}
#else
#define LOOP(max_iterations, callback_fn, ctx) \
#define LOOP(max_iterations, max_unroll, callback_fn, ctx) \
if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 17, 0)) { \
bpf_loop(max_iterations, callback_fn, ctx, 0); \
} else { \
_Pragma("unroll") for (int i = 0; i < max_iterations; i++) { \
_Pragma("unroll") for (int i = 0; i < max_unroll; i++) { \
if (callback_fn(i, ctx) == LOOP_STOP) \
break; \
} \
Expand Down

0 comments on commit 7097fd5

Please sign in to comment.