Skip to content

Commit cb72a11

Browse files
committed
Add cgroup v1/v2 detection and fix multi-CPU map update races
Introduce a `counter_cfg` eBPF map to pass the cgroup filesystem magic from userland to eBPF programs, enabling `get_current_cgroup_id()` to select the correct cgroup ID retrieval path (v2 via bpf helper vs. v1 via task_struct walk). Detect cgroup v1, v2 and hybrid mode in `getCgroupFsMagic()` and push the magic value at startup for KProbes and CGroup SKB modes. Consolidate `get_cgroupid()` into `counter_common.h` (renamed to `get_cgroup_id()`) so it is shared across all BPF programs; update `cgroup.bpf.c` to include `counter_common.h` instead of `cgroup.h`. Fix a multi-CPU TOCTOU race in `update_val()` and the cgroup_skb path: when `BPF_NOEXIST` insert fails (concurrent insert on another CPU), retry the lookup and increment atomically with `__sync_fetch_and_add` to avoid silently dropping packets. Scope interface resolution to only TC and XDP modes, avoiding a spurious fatal error when running in KProbes or CGroup mode without a meaningful `--iface` argument. Fix `icmpv6_rcv` kprobe to guard `update_val()` with `msglen > 0`, matching the convention used by all other probes. Fix cgroup path mapping to emit "/" instead of an empty string when the cgroup root itself is the target. Add a TUI race fix: check the `done` channel after `processMap` returns to avoid queuing a draw after the app has been stopped.
1 parent 08be4d0 commit cb72a11

30 files changed

+435
-97
lines changed

bpf/cgroup.bpf.c

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#include <bpf/bpf_helpers.h>
2929
#include <bpf/bpf_tracing.h>
3030

31-
#include "cgroup.h"
31+
#include "counter_common.h"
3232

