Skip to content

Commit 634c6b7

Browse files
committed
ROX-30714: use cgroup id for container id resolution
This approach is based on using the cgroup id of processes to resolve the container ID in userspace. In order to achieve this, a LRU BPF map is used to keep a mapping of cgroup id to cgroup "path", send the cgroup id as part of file events and parsing the container id by directly accessing the map from userspace. The LRU map is filled from userspace at startup and then kept up to date by a BPF program on the cgroup_attach_task tracepoint, inserting new cgroup ids as we find them.
1 parent b15c76d commit 634c6b7

File tree

14 files changed

+442
-229
lines changed

14 files changed

+442
-229
lines changed

fact-ebpf/src/bpf/builtins.h

Lines changed: 0 additions & 7 deletions
This file was deleted.

fact-ebpf/src/bpf/main.c

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#include "file.h"
55
#include "types.h"
6-
#include "process.h"
76
#include "maps.h"
87
#include "events.h"
98
#include "bound_path.h"
@@ -103,3 +102,38 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
103102
m->path_unlink.error++;
104103
return 0;
105104
}
105+
106+
SEC("tp_btf/cgroup_attach_task")
107+
int BPF_PROG(trace_cgroup_attach_task, struct cgroup* dst_cgrp, const char* path, struct task_struct* _task, bool _threadgroup) {
108+
struct metrics_t* m = get_metrics();
109+
if (m == NULL) {
110+
bpf_printk("Failed to get metrics entry");
111+
return 0;
112+
}
113+
114+
m->cgroup_attach_task.total++;
115+
116+
u64 id = dst_cgrp->kn->id;
117+
if (bpf_map_lookup_elem(&cgroup_map, &id) != NULL) {
118+
// Already have the entry
119+
m->cgroup_attach_task.ignored++;
120+
return 0;
121+
}
122+
123+
struct helper_t* helper = get_helper();
124+
if (helper == NULL) {
125+
bpf_printk("Failed to get helper entry");
126+
m->cgroup_attach_task.error++;
127+
return 0;
128+
}
129+
130+
bpf_core_read_str(helper->cgroup_entry.path, PATH_MAX, path);
131+
helper->cgroup_entry.parsed = false;
132+
int res = bpf_map_update_elem(&cgroup_map, &id, &helper->cgroup_entry, BPF_NOEXIST);
133+
if (res != 0) {
134+
bpf_printk("Failed to update path for %d", id);
135+
m->cgroup_attach_task.error++;
136+
}
137+
138+
return 0;
139+
}

fact-ebpf/src/bpf/maps.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
* Helper struct with buffers for various operations
1212
*/
1313
struct helper_t {
14-
char buf[PATH_MAX * 2];
15-
const unsigned char* array[16];
14+
union {
15+
cgroup_entry_t cgroup_entry;
16+
char buf[PATH_MAX * 2];
17+
};
1618
};
1719

