Skip to content

Commit 22145c5

Browse files
committed
feat: variable event length
Change from a fixed type for events from BPF to a variable length one. This is done by using a helper map in which we can write all the information for a given event, then submit it to the ringbuffer with the bpf_ringbuf_output helper. On the userspace, we get a RingBufItem which can be treated as a slice of bytes we can split to retrieve fields one by one. This change needs to be properly tested for performance, we should use a lot less space for each event in the ringbuffer but serializing and deserializing each event might require more CPU. TODO: Document the format for the serialized event in the ringbuffer.
1 parent 535fd8c commit 22145c5

File tree

10 files changed

+919
-182
lines changed

10 files changed

+919
-182
lines changed

fact-ebpf/src/bpf/events.h

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,83 @@
33
// clang-format off
44
#include "vmlinux.h"
55

6-
#include "inode.h"
76
#include "maps.h"
87
#include "process.h"
98
#include "types.h"
9+
#include "raw_event.h"
1010

1111
#include <bpf/bpf_helpers.h>
1212
// clang-format on
1313

14+
/**
15+
* Format and submit an event to the ringbuffer.
16+
*
17+
* This method is responsible for using the provided values from
18+
* different BPF programs, serialize this data alongside the current
19+
* process information in a binary format and submit it as an event to
20+
* the ringbuffer.
21+
*
22+
* The high level format for an event can be described as follows:
23+
* |--|--------|---------------------------|---------------------------|
24+
* | | | | ^ event end
25+
* | | | ^ begin file data
26+
* | | ^ begin process data
27+
* | ^ timestamp
28+
* ^ event type
29+
*
30+
* Event type: a 16 bit integer specifying the type of event this is.
31+
* Timestamp: the amount of nano seconds since boot time.
32+
* Process data: all the information collected from the current process.
33+
* For more information on this field see the documentation for
34+
* `process_fill`.
35+
* File data: information collected about the file being acted upon.
36+
*
37+
* The file data field can be expanded as follows:
38+
* |----|--------------|---|
39+
* | | ^ Event specific data
40+
* | ^ file path
41+
* ^ inode information
42+
*
43+
* Inode information: Encoded as the inode and device numbers. Used for
44+
* host path tracking.
45+
* File path: The path to the file being acted upon, retrieved from
46+
* d_path.
47+
*/
1448
__always_inline static void submit_event(struct metrics_by_hook_t* m,
1549
file_activity_type_t event_type,
1650
struct bound_path_t* path,
1751
inode_key_t* inode,
1852
bool use_bpf_d_path) {
19-
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
20-
if (event == NULL) {
21-
m->ringbuffer_full++;
53+
unsigned int zero = 0;
54+
struct raw_event_t raw_event = {
55+
.buf = bpf_map_lookup_elem(&heap_map, &zero),
56+
.len = 0,
57+
};
58+
if (raw_event.buf == NULL) {
59+
m->error++;
2260
return;
2361
}
2462

25-
event->type = event_type;
26-
event->timestamp = bpf_ktime_get_boot_ns();
27-
inode_copy_or_reset(&event->inode, inode);
28-
bpf_probe_read(event->filename, path->len & (PATH_MAX - 1), path->path);
29-
event->filename_len = path->len;
30-
31-
struct helper_t* helper = get_helper();
32-
if (helper == NULL) {
33-
goto error;
34-
}
63+
raw_event_copy_u16(&raw_event, event_type);
64+
raw_event_copy_uint(&raw_event, bpf_ktime_get_boot_ns());
3565

36-
int64_t err = process_fill(&event->process, use_bpf_d_path);
66+
int64_t err = process_fill(&raw_event, use_bpf_d_path);
3767
if (err) {
3868
bpf_printk("Failed to fill process information: %d", err);
3969
goto error;
4070
}
4171

72+
// File data
73+
raw_event_copy_inode(&raw_event, inode);
74+
raw_event_copy_bound_path(&raw_event, path);
75+
76+
if (bpf_ringbuf_output(&rb, raw_event.buf, raw_event.len, 0) != 0) {
77+
m->ringbuffer_full++;
78+
return;
79+
}
4280
m->added++;
43-
bpf_ringbuf_submit(event, 0);
4481
return;
4582

4683
error:
4784
m->error++;
48-
bpf_ringbuf_discard(event, 0);
4985
}

fact-ebpf/src/bpf/maps.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77

88
#include <bpf/bpf_helpers.h>
99

10+
#define MAX_EVENT_LEN ((1<< 15) - 1)
11+
/**
12+
* Raw buffer to encode events into prior to submitting to the
13+
* ringbuffer
14+
*/
15+
struct {
16+
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
17+
__type(key, __u32);
18+
__type(value, char[MAX_EVENT_LEN]);
19+
__uint(max_entries, 1);
20+
} heap_map SEC(".maps");
21+
1022
/**
1123
* Helper struct with buffers for various operations
1224
*/

fact-ebpf/src/bpf/process.h

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "d_path.h"
77
#include "maps.h"
88
#include "types.h"
9+
#include "raw_event.h"
910

1011
#include <bpf/bpf_helpers.h>
1112
#include <bpf/bpf_core_read.h>
@@ -77,52 +78,90 @@ __always_inline static const char* get_memory_cgroup(struct helper_t* helper) {
7778
return helper->buf;
7879
}
7980

80-
__always_inline static void process_fill_lineage(process_t* p, struct helper_t* helper, bool use_bpf_d_path) {
81+
__always_inline static long process_fill_lineage(struct raw_event_t* event, struct helper_t* helper, bool use_bpf_d_path) {
8182
struct task_struct* task = (struct task_struct*)bpf_get_current_task_btf();
82-
p->lineage_len = 0;
83+
uint16_t lineage_len_pos = event->len;
84+
event->len += 2;
8385

84-
for (int i = 0; i < LINEAGE_MAX; i++) {
86+
uint16_t i = 0;
87+
for (; i < LINEAGE_MAX; i++) {
8588
struct task_struct* parent = task->real_parent;
8689

8790
if (task == parent || parent->pid == 0) {
88-
return;
91+
break;
8992
}
9093
task = parent;
9194

92-
p->lineage[i].uid = task->cred->uid.val;
93-
94-
d_path(&task->mm->exe_file->f_path, p->lineage[i].exe_path, PATH_MAX, use_bpf_d_path);
95-
p->lineage_len++;
95+
raw_event_copy_uint(event, task->cred->uid.val);
96+
long err = raw_event_d_path(event, &task->mm->exe_file->f_path, use_bpf_d_path);
97+
if (err != 0) {
98+
bpf_printk("Failed to read lineage exe_path");
99+
return err;
100+
}
96101
}
102+
103+
// go back and set the amount of lineage processes in the buffer
104+
uint16_t back = event->len;
105+
event->len = lineage_len_pos;
106+
107+
raw_event_copy_uint(event, i);
108+
109+
event->len = back;
110+
return 0;
97111
}
98112

99113
__always_inline static unsigned long get_mount_ns() {
100114
struct task_struct* task = (struct task_struct*)bpf_get_current_task_btf();
101115
return task->nsproxy->mnt_ns->ns.inum;
102116
}
103117

104-
__always_inline static int64_t process_fill(process_t* p, bool use_bpf_d_path) {
118+
/**
119+
* Fill in the information about the current process to the event
120+
* buffer.
121+
*
122+
* This method serializes all required process information for the event
123+
* as a binary blob into the provided event buffer. The serialized data
124+
* will look something like this:
125+
* |--|--|--|--|-------|--------------|-------------|------------|-|----|---|
126+
* | | | | | | | | | | ^ grandparent lineage
127+
* | | | | | | | | | ^ parent lineage
128+
* | | | | | | | | ^ in_root_mount_ns
129+
* | | | | | | | ^ cgroup
130+
* | | | | | | ^ executable path
131+
* | | | | | ^ arguments
132+
* | | | | ^ comm
133+
* | | | ^ pid
134+
* | | ^ loginuid
135+
* | ^ gid
136+
* ^ uid
137+
*/
138+
__always_inline static int64_t process_fill(struct raw_event_t* event, bool use_bpf_d_path) {
105139
struct task_struct* task = (struct task_struct*)bpf_get_current_task_btf();
106140
uint32_t key = 0;
107141
uint64_t uid_gid = bpf_get_current_uid_gid();
108-
p->uid = uid_gid & 0xFFFFFFFF;
109-
p->gid = (uid_gid >> 32) & 0xFFFFFFFF;
110-
p->login_uid = task->loginuid.val;
111-
p->pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF;
112-
u_int64_t err = bpf_get_current_comm(p->comm, TASK_COMM_LEN);
142+
raw_event_copy_u32(event, uid_gid & 0xFFFFFFFF);
143+
raw_event_copy_u32(event, ((uid_gid >> 32) & 0xFFFFFFFF));
144+
raw_event_copy_uint(event, task->loginuid.val);
145+
raw_event_copy_u32(event, (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF);
146+
uint64_t err = raw_event_copy_comm(event);
113147
if (err != 0) {
114148
bpf_printk("Failed to fill task comm");
115149
return err;
116150
}
117151

118152
unsigned long arg_start = task->mm->arg_start;
119153
unsigned long arg_end = task->mm->arg_end;
120-
p->args_len = (arg_end - arg_start) & 0xFFF;
121-
p->args[4095] = '\0'; // Ensure string termination at end of buffer
122-
err = bpf_probe_read_user(p->args, p->args_len, (const char*)arg_start);
123-
if (err != 0) {
124-
bpf_printk("Failed to fill task args");
125-
return err;
154+
uint16_t args_len = (arg_end - arg_start) & 0xFFF;
155+
err = raw_event_copy_buffer(event, (const void*)arg_start, args_len);
156+
if (err < 0) {
157+
bpf_printk("Failed to read process args");
158+
return -1;
159+
}
160+
161+
err = raw_event_d_path(event, &task->mm->exe_file->f_path, use_bpf_d_path);
162+
if (err < 0) {
163+
bpf_printk("Failed to read exe_path");
164+
return -1;
126165
}
127166

128167
struct helper_t* helper = bpf_map_lookup_elem(&helper_map, &key);
@@ -131,16 +170,26 @@ __always_inline static int64_t process_fill(process_t* p, bool use_bpf_d_path) {
131170
return -1;
132171
}
133172

134-
p->exe_path_len = d_path(&task->mm->exe_file->f_path, p->exe_path, PATH_MAX, use_bpf_d_path);
135-
136173
const char* cg = get_memory_cgroup(helper);
137174
if (cg != NULL) {
138-
bpf_probe_read_str(p->memory_cgroup, PATH_MAX, cg);
175+
// Reserve space for the cgroup length
176+
event->len += 2;
177+
uint16_t cg_len = (uint16_t)bpf_probe_read_str(&event->buf[event->len], PATH_MAX, cg);
178+
179+
// Move back and fix the length
180+
event->len -= 2;
181+
raw_event_copy_u16(event, cg_len - 1);
182+
183+
// Forward past the cgroup
184+
event->len += ((cg_len - 1) & (PATH_MAX - 1));
139185
}
140186

141-
p->in_root_mount_ns = get_mount_ns() == host_mount_ns;
187+
raw_event_copy_u8(event, get_mount_ns() == host_mount_ns);
142188

143-
process_fill_lineage(p, helper, use_bpf_d_path);
189+
err = process_fill_lineage(event, helper, use_bpf_d_path);
190+
if (err < 0) {
191+
return -1;
192+
}
144193

145194
return 0;
146195
}

0 commit comments

Comments
 (0)