3333
typedef struct cgroup_event_t {
3434
char path[PATH_MAX]; // cgroup path
@@ -50,52 +50,6 @@ struct {
5050
__type(value, __u32);
5151
} perf_cgroup_event SEC(".maps");
5252

53-
/**
54-
* get_cgroupid - reads the cgroup ID from the given struct cgroup
55-
*
56-
* This function reads the cgroup ID from the given struct cgroup and returns
57-
* it. The function works on kernels v4.10 and above.
58-
*
59-
* @cgrp: the struct cgroup to read the cgroup ID from
60-
*
61-
* Returns: the cgroup ID as an unsigned 64-bit integer
62-
*
63-
* get_cgroupid() comes from aquasecurity/tracee, license: Apache-2.0
64-
*/
65-
static inline __attribute__((always_inline)) __u64
66-
get_cgroupid(struct cgroup *cgrp) {
67-
struct kernfs_node *kn = BPF_CORE_READ(cgrp, kn);
68-
69-
if (kn == NULL)
70-
return 0;
71-
72-
__u64 id; // was union kernfs_node_id before 5.5, can read it as u64 in both
73-
// situations
74-
75-
if (bpf_core_type_exists(union kernfs_node_id)) {
76-
struct kernfs_node___older_v55 *kn_old = (void *)kn;
77-
struct kernfs_node___rh8 *kn_rh8 = (void *)kn;
78-
79-
if (bpf_core_field_exists(kn_rh8->id)) {
80-
// RHEL8 has both types declared: union and u64:
81-
// kn->id
82-
// rh->rh_kabi_hidden_172->id
83-
// pointing to the same data
84-
bpf_core_read(&id, sizeof(__u64), &kn_rh8->id);
85-
id = id & 0xffffffff; // XXX: u32 is required
86-
} else {
87-
// all other regular kernels below v5.5
88-
bpf_core_read(&id, sizeof(__u64), &kn_old->id);
89-
id = id & 0xffffffff; // XXX: u32 is required
90-
}
91-
} else {
92-
// kernel v5.5 and above
93-
bpf_core_read(&id, sizeof(__u64), &kn->id);
94-
}
95-
96-
return id;
97-
}
98-
9953
/**
10054
* trace_cgroup_mkdir traces the creation of a new cgroup directory.
10155
*
@@ -116,7 +70,7 @@ int trace_cgroup_mkdir(struct bpf_raw_tracepoint_args *ctx) {
11670
struct cgroup *dst_cgrp = (struct cgroup *)ctx->args[0];
11771
char *path = (char *)ctx->args[1];
11872

119-
__u64 cgroupid = get_cgroupid(dst_cgrp);
73+
__u64 cgroupid = get_cgroup_id(dst_cgrp);
12074
__u32 zero_key = 0;
12175

12276
cgroupevent *val =

bpf/cgroup.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
//go:build ignore
2323

24-
#define MAX_ENTRIES 1024
24+
#pragma once
25+
2526
#define PATH_MAX 4096
27+
#define CGROUP_FSMAGIC 0x27e0eb // cgroup v1
28+
#define CGROUP2_FSMAGIC 0x63677270 // cgroup v2
2629

2730
union kernfs_node_id {
2831
struct {

bpf/cgroup_skb.bpf.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ process_cgroup_skb(struct __sk_buff *skb) {
9494
__builtin_memcpy(key.comm, ski->comm, sizeof(key.comm));
9595
}
9696

97-
key.cgroupid = bpf_get_current_cgroup_id();
97+
key.cgroupid = get_current_cgroup_id();
9898

9999
statvalue *val = (statvalue *)bpf_map_lookup_elem(&pkt_count, &key);
100100
if (val) {
@@ -104,7 +104,16 @@ process_cgroup_skb(struct __sk_buff *skb) {
104104
} else {
105105
statvalue initval = {.packets = 1, .bytes = pkt_len};
106106

107-
bpf_map_update_elem(&pkt_count, &key, &initval, BPF_NOEXIST);
107+
// BPF_NOEXIST can race on multi-CPU: another CPU may insert the same key
108+
// between our lookup and this insert. On failure, retry the lookup and
109+
// increment atomically so the packet is not silently dropped.
110+
if (bpf_map_update_elem(&pkt_count, &key, &initval, BPF_NOEXIST) != 0) {
111+
val = (statvalue *)bpf_map_lookup_elem(&pkt_count, &key);
112+
if (val) {
113+
__sync_fetch_and_add(&val->packets, 1);
114+
__sync_fetch_and_add(&val->bytes, pkt_len);
115+
}
116+
}
108117
}
109118
}
110119

bpf/counter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
//go:build ignore
2323

24+
#pragma once
25+
2426
#define s6_addr in6_u.u6_addr8
2527
#define s6_addr16 in6_u.u6_addr16
2628
#define s6_addr32 in6_u.u6_addr32

bpf/counter_common.h

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2020
// SOFTWARE.
2121

22+
//go:build ignore
23+
2224
#pragma once
2325

26+
#include "cgroup.h"
2427
#include "counter.h"
2528

26-
// Map key struct for IP traffic
29+
// Counter map key struct for IP traffic
2730
typedef struct statkey_t {
2831
struct in6_addr srcip; // source IPv6 address
2932
struct in6_addr dstip; // destination IPv6 address
@@ -35,25 +38,39 @@ typedef struct statkey_t {
3538
__u8 proto; // transport protocol
3639
} statkey;
3740

38-
// Map value struct with counters
41+
// Counter map value struct with counters
3942
typedef struct statvalue_t {
4043
__u64 packets; // packets ingress + egress
4144
__u64 bytes; // bytes ingress + egress
4245
} statvalue;
4346

44-
// Map definition
47+
// Counter map definition
4548
struct {
4649
__uint(type, BPF_MAP_TYPE_LRU_HASH); // LRU hash requires 4.10 kernel
4750
__uint(max_entries, MAX_ENTRIES);
4851
__type(key, statkey);
4952
__type(value, statvalue);
5053
} pkt_count SEC(".maps");
5154

55+
// Sockinfo struct
5256
typedef struct sockinfo_t {
5357
__u8 comm[TASK_COMM_LEN];
5458
pid_t pid;
5559
} sockinfo;
5660

61+
// Configuration map value struct
62+
typedef struct counter_cfg_t {
63+
__u64 cgrpfs_magic; // cgroupv1 or cgroupv2 fs magic
64+
} counter_cfg_value;
65+
66+
// Configuration map definition
67+
struct {
68+
__uint(type, BPF_MAP_TYPE_ARRAY);
69+
__uint(max_entries, 1);
70+
__type(key, __u32);
71+
__type(value, counter_cfg_value);
72+
} counter_cfg SEC(".maps");
73+
5774
// IPv4-mapped IPv6 address prefix (for V4MAPPED conversion)
5875
static const __u8 ip4in6[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
5976

@@ -271,7 +288,16 @@ static inline __attribute__((always_inline)) void update_val(statkey *key,
271288
} else {
272289
statvalue initval = {.packets = 1, .bytes = size};
273290

274-
bpf_map_update_elem(&pkt_count, key, &initval, BPF_NOEXIST);
291+
// BPF_NOEXIST can race on multi-CPU: another CPU may insert the same key
292+
// between our lookup and this insert. On failure, retry the lookup and
293+
// increment atomically so the packet is not silently dropped.
294+
if (bpf_map_update_elem(&pkt_count, key, &initval, BPF_NOEXIST) != 0) {
295+
val = (statvalue *)bpf_map_lookup_elem(&pkt_count, key);
296+
if (val) {
297+
__sync_fetch_and_add(&val->packets, 1);
298+
__sync_fetch_and_add(&val->bytes, size);
299+
}
300+
}
275301
}
276302
}
277303

@@ -641,3 +667,84 @@ process_udp_send(struct sk_buff *skb, statkey *key, pid_t pid) {
641667

642668
return msglen;
643669
}
670+
671+
/**
672+
* get_cgroupid - reads the cgroup ID from the given struct cgroup
673+
*
674+
* This function reads the cgroup ID from the given struct cgroup and returns
675+
* it. The function works on kernels v4.10 and above.
676+
*
677+
* @cgrp: the struct cgroup to read the cgroup ID from
678+
*
679+
* Returns: the cgroup ID as an unsigned 64-bit integer
680+
*
681+
* get_cgroupid() comes from aquasecurity/tracee, license: Apache-2.0
682+
*/
683+
static inline __attribute__((always_inline)) __u64
684+
get_cgroup_id(struct cgroup *cgrp) {
685+
struct kernfs_node *kn = BPF_CORE_READ(cgrp, kn);
686+
687+
if (kn == NULL)
688+
return 0;
689+
690+
__u64 id; // was union kernfs_node_id before 5.5, can read it as u64 in both
691+
// situations
692+
693+
if (bpf_core_type_exists(union kernfs_node_id)) {
694+
struct kernfs_node___older_v55 *kn_old = (void *)kn;
695+
struct kernfs_node___rh8 *kn_rh8 = (void *)kn;
696+
697+
if (bpf_core_field_exists(kn_rh8->id)) {
698+
// RHEL8 has both types declared: union and u64:
699+
// kn->id
700+
// rh->rh_kabi_hidden_172->id
701+
// pointing to the same data
702+
bpf_core_read(&id, sizeof(__u64), &kn_rh8->id);
703+
id = id & 0xffffffff; // XXX: u32 is required
704+
} else {
705+
// all other regular kernels below v5.5
706+
bpf_core_read(&id, sizeof(__u64), &kn_old->id);
707+
id = id & 0xffffffff; // XXX: u32 is required
708+
}
709+
} else {
710+
// kernel v5.5 and above
711+
bpf_core_read(&id, sizeof(__u64), &kn->id);
712+
}
713+
714+
return id;
715+
}
716+
717+
/**
718+
* get_current_cgroup_id - get the current cgroup ID
719+
*
720+
* This function determines the current cgroup ID of the running process.
721+
* It first checks if the cgroup v2 pseudo-filesystem is present and
722+
* if so, calls the bpf_get_current_cgroup_id() helper function. If not,
723+
* it reads the cgroup ID from the task_struct structure. It returns the
724+
* cgroup ID as an unsigned 64-bit integer.
725+
*
726+
* @return the current cgroup ID as an unsigned 64-bit integer
727+
*/
728+
static inline __attribute__((always_inline)) __u64 get_current_cgroup_id(void) {
729+
__u32 zero_key = 0;
730+
__u64 cgrpfs_magic = 0;
731+
732+
counter_cfg_value *cfg =
733+
(counter_cfg_value *)bpf_map_lookup_elem(&counter_cfg, &zero_key);
734+
if (cfg) {
735+
cgrpfs_magic = cfg->cgrpfs_magic;
736+
}
737+
738+
// cgroup v2
739+
if (bpf_core_enum_value_exists(enum bpf_func_id,
740+
BPF_FUNC_get_current_cgroup_id) &&
741+
cgrpfs_magic == CGROUP2_FSMAGIC) {
742+
return bpf_get_current_cgroup_id();
743+
}
744+
745+
// cgroup v1
746+
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
747+
struct cgroup *cgroup = BPF_CORE_READ(task, cgroups, subsys[0], cgroup);
748+
749+
return get_cgroup_id(cgroup);
750+
}

bpf/kprobe.bpf.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ int BPF_KPROBE(tcp_sendmsg, struct sock *sk, struct msghdr *msg, size_t size) {
5555
bpf_get_current_comm(&key.comm, sizeof(key.comm));
5656
}
5757

58-
key.cgroupid = bpf_get_current_cgroup_id();
58+
key.cgroupid = get_current_cgroup_id();
5959

6060
if (!process_tcp(false, sk, &key, pid))
6161
return 0;
@@ -92,7 +92,7 @@ int BPF_KPROBE(tcp_cleanup_rbuf, struct sock *sk, int copied) {
9292
bpf_get_current_comm(&key.comm, sizeof(key.comm));
9393
}
9494

95-
key.cgroupid = bpf_get_current_cgroup_id();
95+
key.cgroupid = get_current_cgroup_id();
9696

9797
if (!process_tcp(true, sk, &key, pid))
9898
return 0;
@@ -131,7 +131,7 @@ int BPF_KPROBE(ip_send_skb, struct net *net, struct sk_buff *skb) {
131131
bpf_get_current_comm(&key.comm, sizeof(key.comm));
132132
}
133133

134-
key.cgroupid = bpf_get_current_cgroup_id();
134+
key.cgroupid = get_current_cgroup_id();
135135

136136
size_t msglen = process_udp_send(skb, &key, pid);
137137
if (msglen > 0)
@@ -171,7 +171,7 @@ int BPF_KPROBE(ip6_send_skb, struct sk_buff *skb) {
171171
bpf_get_current_comm(&key.comm, sizeof(key.comm));
172172
}
173173

174-
key.cgroupid = bpf_get_current_cgroup_id();
174+
key.cgroupid = get_current_cgroup_id();
175175

176176
size_t msglen = process_udp_send(skb, &key, pid);
177177
if (msglen > 0)
@@ -209,7 +209,7 @@ int BPF_KPROBE(skb_consume_udp, struct sock *sk, struct sk_buff *skb, int len) {
209209
bpf_get_current_comm(&key.comm, sizeof(key.comm));
210210
}
211211

212-
key.cgroupid = bpf_get_current_cgroup_id();
212+
key.cgroupid = get_current_cgroup_id();
213213

214214
if (!process_udp_recv(true, skb, &key, pid))
215215
return 0;
@@ -246,7 +246,7 @@ int BPF_KPROBE(__icmp_send, struct sk_buff *skb, __u8 type, __u8 code,
246246
bpf_get_current_comm(&key.comm, sizeof(key.comm));
247247
}
248248

249-
key.cgroupid = bpf_get_current_cgroup_id();
249+
key.cgroupid = get_current_cgroup_id();
250250

251251
/* process_icmp4 populates src/dst IPs from the triggering packet's IP
252252
* header; its returned msglen (triggering payload) is not used here. */
@@ -308,7 +308,7 @@ int BPF_KPROBE(icmp6_send, struct sk_buff *skb, __u8 type, __u8 code,
308308
bpf_get_current_comm(&key.comm, sizeof(key.comm));
309309
}
310310

311-
key.cgroupid = bpf_get_current_cgroup_id();
311+
key.cgroupid = get_current_cgroup_id();
312312

313313
/* process_icmp6 populates src/dst IPs from the triggering packet's IPv6
314314
* header; its returned msglen (triggering payload_len) is not used here. */
@@ -359,7 +359,7 @@ int BPF_KPROBE(icmp_rcv, struct sk_buff *skb) {
359359
bpf_get_current_comm(&key.comm, sizeof(key.comm));
360360
}
361361

362-
key.cgroupid = bpf_get_current_cgroup_id();
362+
key.cgroupid = get_current_cgroup_id();
363363

364364
size_t msglen = process_icmp4(skb, &key, pid);
365365
if (msglen > 0)
@@ -391,10 +391,11 @@ int BPF_KPROBE(icmpv6_rcv, struct sk_buff *skb) {
391391
bpf_get_current_comm(&key.comm, sizeof(key.comm));
392392
}
393393

394-
key.cgroupid = bpf_get_current_cgroup_id();
394+
key.cgroupid = get_current_cgroup_id();
395395

396396
size_t msglen = process_icmp6(skb, &key, pid);
397-
update_val(&key, msglen);
397+
if (msglen > 0)
398+
update_val(&key, msglen);
398399

399400
return 0;
400401
}

0 commit comments

Comments
 (0)