1212
1313char LICENSE [] SEC ("license" ) = "GPL" ;
1414
15+ /* Attach mechanism, selected at build time. With a delegated BPF token
16+ * (MEMTRACK_BPF_LINKS) the uprobes attach as uprobe_multi links and the fork
17+ * hook as tp_btf, both authorized through bpf(). Without it they use the classic
18+ * perf-based uprobe/tracepoint paths, which work on kernels predating
19+ * uprobe_multi (< 6.6) but cannot be delegated into the sandbox. */
20+ #ifdef MEMTRACK_BPF_LINKS
21+ #define UPROBE_SEC "uprobe.multi"
22+ #define URETPROBE_SEC "uretprobe.multi"
23+ #else
24+ #define UPROBE_SEC "uprobe"
25+ #define URETPROBE_SEC "uretprobe"
26+ #endif
27+
1528/* Macros for common map definitions */
1629#define BPF_HASH_MAP (name , key_type , value_type , max_ents ) \
1730 struct { \
@@ -35,6 +48,50 @@ char LICENSE[] SEC("license") = "GPL";
3548 __uint(max_entries, size); \
3649 } name SEC(".maps")
3750
51+ /* PID namespace the userspace tracker resolves PIDs in. When set (ino != 0),
52+ * PIDs are read relative to this namespace so tracking works when the tracker
53+ * runs inside a PID namespace (e.g. the macro-agent sandbox) while eBPF sees
54+ * global PIDs. Zero means "use global PIDs" — the default outside a sandbox. */
55+ const volatile __u64 target_pidns_dev = 0 ;
56+ const volatile __u64 target_pidns_ino = 0 ;
57+
58+ /* Current task's PID in the configured namespace (or global when unset). */
59+ static __always_inline __u32 current_pid (void ) {
60+ if (target_pidns_ino == 0 ) {
61+ return bpf_get_current_pid_tgid () >> 32 ;
62+ }
63+ struct bpf_pidns_info nsinfo = {};
64+ if (bpf_get_ns_current_pid_tgid (target_pidns_dev , target_pidns_ino , & nsinfo , sizeof (nsinfo )) !=
65+ 0 ) {
66+ return 0 ;
67+ }
68+ return nsinfo .tgid ;
69+ }
70+
71+ /* A task's PID as seen in the configured namespace (or global when unset).
72+ * Walks the task's pid struct: numbers[level].nr holds the PID at that namespace
73+ * depth, and numbers[level].ns identifies which namespace it is. */
74+ static __always_inline __u32 task_ns_pid (struct task_struct * task ) {
75+ if (target_pidns_ino == 0 ) {
76+ return BPF_CORE_READ (task , pid );
77+ }
78+ struct pid * thread_pid = BPF_CORE_READ (task , thread_pid );
79+ if (!thread_pid ) {
80+ return 0 ;
81+ }
82+ unsigned int level = BPF_CORE_READ (thread_pid , level );
83+ /* Bound the level to satisfy the verifier and match the pid array. */
84+ if (level >= 4 ) {
85+ return 0 ;
86+ }
87+ struct upid * up = & thread_pid -> numbers [level ];
88+ __u64 ino = BPF_CORE_READ (up , ns , ns .inum );
89+ if (ino != target_pidns_ino ) {
90+ return 0 ;
91+ }
92+ return BPF_CORE_READ (up , nr );
93+ }
94+
3895/* Map to store PIDs we're tracking */
3996BPF_HASH_MAP (tracked_pids , __u32 , __u8 , 10000 );
4097/* Map to store parent-child relationships to detect hierarchy */
@@ -71,26 +128,37 @@ static __always_inline int is_tracked(__u32 pid) {
71128 return 0 ;
72129}
73130
74- SEC ("tracepoint/sched/sched_process_fork" )
75- int tracepoint_sched_fork (struct trace_event_raw_sched_process_fork * ctx ) {
76- __u32 parent_pid = ctx -> parent_pid ;
77- __u32 child_pid = ctx -> child_pid ;
78-
79- /* Print process fork with PIDs */
80- // bpf_printk("sched_fork: parent_pid=%u child_pid=%u", parent_pid, child_pid);
81-
82- /* Check if parent is being tracked */
131+ /* Record a parent→child fork so the child inherits the parent's tracked state. */
132+ static __always_inline void follow_fork (__u32 parent_pid , __u32 child_pid ) {
133+ if (parent_pid == 0 || child_pid == 0 ) {
134+ return ;
135+ }
83136 if (is_tracked (parent_pid )) {
84- /* Auto-track this child */
85137 __u8 marker = 1 ;
86138 bpf_map_update_elem (& tracked_pids , & child_pid , & marker , BPF_ANY );
87139 bpf_map_update_elem (& pids_ppid , & child_pid , & parent_pid , BPF_ANY );
88-
89- // bpf_printk("auto-tracking child process: child_pid=%u", child_pid);
90140 }
141+ }
91142
143+ #ifdef MEMTRACK_BPF_LINKS
144+ /* tp_btf attaches via bpf(), so a delegated BPF token can authorize it inside
145+ * the sandbox. Reads task_struct pointers, resolving PIDs in the tracker's
146+ * namespace. */
147+ SEC ("tp_btf/sched_process_fork" )
148+ int BPF_PROG (tracepoint_sched_fork , struct task_struct * parent , struct task_struct * child ) {
149+ follow_fork (task_ns_pid (parent ), task_ns_pid (child ));
92150 return 0 ;
93151}
152+ #else
153+ /* Classic perf tracepoint for kernels without (or runs without) a BPF token.
154+ * The raw tracepoint context carries global PIDs directly; without a token we
155+ * never run in a PID namespace, so global PIDs are correct. */
156+ SEC ("tracepoint/sched/sched_process_fork" )
157+ int tracepoint_sched_fork (struct trace_event_raw_sched_process_fork * ctx ) {
158+ follow_fork (ctx -> parent_pid , ctx -> child_pid );
159+ return 0 ;
160+ }
161+ #endif
94162
95163/* == Helper functions for the allocation tracking == */
96164
@@ -110,9 +178,10 @@ static __always_inline int is_enabled(void) {
110178/* Helper to store parameter value in map for tracking between entry and return
111179 */
112180static __always_inline int store_param (void * map , __u64 value ) {
181+ /* Key by the global tid (stable, unique per thread across the entry/exit
182+ * pair); gate on the namespace-relative PID that the tracker registered. */
113183 __u64 tid = bpf_get_current_pid_tgid ();
114- __u32 pid = tid >> 32 ;
115- if (is_tracked (pid )) {
184+ if (is_tracked (current_pid ())) {
116185 bpf_map_update_elem (map , & tid , & value , BPF_ANY );
117186 }
118187 return 0 ;
@@ -134,7 +203,7 @@ static __always_inline __u64* take_param(void* map) {
134203#define SUBMIT_EVENT (evt_type , fill_data ) \
135204 { \
136205 __u64 tid = bpf_get_current_pid_tgid(); \
137- __u32 pid = tid >> 32; \
206+ __u32 pid = current_pid(); \
138207 \
139208 if (!is_tracked(pid) || !is_enabled()) { \
140209 return 0; \
@@ -210,9 +279,9 @@ static __always_inline int submit_mmap_event(__u64 addr, __u64 size, __u8 event_
210279/* Macro to generate uprobe/uretprobe pairs for allocation functions with 1 argument */
211280#define UPROBE_ARG_RET (name , arg_expr , submit_block ) \
212281 BPF_HASH_MAP(name##_arg, __u64, __u64, 10000); \
213- SEC("uprobe") \
282+ SEC(UPROBE_SEC) \
214283 int uprobe_##name(struct pt_regs* ctx) { return store_param(&name##_arg, arg_expr); } \
215- SEC("uretprobe") \
284+ SEC(URETPROBE_SEC) \
216285 int uretprobe_##name(struct pt_regs* ctx) { \
217286 __u64* arg_ptr = take_param(&name##_arg); \
218287 if (!arg_ptr) { \
@@ -228,7 +297,7 @@ static __always_inline int submit_mmap_event(__u64 addr, __u64 size, __u8 event_
228297
229298/* Macro for simple return value only functions like free */
230299#define UPROBE_RET (name , arg_expr , submit_block ) \
231- SEC("uprobe") \
300+ SEC(UPROBE_SEC) \
232301 int uprobe_##name(struct pt_regs* ctx) { \
233302 __u64 arg0 = arg_expr; \
234303 if (arg0 == 0) { \
@@ -244,7 +313,7 @@ static __always_inline int submit_mmap_event(__u64 addr, __u64 size, __u8 event_
244313 __u64 arg1; \
245314 }; \
246315 BPF_HASH_MAP(name##_args, __u64, struct name##_args_t, 10000); \
247- SEC("uprobe") \
316+ SEC(UPROBE_SEC) \
248317 int uprobe_##name(struct pt_regs* ctx) { \
249318 __u64 tid = bpf_get_current_pid_tgid(); \
250319 __u32 pid = tid >> 32; \
@@ -258,7 +327,7 @@ static __always_inline int submit_mmap_event(__u64 addr, __u64 size, __u8 event_
258327 bpf_map_update_elem(&name##_args, &tid, &args, BPF_ANY); \
259328 return 0; \
260329 } \
261- SEC("uretprobe") \
330+ SEC(URETPROBE_SEC) \
262331 int uretprobe_##name(struct pt_regs* ctx) { \
263332 __u64 tid = bpf_get_current_pid_tgid(); \
264333 struct name##_args_t* args = bpf_map_lookup_elem(&name##_args, &tid); \
0 commit comments