1820
struct {
@@ -104,6 +106,13 @@ __always_inline static struct metrics_t* get_metrics() {
104106
return bpf_map_lookup_elem(&metrics, &zero);
105107
}
106108

109+
struct {
110+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
111+
__type(key, __u64);
112+
__type(value, cgroup_entry_t);
113+
__uint(max_entries, (2^16)-1);
114+
} cgroup_map SEC(".maps");
115+
107116
uint64_t host_mount_ns;
108117
volatile const bool path_unlink_supports_bpf_d_path;
109118

fact-ebpf/src/bpf/process.h

Lines changed: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -11,72 +11,6 @@
1111
#include <bpf/bpf_core_read.h>
1212
// clang-format on
1313

14-
__always_inline static const char* get_memory_cgroup(struct helper_t* helper) {
15-
if (!bpf_core_enum_value_exists(enum cgroup_subsys_id, memory_cgrp_id)) {
16-
return NULL;
17-
}
18-
19-
struct task_struct* task = (struct task_struct*)bpf_get_current_task();
20-
21-
// We're guessing which cgroup controllers are enabled for this task. The
22-
// assumption is that memory controller is present more often than
23-
// cpu & cpuacct.
24-
struct kernfs_node* kn = BPF_CORE_READ(task, cgroups, subsys[memory_cgrp_id], cgroup, kn);
25-
if (kn == NULL) {
26-
return NULL;
27-
}
28-
29-
int i = 0;
30-
for (; i < 16; i++) {
31-
helper->array[i] = (const unsigned char*)BPF_CORE_READ(kn, name);
32-
if (bpf_core_field_exists(kn->__parent)) {
33-
kn = BPF_CORE_READ(kn, __parent);
34-
} else {
35-
struct kernfs_node___pre6_15 {
36-
struct kernfs_node* parent;
37-
};
38-
struct kernfs_node___pre6_15* kn_old = (void*)kn;
39-
kn = BPF_CORE_READ(kn_old, parent);
40-
}
41-
if (kn == NULL) {
42-
break;
43-
}
44-
}
45-
46-
if (i == 16) {
47-
i--;
48-
}
49-
50-
int offset = 0;
51-
for (; i >= 0 && offset < PATH_MAX; i--) {
52-
// Skip empty directories
53-
if (helper->array[i] == NULL) {
54-
continue;
55-
}
56-
57-
helper->buf[offset & (PATH_MAX - 1)] = '/';
58-
if (++offset >= PATH_MAX) {
59-
return NULL;
60-
}
61-
62-
int len = bpf_probe_read_kernel_str(&helper->buf[offset & (PATH_MAX - 1)], PATH_MAX, helper->array[i]);
63-
if (len < 0) {
64-
// We should have skipped all empty entries, any other error is a genuine
65-
// problem, stop processing.
66-
return NULL;
67-
}
68-
69-
if (len == 1) {
70-
offset--;
71-
continue;
72-
}
73-
74-
offset += len - 1;
75-
}
76-
77-
return helper->buf;
78-
}
79-
8014
__always_inline static void process_fill_lineage(process_t* p, struct helper_t* helper, bool use_bpf_d_path) {
8115
struct task_struct* task = (struct task_struct*)bpf_get_current_task_btf();
8216
p->lineage_len = 0;
@@ -109,6 +43,7 @@ __always_inline static int64_t process_fill(process_t* p, bool use_bpf_d_path) {
10943
p->gid = (uid_gid >> 32) & 0xFFFFFFFF;
11044
p->login_uid = task->loginuid.val;
11145
p->pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF;
46+
p->cgroup_id = bpf_get_current_cgroup_id();
11247
u_int64_t err = bpf_get_current_comm(p->comm, TASK_COMM_LEN);
11348
if (err != 0) {
11449
bpf_printk("Failed to fill task comm");
@@ -133,11 +68,6 @@ __always_inline static int64_t process_fill(process_t* p, bool use_bpf_d_path) {
13368

13469
d_path(&task->mm->exe_file->f_path, p->exe_path, PATH_MAX, use_bpf_d_path);
13570

136-
const char* cg = get_memory_cgroup(helper);
137-
if (cg != NULL) {
138-
bpf_probe_read_str(p->memory_cgroup, PATH_MAX, cg);
139-
}
140-
14171
p->in_root_mount_ns = get_mount_ns() == host_mount_ns;
14272

14373
process_fill_lineage(p, helper, use_bpf_d_path);

fact-ebpf/src/bpf/types.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ typedef struct process_t {
2222
char args[4096];
2323
unsigned int args_len;
2424
char exe_path[PATH_MAX];
25-
char memory_cgroup[PATH_MAX];
25+
unsigned long long cgroup_id;
2626
unsigned int uid;
2727
unsigned int gid;
2828
unsigned int login_uid;
@@ -73,4 +73,10 @@ struct metrics_by_hook_t {
7373
struct metrics_t {
7474
struct metrics_by_hook_t file_open;
7575
struct metrics_by_hook_t path_unlink;
76+
struct metrics_by_hook_t cgroup_attach_task;
7677
};
78+
79+
typedef struct cgroup_entry_t {
80+
char parsed;
81+
char path[PATH_MAX];
82+
} cgroup_entry_t;

fact-ebpf/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,13 @@ impl metrics_t {
8181
let mut m = metrics_t { ..*self };
8282
m.file_open = m.file_open.accumulate(&other.file_open);
8383
m.path_unlink = m.path_unlink.accumulate(&other.path_unlink);
84+
m.cgroup_attach_task = m.cgroup_attach_task.accumulate(&other.cgroup_attach_task);
8485
m
8586
}
8687
}
8788

8889
unsafe impl Pod for metrics_t {}
90+
unsafe impl Pod for cgroup_entry_t {}
8991

9092
pub const EBPF_OBJ: &[u8] = aya::include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.o"));
9193
pub const CHECKS_OBJ: &[u8] = aya::include_bytes_aligned!(concat!(env!("OUT_DIR"), "/checks.o"));

fact/src/bpf/mod.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{io, path::PathBuf, sync::Arc};
33
use anyhow::{bail, Context};
44
use aya::{
55
maps::{Array, LpmTrie, MapData, PerCpuArray, RingBuf},
6-
programs::Lsm,
6+
programs::Program,
77
Btf, Ebpf,
88
};
99
use checks::Checks;
@@ -15,9 +15,13 @@ use tokio::{
1515
task::JoinHandle,
1616
};
1717

18-
use crate::{event::Event, host_info, metrics::EventCounter};
18+
use crate::{
19+
event::{self, Event},
20+
host_info,
21+
metrics::EventCounter,
22+
};
1923

20-
use fact_ebpf::{event_t, metrics_t, path_prefix_t, LPM_SIZE_MAX};
24+
use fact_ebpf::{cgroup_entry_t, event_t, metrics_t, path_prefix_t, LPM_SIZE_MAX};
2125

2226
mod checks;
2327

@@ -30,6 +34,8 @@ pub struct Bpf {
3034

3135
paths: Vec<path_prefix_t>,
3236
paths_config: watch::Receiver<Vec<PathBuf>>,
37+
38+
event_parser: event::parser::Parser,
3339
}
3440

3541
impl Bpf {
@@ -44,7 +50,7 @@ impl Bpf {
4450

4551
// Include the BPF object as raw bytes at compile-time and load it
4652
// at runtime.
47-
let obj = aya::EbpfLoader::new()
53+
let mut obj = aya::EbpfLoader::new()
4854
.set_global("host_mount_ns", &host_info::get_host_mount_ns(), true)
4955
.set_global(
5056
"path_unlink_supports_bpf_d_path",
@@ -56,11 +62,17 @@ impl Bpf {
5662

5763
let paths = Vec::new();
5864
let (tx, _) = broadcast::channel(100);
65+
let Some(cgroup_map) = obj.take_map("cgroup_map") else {
66+
bail!("Failed to get cgroup_map");
67+
};
68+
let cgroup_map: aya::maps::HashMap<MapData, u64, cgroup_entry_t> = cgroup_map.try_into()?;
69+
let event_parser = event::parser::Parser::new(cgroup_map);
5970
let mut bpf = Bpf {
6071
obj,
6172
tx,
6273
paths,
6374
paths_config,
75+
event_parser,
6476
};
6577

6678
bpf.load_paths()?;
@@ -138,24 +150,42 @@ impl Bpf {
138150
Ok(())
139151
}
140152

141-
fn load_lsm_prog(&mut self, name: &str, hook: &str, btf: &Btf) -> anyhow::Result<()> {
153+
fn load_prog(&mut self, name: &str, hook: &str, btf: &Btf) -> anyhow::Result<()> {
142154
let Some(prog) = self.obj.program_mut(name) else {
143155
bail!("{name} program not found");
144156
};
145-
let prog: &mut Lsm = prog.try_into()?;
146-
prog.load(hook, btf)?;
157+
match prog {
158+
Program::Lsm(prog) => prog.load(hook, btf)?,
159+
Program::BtfTracePoint(prog) => prog.load(hook, btf)?,
160+
_ => todo!(),
161+
}
147162
Ok(())
148163
}
149164

150165
fn load_progs(&mut self, btf: &Btf) -> anyhow::Result<()> {
151-
self.load_lsm_prog("trace_file_open", "file_open", btf)?;
152-
self.load_lsm_prog("trace_path_unlink", "path_unlink", btf)
166+
let progs = [
167+
("trace_file_open", "file_open"),
168+
("trace_path_unlink", "path_unlink"),
169+
("trace_cgroup_attach_task", "cgroup_attach_task"),
170+
];
171+
172+
for (name, hook) in progs {
173+
self.load_prog(name, hook, btf)?;
174+
}
175+
Ok(())
153176
}
154177

155178
fn attach_progs(&mut self) -> anyhow::Result<()> {
156179
for (_, prog) in self.obj.programs_mut() {
157-
let prog: &mut Lsm = prog.try_into()?;
158-
prog.attach()?;
180+
match prog {
181+
Program::Lsm(prog) => {
182+
prog.attach()?;
183+
}
184+
Program::BtfTracePoint(prog) => {
185+
prog.attach()?;
186+
}
187+
_ => todo!(),
188+
}
159189
}
160190
Ok(())
161191
}
@@ -165,8 +195,10 @@ impl Bpf {
165195
mut self,
166196
mut running: watch::Receiver<bool>,
167197
event_counter: EventCounter,
198+
parser_counter: EventCounter,
168199
) -> JoinHandle<anyhow::Result<()>> {
169200
info!("Starting BPF worker...");
201+
self.event_parser.set_metrics(parser_counter);
170202

171203
tokio::spawn(async move {
172204
self.attach_progs()
@@ -183,7 +215,7 @@ impl Bpf {
183215
let ringbuf = guard.get_inner_mut();
184216
while let Some(event) = ringbuf.next() {
185217
let event: &event_t = unsafe { &*(event.as_ptr() as *const _) };
186-
let event = match Event::try_from(event) {
218+
let event = match self.event_parser.parse(event) {
187219
Ok(event) => Arc::new(event),
188220
Err(e) => {
189221
error!("Failed to parse event: '{e}'");
@@ -268,7 +300,11 @@ mod bpf_tests {
268300
// Create a metrics exporter, but don't start it
269301
let exporter = Exporter::new(bpf.take_metrics().unwrap());
270302

271-
let handle = bpf.start(run_rx, exporter.metrics.bpf_worker.clone());
303+
let handle = bpf.start(
304+
run_rx,
305+
exporter.metrics.bpf_worker.clone(),
306+
exporter.metrics.event_parser.clone(),
307+
);
272308

273309
tokio::time::sleep(Duration::from_millis(500)).await;
274310

@@ -277,7 +313,7 @@ mod bpf_tests {
277313
NamedTempFile::new_in(monitored_path).expect("Failed to create temporary file");
278314
println!("Created {file:?}");
279315

280-
let expected = Event::new(
316+
let expected = Event::from_raw_parts(
281317
file_activity_type_t::FILE_ACTIVITY_CREATION,
282318
host_info::get_hostname(),
283319
file.path().to_path_buf(),

0 commit comments

Comments
 (0)