-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmark.c
More file actions
275 lines (240 loc) · 7.42 KB
/
Copy pathmark.c
File metadata and controls
275 lines (240 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
//go:build ignore
#include <stdbool.h>
#include <asm/param.h>
#include <linux/bpf.h>
#include <linux/types.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#ifndef USER_HZ
#define USER_HZ 100ULL
#endif
#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
/*
* procfs exposes task start time in USER_HZ clock ticks. Keeping the same unit
* in the BPF event lets Go build identical keys from /proc/<pid>/stat before
* the BPF programs are attached.
*
* This is timekeeping state, not a CPU cycle counter, so CPU frequency changes,
* CONFIG_HZ, and tickless scheduling do not change the scale. The kernel procfs
* implementation prints /proc/<pid>/stat field 22 from task->start_boottime via
* nsec_to_clock_t(timens_add_boottime_ns(...)). USER_HZ is the userspace ABI
* clock tick rate returned by sysconf(_SC_CLK_TCK), normally 100 on Linux.
*
* The one important caveat is time namespaces: procfs applies the namespace
* boottime offset, while this BPF program reads the raw task_struct field. Keep
* userspace in the same time namespace as the observed host, or account for the
* namespace offset before comparing keys.
*/
#define NSEC_PER_SEC 1000000000ULL
#define NSEC_PER_USER_TICK (NSEC_PER_SEC / USER_HZ)
#define EVENT_FORK 1
#define EVENT_EXIT 2
#define EVENT_EXEC 3
/*
* Small CO-RE view of task_struct. start_boottime is the timestamp used by
* procfs for field 22 in /proc/<pid>/stat, so it can be shared with userspace.
*/
struct task_struct {
int pid;
int tgid;
__u64 start_boottime;
char comm[TASK_COMM_LEN];
} __attribute__((preserve_access_index));
/*
* A PID can be reused, so TGID alone is not a process identity. Pair TGID with
* procfs-compatible start time ticks to identify one process lifetime per boot.
* The tick granularity is usually 10ms, which is enough to distinguish normal
* PID reuse but not a cryptographic process UUID.
*/
struct process_key {
__u32 tgid;
__u64 start_time;
};
/*
* Map entries represent effective marks plus checker provenance. A missing
* entry, a live entry with has_mark=false, and a tombstone all mean "unmarked"
* for policy. Live no-mark entries are kept after a process had a mark once so
* newer checker generations can remove a mark without losing lifetime state.
* Only userspace tombstone collection physically deletes entries.
*/
struct process_value {
bool tombstone;
bool inheritance;
bool has_mark;
__s8 priority;
__u64 generation;
__u64 mark;
__u64 timestamp;
};
/*
* Ring events carry the kernel's view at the time of the transition.
*/
struct event {
__u32 type;
struct process_key key;
struct process_key parent_key;
__u32 pid;
__u32 ppid;
bool has_mark;
struct process_value value;
char comm[TASK_COMM_LEN];
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(pinning, LIBBPF_PIN_BY_NAME);
__type(key, struct process_key);
__type(value, struct process_value);
__uint(max_entries, 32768);
} processes SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(pinning, LIBBPF_PIN_BY_NAME);
__uint(max_entries, 1 << 24);
__type(value, struct event);
} events SEC(".maps");
static __always_inline __u64 now_ns(void)
{
return bpf_ktime_get_boot_ns();
}
static __always_inline struct process_key process_key_from_task(struct task_struct *task)
{
struct process_key key = {};
key.tgid = task->tgid;
key.start_time = task->start_boottime / NSEC_PER_USER_TICK;
return key;
}
static __always_inline void copy_task_comm(char *dst, struct task_struct *task)
{
__builtin_memcpy(dst, task->comm, TASK_COMM_LEN);
}
SEC("tp_btf/sched_process_fork")
int BPF_PROG(handle_sched_process_fork, struct task_struct *parent, struct task_struct *child)
{
struct process_key key = process_key_from_task(child);
struct process_key parent_key = process_key_from_task(parent);
struct process_value value = {};
struct process_value *parent_value;
struct process_value *existing_value;
__u32 child_pid = child->pid;
__u32 child_tgid = child->tgid;
bool has_mark = false;
if (child_pid != child_tgid) {
return 0;
}
/*
* Inheritance is resolved before the fork event is submitted. If another
* BPF program or userspace inserted the child first, report only a live
* value with has_mark=true as a mark. Live no-mark entries deliberately do
* not inherit and do not make the event marked.
*/
existing_value = bpf_map_lookup_elem(&processes, &key);
if (existing_value) {
value = *existing_value;
has_mark = !existing_value->tombstone && existing_value->has_mark;
} else {
parent_value = bpf_map_lookup_elem(&processes, &parent_key);
if (parent_value && !parent_value->tombstone && parent_value->has_mark) {
value = *parent_value;
value.tombstone = false;
value.inheritance = false;
value.timestamp = now_ns();
if (bpf_map_update_elem(&processes, &key, &value, BPF_NOEXIST) == 0) {
has_mark = true;
} else {
existing_value = bpf_map_lookup_elem(&processes, &key);
if (existing_value) {
value = *existing_value;
has_mark = !existing_value->tombstone && existing_value->has_mark;
}
}
}
}
struct event *event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event) {
return 0;
}
event->type = EVENT_FORK;
event->key = key;
event->parent_key = parent_key;
event->pid = child_pid;
event->ppid = parent->tgid;
event->has_mark = has_mark;
event->value = value;
copy_task_comm(event->comm, child);
bpf_ringbuf_submit(event, 0);
return 0;
}
SEC("tp_btf/sched_process_exit")
int BPF_PROG(handle_sched_process_exit, struct task_struct *task)
{
struct process_key key = process_key_from_task(task);
struct process_value value = {};
struct process_value *existing_value;
__u32 pid = task->pid;
__u32 tgid = task->tgid;
bool has_mark = false;
if (pid != tgid) {
return 0;
}
/*
* Stop transitions tombstone any existing mark instead of deleting it.
* Userspace removes old tombstones from both mirrors after a grace period.
*/
existing_value = bpf_map_lookup_elem(&processes, &key);
if (existing_value) {
value = *existing_value;
value.tombstone = true;
value.timestamp = now_ns();
bpf_map_update_elem(&processes, &key, &value, BPF_ANY);
has_mark = !existing_value->tombstone && existing_value->has_mark;
}
struct event *event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event) {
return 0;
}
event->type = EVENT_EXIT;
event->key = key;
event->parent_key = (struct process_key){};
event->pid = pid;
event->ppid = 0;
event->has_mark = has_mark;
event->value = value;
copy_task_comm(event->comm, task);
bpf_ringbuf_submit(event, 0);
return 0;
}
SEC("tp_btf/sched_process_exec")
int BPF_PROG(handle_sched_process_exec, struct task_struct *task, int old_pid, void *bprm)
{
struct process_key key = process_key_from_task(task);
struct process_value value = {};
struct process_value *existing_value;
__u32 pid = task->pid;
__u32 tgid = task->tgid;
bool has_mark = false;
if (pid != tgid) {
return 0;
}
existing_value = bpf_map_lookup_elem(&processes, &key);
if (existing_value) {
value = *existing_value;
has_mark = !existing_value->tombstone && existing_value->has_mark;
}
struct event *event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event) {
return 0;
}
event->type = EVENT_EXEC;
event->key = key;
event->parent_key = (struct process_key){};
event->pid = pid;
event->ppid = 0;
event->has_mark = has_mark;
event->value = value;
copy_task_comm(event->comm, task);
bpf_ringbuf_submit(event, 0);
return 0;
}
char __license[] SEC("license") = "Dual MIT/GPL";