Skip to content
API Documentation
BastionGuard™
rootguard.bpf.c
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
2#include "vmlinux.h"
3#include <bpf/bpf_helpers.h>
4#include <bpf/bpf_core_read.h>
5#include <bpf/bpf_tracing.h>
7
8#ifndef EPERM
9#define EPERM 1
10#endif
11#ifndef ATTR_MODE
12#define ATTR_MODE (1U << 0)
13#define ATTR_UID (1U << 1)
14#define ATTR_GID (1U << 2)
15#endif
16#ifndef S_ISUID
17#define S_ISUID 0004000
18#define S_ISGID 0002000
19#endif
20#ifndef S_IWGRP
21#define S_IWGRP 0000020
22#define S_IWOTH 0000002
23#endif
24#ifndef S_IRUSR
25#define S_IRUSR 0000400
26#define S_IWUSR 0000200
27#endif
28
29char LICENSE[] SEC("license") = "GPL";
30
31/*
32 * Use the BTF-provided declaration whenever the target kernel exposes the
33 * kfunc. Its first argument changed from struct path * to const struct path *
34 * across kernel versions, so redeclaring it unconditionally is not portable.
35 * Keep a weak fallback declaration for kernels whose BTF has no such kfunc.
36 */
37#ifndef ROOTGUARD_VMLINUX_HAS_BPF_PATH_D_PATH
38extern int bpf_path_d_path(const struct path *path, char *buf,
39 size_t buf__sz) __weak __ksym;
40#endif
41
42struct {
43 __uint(type, BPF_MAP_TYPE_RINGBUF);
44 __uint(max_entries, 1 << 22); /* 4 MiB */
45} events SEC(".maps");
46
47/* Key: TGID. Populated by a future trusted authorization bridge. */
48struct {
49 __uint(type, BPF_MAP_TYPE_HASH);
50 __uint(max_entries, 4096);
51 __type(key, __u32);
52 __type(value, struct rg_auth_token);
53} auth_tokens SEC(".maps");
54
55struct {
56 __uint(type, BPF_MAP_TYPE_ARRAY);
57 __uint(max_entries, 1);
58 __type(key, __u32);
59 __type(value, struct rg_kernel_config);
60} rg_config_map SEC(".maps");
61
62/* Exact executable identities, populated from policy by userspace. */
63struct {
64 __uint(type, BPF_MAP_TYPE_HASH);
65 __uint(max_entries, 2048);
66 __type(key, struct rg_file_key);
67 __type(value, struct rg_rule_value);
68} trusted_execs SEC(".maps");
69
70struct {
71 __uint(type, BPF_MAP_TYPE_HASH);
72 __uint(max_entries, 2048);
73 __type(key, struct rg_file_key);
74 __type(value, struct rg_rule_value);
75} denied_execs SEC(".maps");
76
77/* Inodes whose ownership, mode and security metadata are protected. */
78struct {
79 __uint(type, BPF_MAP_TYPE_HASH);
80 __uint(max_entries, 8192);
81 __type(key, struct rg_file_key);
82 __type(value, struct rg_protected_value);
83} protected_inodes SEC(".maps");
84
91
97
103
110
111/*
112 * Aggregate by execution domain, not by cgroup alone: desktop applications may
113 * share a broad systemd user slice. Actor+parent executable identities keep a
114 * backup helper from consuming the cooldown of an unrelated process. Protected
115 * identities never use this limiter.
116 */
124
133
134/*
135 * bpf_spin_lock is only supported in HASH/ARRAY map values. Keep this a
136 * bounded HASH (rather than LRU_HASH) so the verifier can associate the BTF
137 * spin-lock metadata with each value. Execution-domain cardinality is low on
138 * a desktop because actor/parent executable inode identities are stable; the
139 * map is also cleared on every successful policy swap.
140 */
141struct {
142 __uint(type, BPF_MAP_TYPE_HASH);
143 __uint(max_entries, 32768);
144 __type(key, struct rg_metadata_domain_key);
145 __type(value, struct rg_metadata_domain_state);
146} metadata_domain_states SEC(".maps");
147
148#define RG_METADATA_DECISION_SAMPLE (1U << 0)
149#define RG_METADATA_DECISION_BURST (1U << 1)
150
151/*
152 * Exec identity is captured while the task is alive, avoiding the /proc race
153 * that occurs when short-lived helpers exit before userspace drains the ring.
154 */
155struct {
156 __uint(type, BPF_MAP_TYPE_LRU_HASH);
157 __uint(max_entries, 65536);
158 __type(key, __u32); /* TGID */
159 __type(value, struct rg_exec_context);
160} exec_contexts SEC(".maps");
161
162/*
163 * path_chmod/path_chown receive a complete struct path. The exact path is
164 * cached for the following inode_setattr hook in the same task.
165 */
166struct {
167 __uint(type, BPF_MAP_TYPE_LRU_HASH);
168 __uint(max_entries, 16384);
169 __type(key, __u64); /* PID/TGID pair */
170 __type(value, struct rg_pending_path);
171} pending_paths SEC(".maps");
172
173/*
174 * A second signal independent from the credential-transition hooks. It keeps
175 * the fact that a task was previously observed as non-root. If an exploit
176 * mutates cred directly and later asks the kernel for a capability, the
177 * capable hook can still identify the inconsistent state.
178 */
179struct {
180 __uint(type, BPF_MAP_TYPE_LRU_HASH);
181 __uint(max_entries, 65536);
182 __type(key, __u64); /* task_struct address */
183 __type(value, struct rg_task_state);
184} task_states SEC(".maps");
185
186static __always_inline __u32 cred_uid(const struct cred *cred)
187{
188 kuid_t uid = {};
189
190 if (!cred)
191 return (__u32)-1;
192
193 BPF_CORE_READ_INTO(&uid, cred, uid);
194 return uid.val;
195}
196
197static __always_inline __u32 cred_euid(const struct cred *cred)
198{
199 kuid_t euid = {};
200
201 if (!cred)
202 return (__u32)-1;
203
204 BPF_CORE_READ_INTO(&euid, cred, euid);
205 return euid.val;
206}
207
208static __always_inline const struct cred *current_cred_ptr(void)
209{
210 struct task_struct *task =
211 (struct task_struct *)bpf_get_current_task_btf();
212
213 return BPF_CORE_READ(task, cred);
214}
215
216static __always_inline __u32 current_euid_value(void)
217{
218 return cred_euid(current_cred_ptr());
219}
220
221static __always_inline void current_ids(__u32 *pid, __u32 *tgid, __u32 *ppid)
222{
223 __u64 id = bpf_get_current_pid_tgid();
224 struct task_struct *task = (struct task_struct *)bpf_get_current_task_btf();
225 struct task_struct *parent = BPF_CORE_READ(task, real_parent);
226
227 *pid = (__u32)id;
228 *tgid = (__u32)(id >> 32);
229 *ppid = parent ? (__u32)BPF_CORE_READ(parent, tgid) : 0;
230}
231
232static __always_inline const struct rg_kernel_config *kernel_config(void)
233{
234 __u32 key = 0;
235 return bpf_map_lookup_elem(&rg_config_map, &key);
236}
237
238static __always_inline bool current_executable_key(struct rg_file_key *key);
239static __always_inline bool parent_executable_key(struct rg_file_key *key);
240
241/*
242 * Return SAMPLE while this execution domain is inside its small forensic
243 * sample budget, and BURST once a destructive metadata pattern crosses the
244 * configured threshold.
245 * The burst notification has its own long cooldown; counting continues without
246 * sending one ring-buffer record per file.
247 */
248static __always_inline __u32 observe_unprotected_metadata(bool count_burst_candidate,
249 bool request_sample,
250 __u32 *burst_count)
251{
252 const struct rg_kernel_config *cfg = kernel_config();
253 struct rg_metadata_domain_state initial = {};
254 struct rg_metadata_domain_state *state;
255 struct rg_metadata_domain_key key = {};
256 __u64 now, window_ns, cooldown_ns;
257 __u32 monitor_global, detect_bursts, sample_limit, burst_threshold;
258 __u32 decision = 0;
259
260 if (burst_count)
261 *burst_count = 0;
262 if (!cfg)
263 return 0;
264
265 /* Copy config scalars before taking a map-value spin lock. */
266 monitor_global = cfg->monitor_global_privilege_metadata;
267 detect_bursts = cfg->detect_global_metadata_bursts;
268 sample_limit = cfg->global_metadata_sample_limit;
269 burst_threshold = cfg->metadata_burst_threshold;
270 window_ns = (__u64)cfg->metadata_burst_window_ms * 1000000ULL;
271 cooldown_ns = (__u64)cfg->metadata_burst_cooldown_ms * 1000000ULL;
272 if (!monitor_global && !detect_bursts)
273 return 0;
274
275 key.cgroup_id = bpf_get_current_cgroup_id();
276 if (!current_executable_key(&key.actor))
277 key.tgid_fallback = (__u32)(bpf_get_current_pid_tgid() >> 32);
278 (void)parent_executable_key(&key.parent);
279 now = bpf_ktime_get_ns();
280 if (window_ns == 0)
281 window_ns = 5000000000ULL;
282 if (cooldown_ns < window_ns)
283 cooldown_ns = window_ns;
284
285 state = bpf_map_lookup_elem(&metadata_domain_states, &key);
286 if (!state) {
287 initial.window_start_ns = now;
288 bpf_map_update_elem(&metadata_domain_states, &key, &initial, BPF_NOEXIST);
289 state = bpf_map_lookup_elem(&metadata_domain_states, &key);
290 if (!state)
291 return 0;
292 }
293
294 /* Multiple workers in one service/cgroup may run on different CPUs. */
295 bpf_spin_lock(&state->lock);
296
297 if (state->window_start_ns == 0 ||
298 now - state->window_start_ns >= window_ns) {
299 state->window_start_ns = now;
300 state->destructive_count = 0;
301 state->sampled_events = 0;
302 }
303
304 if (request_sample && monitor_global &&
305 state->sampled_events < sample_limit) {
306 ++state->sampled_events;
307 decision |= RG_METADATA_DECISION_SAMPLE;
308 }
309
310 if (count_burst_candidate && detect_bursts) {
311 if (state->destructive_count != (__u32)-1)
312 ++state->destructive_count;
313 if (state->destructive_count >= burst_threshold &&
314 (state->last_burst_ns == 0 ||
315 now - state->last_burst_ns >= cooldown_ns)) {
316 state->last_burst_ns = now;
317 decision |= RG_METADATA_DECISION_BURST;
318 if (burst_count)
319 *burst_count = state->destructive_count;
320 }
321 }
322
323 bpf_spin_unlock(&state->lock);
324 return decision;
325}
326
327/*
328 * Clang normally lowers `blocked ? -EPERM : 0` to a boolean followed by a
329 * 64-bit NEG instruction. Some verifier versions lose the signed bounds of
330 * that value and reject an otherwise valid LSM return. The compiler barrier
331 * keeps two constant-return control-flow paths: exactly -EPERM or 0.
332 */
333static __always_inline int lsm_result_from_verdict(__u32 verdict)
334{
335 if (verdict == RG_VERDICT_BLOCKED) {
336 asm volatile("" ::: "memory");
337 return -EPERM;
338 }
339
340 return 0;
341}
342
343static __always_inline bool file_key_from_inode(const struct inode *inode,
344 struct rg_file_key *key)
345{
346 struct super_block *superblock;
347
348 if (!inode || !key)
349 return false;
350
351 superblock = BPF_CORE_READ(inode, i_sb);
352 if (!superblock)
353 return false;
354
355 key->inode = BPF_CORE_READ(inode, i_ino);
356 key->device = BPF_CORE_READ(superblock, s_dev);
357 return key->inode != 0;
358}
359
360static __always_inline bool file_key_from_file(const struct file *file,
361 struct rg_file_key *key)
362{
363 struct inode *inode;
364
365 if (!file || !key)
366 return false;
367
368 inode = BPF_CORE_READ(file, f_inode);
369 return file_key_from_inode(inode, key);
370}
371
372static __always_inline bool file_key_from_dentry(const struct dentry *dentry,
373 struct rg_file_key *key)
374{
375 struct inode *inode;
376
377 if (!dentry || !key)
378 return false;
379
380 inode = BPF_CORE_READ(dentry, d_inode);
381 return file_key_from_inode(inode, key);
382}
383
384static __always_inline const char *dentry_name_ptr(const struct dentry *dentry)
385{
386 if (!dentry)
387 return 0;
388 return (const char *)BPF_CORE_READ(dentry, d_name.name);
389}
390
391static __always_inline bool current_executable_key(struct rg_file_key *key)
392{
393 struct task_struct *task;
394 struct mm_struct *mm;
395 struct file *file;
396
397 task = (struct task_struct *)bpf_get_current_task_btf();
398 mm = BPF_CORE_READ(task, mm);
399 if (!mm)
400 return false;
401
402 file = BPF_CORE_READ(mm, exe_file);
403 return file_key_from_file(file, key);
404}
405
406static __always_inline bool parent_executable_key(struct rg_file_key *key)
407{
408 struct task_struct *task, *parent;
409 struct mm_struct *mm;
410 struct file *file;
411
412 if (!key)
413 return false;
414 task = (struct task_struct *)bpf_get_current_task_btf();
415 if (!task)
416 return false;
417 parent = BPF_CORE_READ(task, real_parent);
418 if (!parent)
419 return false;
420 mm = BPF_CORE_READ(parent, mm);
421 if (!mm)
422 return false;
423 file = BPF_CORE_READ(mm, exe_file);
424 return file_key_from_file(file, key);
425}
426
427static __always_inline bool file_key_equal(const struct rg_file_key *left,
428 const struct rg_file_key *right)
429{
430 return left && right &&
431 left->device == right->device &&
432 left->inode == right->inode;
433}
434
435static __always_inline void copy_map_string(char *destination,
436 const char *source,
437 __u32 size)
438{
439 if (!destination || !source || size == 0)
440 return;
441 if (size == RG_FILENAME_LEN)
442 __builtin_memcpy_inline(destination, source, RG_FILENAME_LEN);
443 else if (size == RG_COMM_LEN)
444 __builtin_memcpy_inline(destination, source, RG_COMM_LEN);
445}
446
447static __always_inline void capture_exec_context(struct linux_binprm *bprm,
448 const struct rg_file_key *key)
449{
450 struct rg_exec_context context = {};
451 struct task_struct *task;
452 const char *filename;
453 __u64 ids;
454 __u32 tgid;
455
456 if (!bprm)
457 return;
458
459 ids = bpf_get_current_pid_tgid();
460 tgid = (__u32)(ids >> 32);
461 if (tgid == 0)
462 return;
463
464 if (key)
465 context.executable = *key;
466
467 task = (struct task_struct *)bpf_get_current_task_btf();
468 if (task)
469 context.start_boottime_ns = BPF_CORE_READ(task, start_boottime);
470
471 /*
472 * bprm->filename is captured synchronously at exec time. The executable
473 * device/inode above remains the authoritative identity even when this
474 * string is relative or later renamed.
475 */
476 filename = BPF_CORE_READ(bprm, filename);
477 if (filename)
478 bpf_probe_read_kernel_str(context.executable_path,
479 sizeof(context.executable_path),
480 filename);
481 bpf_map_update_elem(&exec_contexts, &tgid, &context, BPF_ANY);
482}
483
484static __always_inline bool runtime_path_is_run(const char *path)
485{
486 if (!path)
487 return false;
488
489 /* Match /run and /run/... but not /runtime, /runner, etc. */
490 return path[0] == '/' && path[1] == 'r' && path[2] == 'u' &&
491 path[3] == 'n' && (path[4] == '\0' || path[4] == '/');
492}
493
494static __always_inline bool pending_path_is_under_run(
495 const struct rg_file_key *file_key)
496{
497 __u64 ids = bpf_get_current_pid_tgid();
498 struct rg_pending_path *pending =
499 bpf_map_lookup_elem(&pending_paths, &ids);
500
501 if (!pending || !file_key_equal(file_key, &pending->file))
502 return false;
503
504 return runtime_path_is_run(pending->path);
505}
506
507static __always_inline void discard_pending_path(void)
508{
509 __u64 ids = bpf_get_current_pid_tgid();
510 bpf_map_delete_elem(&pending_paths, &ids);
511}
512
513static __always_inline void record_pending_path(const struct path *path,
514 __u32 operation)
515{
516 struct rg_pending_path pending = {};
517 __u64 ids;
518
519 if (!path || !bpf_path_d_path)
520 return;
521 if (!file_key_from_dentry(BPF_CORE_READ(path, dentry), &pending.file))
522 return;
523 /*
524 * Older BTF declares the first argument as struct path *, while newer
525 * kernels const-qualify it. A mutable pointer is accepted by both forms.
526 */
527 if (bpf_path_d_path((struct path *)path, pending.path,
528 sizeof(pending.path)) <= 0)
529 return;
530
531 pending.operation = operation;
532 ids = bpf_get_current_pid_tgid();
533 bpf_map_update_elem(&pending_paths, &ids, &pending, BPF_ANY);
534}
535
536static __always_inline bool consume_pending_path(
537 const struct rg_file_key *file_key, char *destination,
538 __u32 *path_resolution)
539{
540 __u64 ids = bpf_get_current_pid_tgid();
541 struct rg_pending_path *pending =
542 bpf_map_lookup_elem(&pending_paths, &ids);
543
544 if (!pending)
545 return false;
546
547 if (!file_key_equal(file_key, &pending->file)) {
548 bpf_map_delete_elem(&pending_paths, &ids);
549 return false;
550 }
551
552 __builtin_memcpy_inline(destination, pending->path, RG_FILENAME_LEN);
553 *path_resolution = RG_PATH_LSM_EXACT;
554 bpf_map_delete_elem(&pending_paths, &ids);
555 return true;
556}
557
558static __always_inline void enrich_event_actor(struct rg_event *event)
559{
560 struct task_struct *task;
561 struct task_struct *parent;
562 struct rg_exec_context *context;
563 struct rg_exec_context *parent_context;
564 __u32 tgid;
565
566 if (!event)
567 return;
568
569 tgid = event->tgid;
570 if (current_executable_key(&event->actor_file)) {
571 /* Exact executable identity is always captured at event time. */
572 }
573
574 task = (struct task_struct *)bpf_get_current_task_btf();
575 if (task) {
576 event->actor_start_boottime_ns =
577 BPF_CORE_READ(task, start_boottime);
578 parent = BPF_CORE_READ(task, real_parent);
579 if (parent)
580 bpf_core_read_str(event->parent_comm,
581 sizeof(event->parent_comm),
582 &parent->comm);
583 }
584
585 context = bpf_map_lookup_elem(&exec_contexts, &tgid);
586 if (context) {
587 if (event->actor_file.inode == 0)
588 event->actor_file = context->executable;
589 if (event->actor_start_boottime_ns == 0)
590 event->actor_start_boottime_ns = context->start_boottime_ns;
593 }
594
595 if (event->ppid == 0)
596 return;
597 parent_context = bpf_map_lookup_elem(&exec_contexts, &event->ppid);
598 if (!parent_context)
599 return;
600 event->parent_file = parent_context->executable;
602 parent_context->executable_path,
604}
605
606static __always_inline __u64 task_key(const struct task_struct *task)
607{
608 return (__u64)(unsigned long)task;
609}
610
611static __always_inline void observe_task(struct task_struct *task,
612 __u32 uid, __u32 euid)
613{
614 __u64 key;
615 struct rg_task_state state = {};
616 struct rg_task_state *existing;
617
618 if (!task)
619 return;
620
621 key = task_key(task);
622 existing = bpf_map_lookup_elem(&task_states, &key);
623
624 if (euid != 0) {
625 /*
626 * The effective UID determines whether the task can exercise root
627 * privileges. A legitimate setuid-root process normally keeps the
628 * caller's real UID, so testing uid != 0 here would erase an
629 * authorization immediately after sudo/pkexec gained EUID 0.
630 */
631 /* inode_permission is hot: avoid rewriting an unchanged state. */
632 if (existing && existing->observed_nonroot &&
633 !existing->authorized_root &&
634 existing->baseline_uid == uid)
635 return;
636
637 if (existing)
638 state = *existing;
639 state.observed_nonroot = 1;
640 state.authorized_root = 0;
641 state.anomaly_reported = 0;
642 state.baseline_uid = uid;
643 bpf_map_update_elem(&task_states, &key, &state, BPF_ANY);
644 return;
645 }
646
647 /* Root tasks are inserted by task_alloc/authorized transitions only. */
648 if (!existing)
649 bpf_map_update_elem(&task_states, &key, &state, BPF_ANY);
650}
651
652static __always_inline void observe_current_task(const struct cred *cred)
653{
654 struct task_struct *task =
655 (struct task_struct *)bpf_get_current_task_btf();
656 observe_task(task, cred_uid(cred), cred_euid(cred));
657}
658
659static __always_inline void mark_current_authorized_root(void)
660{
661 struct task_struct *task =
662 (struct task_struct *)bpf_get_current_task_btf();
663 __u64 key = task_key(task);
664 struct rg_task_state state = {};
665 struct rg_task_state *existing =
666 bpf_map_lookup_elem(&task_states, &key);
667
668 if (existing)
669 state = *existing;
670 state.authorized_root = 1;
671 state.anomaly_reported = 0;
672 bpf_map_update_elem(&task_states, &key, &state, BPF_ANY);
673}
674
675static __always_inline bool consume_token_for(__u32 tgid, __u32 ppid,
676 __u32 target_euid)
677{
678 __u64 now = bpf_ktime_get_ns();
679 struct rg_auth_token *token;
680 bool valid;
681
682 token = bpf_map_lookup_elem(&auth_tokens, &tgid);
683 if (token) {
684 valid = token->expires_ns >= now && token->target_euid == target_euid;
685 bpf_map_delete_elem(&auth_tokens, &tgid);
686 if (valid)
687 return true;
688 }
689
690 token = bpf_map_lookup_elem(&auth_tokens, &ppid);
691 if (token) {
692 valid = token->expires_ns >= now && token->target_euid == target_euid;
693 bpf_map_delete_elem(&auth_tokens, &ppid);
694 if (valid)
695 return true;
696 }
697
698 return false;
699}
700
701static __always_inline __u32 decide_escalation(
702 const struct rg_file_key *file_key, bool have_file_key, bool authorized,
703 __u32 *reason_flags, __u32 *rule_id)
704{
705 const struct rg_kernel_config *cfg;
706 struct rg_rule_value *rule;
707
708 *reason_flags = RG_REASON_NONE;
709 *rule_id = 0;
710
711 if (authorized) {
712 *reason_flags = RG_REASON_AUTH_TOKEN;
714 }
715
716 if (!have_file_key) {
718 } else {
719 rule = bpf_map_lookup_elem(&denied_execs, file_key);
720 if (rule) {
721 *reason_flags = RG_REASON_DENIED_EXEC;
722 *rule_id = rule->rule_id;
723 cfg = kernel_config();
724 return cfg && cfg->mode == RG_MODE_ENFORCE
727 }
728
729 rule = bpf_map_lookup_elem(&trusted_execs, file_key);
730 if (rule) {
731 *reason_flags = RG_REASON_TRUSTED_EXEC;
732 *rule_id = rule->rule_id;
733 return RG_VERDICT_TRUSTED;
734 }
735
736 *reason_flags = RG_REASON_UNKNOWN_EXEC;
737 }
738
739 cfg = kernel_config();
740 if (cfg && cfg->mode == RG_MODE_ENFORCE &&
742 return RG_VERDICT_BLOCKED;
743
744 return RG_VERDICT_AUDIT;
745}
746
747static __always_inline __u32 metadata_verdict(bool protected_change,
748 bool protected_audit_only,
749 bool global_change)
750{
751 const struct rg_kernel_config *cfg = kernel_config();
752
753 if (!cfg || cfg->mode != RG_MODE_ENFORCE)
754 return RG_VERDICT_AUDIT;
755 if (protected_change && !protected_audit_only &&
757 return RG_VERDICT_BLOCKED;
758 if (global_change && cfg->global_metadata_action == RG_ACTION_BLOCK)
759 return RG_VERDICT_BLOCKED;
760 return RG_VERDICT_AUDIT;
761}
762
763static __always_inline void emit_event(
764 __u32 type, __u32 verdict, __u32 reason_flags, __u32 rule_id,
765 __u32 old_euid, __u32 new_euid, __s32 flags, __u32 auxiliary,
766 const struct rg_file_key *file_key, const char *kernel_filename,
767 const char *kernel_xattr, __u32 old_mode, __u32 new_mode,
768 __u32 old_uid, __u32 new_uid, __u32 old_gid, __u32 new_gid)
769{
770 const struct rg_kernel_config *cfg;
771 struct rg_event *event;
772
774 cfg = kernel_config();
775 if (cfg && cfg->log_trusted == 0)
776 return;
777 }
778
779 event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
780 if (!event)
781 return;
782
783 __builtin_memset(event, 0, sizeof(*event));
784 event->timestamp_ns = bpf_ktime_get_ns();
785 current_ids(&event->pid, &event->tgid, &event->ppid);
786 event->old_euid = old_euid;
787 event->new_euid = new_euid;
788 event->event_type = type;
789 event->verdict = verdict;
790 event->reason_flags = reason_flags;
791 event->rule_id = rule_id;
792 event->lsm_flags = flags;
793 event->auxiliary = auxiliary;
794 event->old_mode = old_mode;
795 event->new_mode = new_mode;
796 event->old_uid = old_uid;
797 event->new_uid = new_uid;
798 event->old_gid = old_gid;
799 event->new_gid = new_gid;
800 if (file_key)
801 event->file = *file_key;
802 bpf_get_current_comm(event->comm, sizeof(event->comm));
803 enrich_event_actor(event);
804
805 if (!consume_pending_path(file_key, event->filename,
806 &event->path_resolution) &&
807 kernel_filename) {
808 bpf_probe_read_kernel_str(event->filename,
809 sizeof(event->filename),
810 kernel_filename);
811 event->path_resolution = RG_PATH_BASENAME;
812 }
813 if (kernel_xattr)
814 bpf_probe_read_kernel_str(event->xattr_name,
815 sizeof(event->xattr_name),
816 kernel_xattr);
817
818 bpf_ringbuf_submit(event, 0);
819}
820
821static __always_inline int xattr_kind(const char *name)
822{
823 char value[RG_XATTR_NAME_LEN] = {};
824
825 if (!name)
826 return 0;
827 if (bpf_probe_read_kernel_str(value, sizeof(value), name) <= 0)
828 return 0;
829
830 if (__builtin_memcmp(value, "security.capability", 20) == 0)
831 return 1;
832 if (__builtin_memcmp(value, "security.ima", 13) == 0 ||
833 __builtin_memcmp(value, "security.evm", 13) == 0)
834 return 2;
835 if (__builtin_memcmp(value, "security.selinux", 17) == 0 ||
836 __builtin_memcmp(value, "security.apparmor", 18) == 0)
837 return 3;
838 return 0;
839}
840
841static __always_inline void inode_metadata(const struct inode *inode,
842 __u32 *mode, __u32 *uid, __u32 *gid)
843{
844 kuid_t inode_uid = {};
845 kgid_t inode_gid = {};
846
847 *mode = BPF_CORE_READ(inode, i_mode) & 07777U;
848 BPF_CORE_READ_INTO(&inode_uid, inode, i_uid);
849 BPF_CORE_READ_INTO(&inode_gid, inode, i_gid);
850 *uid = inode_uid.val;
851 *gid = inode_gid.val;
852}
853
854/* Inherit the task provenance marker across fork/clone. */
855SEC("lsm/task_alloc")
856int BPF_PROG(rg_task_alloc, struct task_struct *task,
857 unsigned long clone_flags, int ret)
858{
859 struct task_struct *current;
860 const struct cred *cred;
861 struct rg_task_state state = {};
862 struct rg_task_state *parent_state;
863 __u64 parent_key, child_key;
864
865 /*
866 * The chained LSM result arrives through a u64 tracing-context slot.
867 * Some Clang/verifier combinations lose the signed 32-bit range when
868 * returning that dynamic value directly. Never override a previous
869 * denial: normalize it to a verifier-safe negative errno instead.
870 */
871 if (ret)
872 return -EPERM;
873
874 current = (struct task_struct *)bpf_get_current_task_btf();
875 parent_key = task_key(current);
876 child_key = task_key(task);
877 parent_state = bpf_map_lookup_elem(&task_states, &parent_key);
878 if (parent_state) {
879 state = *parent_state;
880 } else {
881 cred = BPF_CORE_READ(current, cred);
882 state.observed_nonroot = cred_euid(cred) != 0;
883 state.baseline_uid = cred_uid(cred);
884 }
885
886 bpf_map_update_elem(&task_states, &child_key, &state, BPF_ANY);
887 return 0;
888}
889
890/* Covers setuid()/setresuid()-style credential transitions. */
891SEC("lsm/task_fix_setuid")
892int BPF_PROG(rg_task_fix_setuid, struct cred *new, const struct cred *old,
893 int flags, int ret)
894{
895 struct rg_file_key file_key = {};
896 __u32 old_euid, new_euid, pid, tgid, ppid;
897 __u32 reason_flags, rule_id, verdict;
898 bool authorized, have_file_key;
899
900 /* Preserve an earlier LSM denial with a verifier-safe errno. */
901 if (ret)
902 return -EPERM;
903
905 old_euid = cred_euid(old);
906 new_euid = cred_euid(new);
907
908 /* A root service dropping to a user must establish a non-root baseline. */
909 if (new_euid != 0) {
910 struct task_struct *task =
911 (struct task_struct *)bpf_get_current_task_btf();
912 observe_task(task, cred_uid(new), new_euid);
913 return 0;
914 }
915 if (old_euid == 0)
916 return 0;
917
918 current_ids(&pid, &tgid, &ppid);
919 authorized = consume_token_for(tgid, ppid, new_euid);
920 have_file_key = current_executable_key(&file_key);
921 verdict = decide_escalation(&file_key, have_file_key, authorized,
922 &reason_flags, &rule_id);
923
924 if (verdict == RG_VERDICT_AUTHORIZED || verdict == RG_VERDICT_TRUSTED)
926
927 emit_event(RG_EVENT_SETUID, verdict, reason_flags, rule_id,
928 old_euid, new_euid, flags, 0,
929 have_file_key ? &file_key : 0, 0, 0,
930 0, 0, 0, 0, 0, 0);
931
932 return lsm_result_from_verdict(verdict);
933}
934
935/* Covers privilege changes prepared during exec, including setuid/file caps. */
936SEC("lsm/bprm_check_security")
937int BPF_PROG(rg_bprm_check_security, struct linux_binprm *bprm, int ret)
938{
939 struct task_struct *task;
940 const struct cred *old_cred;
941 const struct cred *new_cred;
942 const struct file *file;
943 const char *filename;
944 struct rg_file_key file_key = {};
945 __u32 old_euid, new_euid, pid, tgid, ppid;
946 __u32 reason_flags, rule_id, verdict;
947 bool authorized, have_file_key;
948
949 /* Preserve an earlier LSM denial with a verifier-safe errno. */
950 if (ret)
951 return -EPERM;
952
953 task = (struct task_struct *)bpf_get_current_task_btf();
954 old_cred = BPF_CORE_READ(task, cred);
955 observe_current_task(old_cred);
956 new_cred = BPF_CORE_READ(bprm, cred);
957 old_euid = cred_euid(old_cred);
958 new_euid = cred_euid(new_cred);
959
960 /*
961 * Capture every exec, not only privilege transitions. Short-lived tools
962 * such as chmod/chown may exit before userspace can read /proc/<pid>/exe.
963 */
964 file = BPF_CORE_READ(bprm, file);
965 have_file_key = file_key_from_file(file, &file_key);
966 capture_exec_context(bprm, have_file_key ? &file_key : 0);
967
968 if (new_euid != 0) {
969 observe_task(task, cred_uid(new_cred), new_euid);
970 return 0;
971 }
972 if (old_euid == 0)
973 return 0;
974
975 current_ids(&pid, &tgid, &ppid);
976 authorized = consume_token_for(tgid, ppid, new_euid);
977 verdict = decide_escalation(&file_key, have_file_key, authorized,
978 &reason_flags, &rule_id);
979 filename = BPF_CORE_READ(bprm, filename);
980
981 if (verdict == RG_VERDICT_AUTHORIZED || verdict == RG_VERDICT_TRUSTED)
983
984 emit_event(RG_EVENT_EXEC_PRIV, verdict, reason_flags, rule_id,
985 old_euid, new_euid, 0, 0,
986 have_file_key ? &file_key : 0, filename, 0,
987 0, 0, 0, 0, 0, 0);
988
989 return lsm_result_from_verdict(verdict);
990}
991
992/*
993 * A direct cred overwrite may bypass task_fix_setuid. The provenance guard is
994 * therefore invoked from several independent authorization surfaces. A task
995 * previously observed as non-root must have crossed an authorized transition
996 * before it is allowed to use UID 0.
997 */
998static __always_inline int guard_current_root_provenance(
999 __u32 auxiliary, __s32 operation_flags,
1000 const struct rg_file_key *file_key)
1001{
1002 struct task_struct *task;
1003 const struct cred *cred;
1004 struct rg_task_state *state;
1005 const struct rg_kernel_config *cfg;
1006 __u64 key;
1007 __u32 uid, euid, verdict;
1008
1009 task = (struct task_struct *)bpf_get_current_task_btf();
1010 cred = BPF_CORE_READ(task, cred);
1011 uid = cred_uid(cred);
1012 euid = cred_euid(cred);
1013
1014 if (euid != 0) {
1015 observe_task(task, uid, euid);
1016 return 0;
1017 }
1018
1019 key = task_key(task);
1020 state = bpf_map_lookup_elem(&task_states, &key);
1021 if (!state || !state->observed_nonroot || state->authorized_root)
1022 return 0;
1023
1024 /*
1025 * File-based setuid credentials can already be active before
1026 * bprm_check_security observes the final EUID. Resolve the current
1027 * executable here as a verifier-safe fallback. The userspace loader
1028 * admits only exact, root-owned, non-writable device+inode identities.
1029 */
1030 {
1031 struct rg_file_key executable_key = {};
1032 struct rg_rule_value *trusted_rule;
1033
1034 if (current_executable_key(&executable_key)) {
1035 trusted_rule = bpf_map_lookup_elem(&trusted_execs,
1036 &executable_key);
1037 if (trusted_rule) {
1038 state->authorized_root = 1;
1039 state->anomaly_reported = 0;
1040 return 0;
1041 }
1042 }
1043 }
1044
1045 cfg = kernel_config();
1046 verdict = cfg && cfg->mode == RG_MODE_ENFORCE &&
1050
1051 if (!state->anomaly_reported) {
1052 state->anomaly_reported = 1;
1055 state->baseline_uid, euid, operation_flags,
1056 auxiliary, file_key, 0, 0,
1057 0, 0, uid, uid, 0, 0);
1058 }
1059
1060 return lsm_result_from_verdict(verdict);
1061}
1062
1063SEC("lsm/capable")
1064int BPF_PROG(rg_capable, const struct cred *cred, struct user_namespace *ns,
1065 int cap, unsigned int opts, int ret)
1066{
1067 const struct cred *current_cred;
1068
1069 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1070 if (ret)
1071 return -EPERM;
1072
1073 current_cred = current_cred_ptr();
1074 if (cred != current_cred)
1075 return 0;
1076
1077 return guard_current_root_provenance((__u32)cap, (__s32)opts, 0);
1078}
1079
1080/*
1081 * Filesystem permission checks are a second interception point after a direct
1082 * cred overwrite. This catches many attacks before they can modify a file,
1083 * even when no explicit capable() check is reached on the exact path.
1084 */
1085SEC("lsm/inode_permission")
1086int BPF_PROG(rg_inode_permission, struct inode *inode, int mask, int ret)
1087{
1088 struct rg_file_key key = {};
1089 bool have_key;
1090
1091 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1092 if (ret)
1093 return -EPERM;
1094
1095 have_key = file_key_from_inode(inode, &key);
1096 return guard_current_root_provenance((__u32)mask, mask,
1097 have_key ? &key : 0);
1098}
1099
1100/* Also cover access through an already resolved struct file. */
1101SEC("lsm/file_open")
1102int BPF_PROG(rg_file_open, struct file *file, int ret)
1103{
1104 struct rg_file_key key = {};
1105 bool have_key;
1106
1107 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1108 if (ret)
1109 return -EPERM;
1110
1111 have_key = file_key_from_file(file, &key);
1113 have_key ? &key : 0);
1114}
1115
1116SEC("lsm/path_chmod")
1117int BPF_PROG(rg_path_chmod, const struct path *path, umode_t mode, int ret)
1118{
1119 if (ret)
1120 return -EPERM;
1122 return 0;
1123}
1124
1125SEC("lsm/path_chown")
1126int BPF_PROG2(rg_path_chown,
1127 const struct path *, path,
1128 kuid_t, uid,
1129 kgid_t, gid,
1130 int, ret)
1131{
1132 /* kuid_t/kgid_t are small struct-by-value arguments. BPF_PROG performs
1133 * scalar casts from ctx[] and therefore cannot materialise them. Use
1134 * BPF_PROG2, which accounts for aggregate arguments in the trampoline
1135 * context. The values are not needed here; inode_setattr records the
1136 * resulting owner metadata change.
1137 */
1138 (void)uid;
1139 (void)gid;
1140
1141 if (ret)
1142 return -EPERM;
1144 return 0;
1145}
1146
1147SEC("lsm/inode_setattr")
1148int BPF_PROG(rg_inode_setattr, struct mnt_idmap *idmap,
1149 struct dentry *dentry, struct iattr *attr, int ret)
1150{
1151 struct rg_file_key key = {};
1152 struct rg_protected_value *protected;
1153 const struct rg_kernel_config *cfg;
1154 struct inode *inode;
1155 __u32 old_mode, new_mode, old_uid, new_uid, old_gid, new_gid;
1156 __u32 valid, reasons = 0, protected_reasons = 0, global_reasons = 0;
1157 __u32 verdict, event_type, metadata_decision = 0, burst_count = 0;
1158
1159 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1160 if (ret)
1161 return -EPERM;
1162 ret = guard_current_root_provenance(0, 0, 0);
1163 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1164 if (ret)
1165 return -EPERM;
1166 if (!file_key_from_dentry(dentry, &key))
1167 return 0;
1168
1169 inode = BPF_CORE_READ(dentry, d_inode);
1170 inode_metadata(inode, &old_mode, &old_uid, &old_gid);
1171 valid = BPF_CORE_READ(attr, ia_valid);
1172 new_mode = old_mode;
1173 new_uid = old_uid;
1174 new_gid = old_gid;
1175
1176 if (valid & ATTR_MODE)
1177 new_mode = BPF_CORE_READ(attr, ia_mode) & 07777U;
1178 if (valid & ATTR_UID) {
1179 kuid_t uid = {};
1180 BPF_CORE_READ_INTO(&uid, attr, ia_uid);
1181 new_uid = uid.val;
1182 }
1183 if (valid & ATTR_GID) {
1184 kgid_t gid = {};
1185 BPF_CORE_READ_INTO(&gid, attr, ia_gid);
1186 new_gid = gid.val;
1187 }
1188
1189 /*
1190 * Production filtering: ordinary chmod/chown traffic is not a security
1191 * incident and must not flood a desktop system. Global events are emitted
1192 * only for privilege-relevant transitions. Full metadata monitoring and
1193 * enforcement remain available for identities explicitly loaded into
1194 * protected_inodes.
1195 */
1196 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1197 if (protected) {
1198 if ((valid & ATTR_MODE) &&
1199 (protected->flags & RG_PROTECT_MODE) &&
1200 new_mode != protected->baseline_mode)
1201 protected_reasons |= RG_REASON_PROTECTED_MODE;
1202 if (((valid & ATTR_UID) || (valid & ATTR_GID)) &&
1203 (protected->flags & RG_PROTECT_OWNER) &&
1204 (new_uid != protected->baseline_uid ||
1205 new_gid != protected->baseline_gid))
1206 protected_reasons |= RG_REASON_PROTECTED_OWNER;
1207 }
1208
1209 cfg = kernel_config();
1210
1211 /*
1212 * Do not feed the ring buffer with unprotected chmod/chown traffic below
1213 * /run. BastionGuard Backup mounts temporary backup filesystems there and
1214 * restoring file metadata would otherwise look like a stream of global
1215 * privilege changes. Explicit protected identities always take precedence.
1216 * Credential provenance was checked above and is not bypassed.
1217 */
1218 if (!protected && cfg && cfg->ignore_run_global_metadata &&
1221 return 0;
1222 }
1223
1224 if (cfg && cfg->monitor_global_privilege_metadata) {
1225 /* A newly added setuid/setgid bit can create a direct privilege path. */
1226 if ((valid & ATTR_MODE) &&
1227 ((new_mode & (S_ISUID | S_ISGID)) &
1228 ~(old_mode & (S_ISUID | S_ISGID))))
1230
1231 /* Changing a previously non-root object to UID 0 is security-relevant. */
1232 if ((valid & ATTR_UID) && old_uid != 0 && new_uid == 0)
1234
1235 /*
1236 * Alert when an object that was already root-owned gains write access
1237 * for group or other users.
1238 */
1239 if ((valid & ATTR_MODE) && old_uid == 0 && new_uid == 0 &&
1240 ((new_mode & (S_IWGRP | S_IWOTH)) &
1241 ~(old_mode & (S_IWGRP | S_IWOTH))))
1242 global_reasons |= RG_REASON_ROOT_OWNED_WRITE_EXPOSURE |
1244 }
1245
1246 if (!protected && cfg &&
1247 ((valid & ATTR_MODE) || (valid & ATTR_UID) || (valid & ATTR_GID))) {
1248 bool burst_candidate = false;
1249
1250 /*
1251 * Backups and package managers legitimately replay huge numbers of
1252 * chmod/chown operations. A ransomware-style lockout is materially
1253 * different: it removes the owner's ability to access the object, or
1254 * promotes ownership to root. Count only those destructive patterns
1255 * for burst escalation; routine metadata replay does not consume the
1256 * burst budget.
1257 */
1258 if ((valid & ATTR_MODE) &&
1259 (old_mode & (S_IRUSR | S_IWUSR)) != 0 &&
1260 (new_mode & (S_IRUSR | S_IWUSR)) == 0)
1261 burst_candidate = true;
1262 if ((valid & ATTR_UID) && old_uid != 0 && new_uid == 0)
1263 burst_candidate = true;
1264
1265 metadata_decision = observe_unprotected_metadata(
1266 burst_candidate, global_reasons != 0, &burst_count);
1267 if (metadata_decision & RG_METADATA_DECISION_BURST)
1269 if (global_reasons &&
1270 !(metadata_decision & RG_METADATA_DECISION_SAMPLE))
1271 global_reasons = 0;
1272 }
1273
1274 reasons |= global_reasons | protected_reasons;
1275 if (!reasons)
1276 return 0;
1277
1278 verdict = metadata_verdict(
1279 protected_reasons != 0,
1280 protected && (protected->flags & RG_PROTECT_AUDIT_ONLY),
1281 (reasons & RG_REASON_GLOBAL_METADATA) != 0);
1282 event_type = (((valid & ATTR_UID) && new_uid != old_uid) ||
1283 ((valid & ATTR_GID) && new_gid != old_gid))
1286
1287 {
1288 __u32 actor_euid = current_euid_value();
1289 emit_event(event_type, verdict, reasons,
1290 protected ? protected->rule_id : 0,
1291 actor_euid, actor_euid,
1292 (int)valid, burst_count, &key, dentry_name_ptr(dentry), 0,
1293 old_mode, new_mode, old_uid, new_uid, old_gid, new_gid);
1294 }
1295
1296 return lsm_result_from_verdict(verdict);
1297}
1298
1299static __always_inline int handle_xattr_change(struct dentry *dentry,
1300 const char *name,
1301 int operation_flags)
1302{
1303 struct rg_file_key key = {};
1304 struct rg_protected_value *protected;
1305 const struct rg_kernel_config *cfg;
1306 struct inode *inode;
1307 __u32 current_mode = 0, current_uid = 0, current_gid = 0;
1308 __u32 reasons = 0, protected_reasons = 0, verdict, metadata_decision = 0;
1309 int kind;
1310
1311 int guard = guard_current_root_provenance(0, operation_flags, 0);
1312 if (guard)
1313 return guard;
1314 if (!file_key_from_dentry(dentry, &key))
1315 return 0;
1316
1317 inode = BPF_CORE_READ(dentry, d_inode);
1318 inode_metadata(inode, &current_mode, &current_uid, &current_gid);
1319
1320 kind = xattr_kind(name);
1321 if (!kind)
1322 return 0;
1323
1324 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1325 if (protected) {
1326 if (kind == 1 &&
1327 (protected->flags & RG_PROTECT_CAPABILITY_XATTR))
1328 protected_reasons |= RG_REASON_SECURITY_CAPABILITY;
1329 else if (kind == 2 &&
1330 (protected->flags & RG_PROTECT_INTEGRITY_XATTR))
1331 protected_reasons |= RG_REASON_INTEGRITY_XATTR;
1332 else if (kind == 3 &&
1333 (protected->flags & RG_PROTECT_SECURITY_LABEL))
1334 protected_reasons |= RG_REASON_SECURITY_LABEL;
1335 }
1336
1337 cfg = kernel_config();
1338 if (!protected && kind == 1 && operation_flags >= 0 && cfg &&
1340 metadata_decision = observe_unprotected_metadata(false, true, 0);
1341 if (kind == 1 && cfg && cfg->monitor_global_privilege_metadata &&
1342 (protected || (metadata_decision & RG_METADATA_DECISION_SAMPLE)))
1344
1345 reasons |= protected_reasons;
1346 if (!reasons)
1347 return 0;
1348
1349 verdict = metadata_verdict(
1350 protected_reasons != 0,
1351 protected && (protected->flags & RG_PROTECT_AUDIT_ONLY),
1352 (reasons & RG_REASON_GLOBAL_METADATA) != 0);
1353 emit_event(RG_EVENT_XATTR_CHANGE, verdict, reasons,
1354 protected ? protected->rule_id : 0,
1355 0, 0, operation_flags, (__u32)kind,
1356 &key, dentry_name_ptr(dentry), name,
1357 current_mode, current_mode,
1358 current_uid, current_uid, current_gid, current_gid);
1359 return lsm_result_from_verdict(verdict);
1360}
1361
1362SEC("lsm/inode_setxattr")
1363int BPF_PROG(rg_inode_setxattr, struct mnt_idmap *idmap,
1364 struct dentry *dentry, const char *name, const void *value,
1365 size_t size, int flags, int ret)
1366{
1367 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1368 if (ret)
1369 return -EPERM;
1370 return handle_xattr_change(dentry, name, flags);
1371}
1372
1373SEC("lsm/inode_removexattr")
1374int BPF_PROG(rg_inode_removexattr, struct mnt_idmap *idmap,
1375 struct dentry *dentry, const char *name, int ret)
1376{
1377 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1378 if (ret)
1379 return -EPERM;
1380 return handle_xattr_change(dentry, name, -1);
1381}
1382
1383SEC("lsm/inode_set_acl")
1384int BPF_PROG(rg_inode_set_acl, struct mnt_idmap *idmap,
1385 struct dentry *dentry, const char *acl_name,
1386 struct posix_acl *kacl, int ret)
1387{
1388 struct rg_file_key key = {};
1389 struct rg_protected_value *protected;
1390 __u32 verdict;
1391
1392 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1393 if (ret)
1394 return -EPERM;
1395 ret = guard_current_root_provenance(0, 0, 0);
1396 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1397 if (ret)
1398 return -EPERM;
1399 if (!file_key_from_dentry(dentry, &key))
1400 return 0;
1401 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1402 if (protected && (protected->flags & RG_PROTECT_ACL)) {
1403 verdict = metadata_verdict(true,
1404 (protected->flags & RG_PROTECT_AUDIT_ONLY) != 0, false);
1406 protected->rule_id, 0, 0, 1, 0,
1407 &key, dentry_name_ptr(dentry), acl_name,
1408 0, 0, 0, 0, 0, 0);
1409 } else {
1410 /*
1411 * Do not emit every global ACL edit. The hook does not provide a
1412 * low-noise semantic verdict here, so unprotected ACL activity remains
1413 * silent instead of generating production false positives.
1414 */
1415 return 0;
1416 }
1417 return lsm_result_from_verdict(verdict);
1418}
1419
1420SEC("lsm/inode_remove_acl")
1421int BPF_PROG(rg_inode_remove_acl, struct mnt_idmap *idmap,
1422 struct dentry *dentry, const char *acl_name, int ret)
1423{
1424 struct rg_file_key key = {};
1425 struct rg_protected_value *protected;
1426 __u32 verdict;
1427
1428 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1429 if (ret)
1430 return -EPERM;
1431 ret = guard_current_root_provenance(0, 0, 0);
1432 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1433 if (ret)
1434 return -EPERM;
1435 if (!file_key_from_dentry(dentry, &key))
1436 return 0;
1437 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1438 if (protected && (protected->flags & RG_PROTECT_ACL)) {
1439 verdict = metadata_verdict(true,
1440 (protected->flags & RG_PROTECT_AUDIT_ONLY) != 0, false);
1442 protected->rule_id, 0, 0, -1, 0,
1443 &key, dentry_name_ptr(dentry), acl_name,
1444 0, 0, 0, 0, 0, 0);
1445 } else {
1446 /* Unprotected ACL removals are intentionally silent for the same reason. */
1447 return 0;
1448 }
1449 return lsm_result_from_verdict(verdict);
1450}
1451
1452SEC("lsm/inode_unlink")
1453int BPF_PROG(rg_inode_unlink, struct inode *dir,
1454 struct dentry *dentry, int ret)
1455{
1456 struct rg_file_key key = {};
1457 struct rg_protected_value *protected;
1458 __u32 verdict;
1459
1460 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1461 if (ret)
1462 return -EPERM;
1463 ret = guard_current_root_provenance(0, 0, 0);
1464 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1465 if (ret)
1466 return -EPERM;
1467 if (!file_key_from_dentry(dentry, &key))
1468 return 0;
1469 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1470 if (!protected || !(protected->flags & RG_PROTECT_UNLINK))
1471 return 0;
1472
1473 verdict = metadata_verdict(true,
1474 (protected->flags & RG_PROTECT_AUDIT_ONLY) != 0, false);
1476 RG_REASON_PROTECTED_UNLINK, protected->rule_id,
1477 0, 0, 0, 0, &key, dentry_name_ptr(dentry), 0,
1478 0, 0, 0, 0, 0, 0);
1479 return lsm_result_from_verdict(verdict);
1480}
1481
1482SEC("lsm/inode_rename")
1483int BPF_PROG(rg_inode_rename, struct inode *old_dir,
1484 struct dentry *old_dentry, struct inode *new_dir,
1485 struct dentry *new_dentry, int ret)
1486{
1487 struct rg_file_key key = {};
1488 struct rg_protected_value *protected = 0;
1489 __u32 verdict;
1490
1491 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1492 if (ret)
1493 return -EPERM;
1494 ret = guard_current_root_provenance(0, 0, 0);
1495 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1496 if (ret)
1497 return -EPERM;
1498
1499 if (file_key_from_dentry(old_dentry, &key))
1500 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1501 if (!protected && file_key_from_dentry(new_dentry, &key))
1502 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1503 if (!protected || !(protected->flags & RG_PROTECT_RENAME))
1504 return 0;
1505
1506 verdict = metadata_verdict(true,
1507 (protected->flags & RG_PROTECT_AUDIT_ONLY) != 0, false);
1509 RG_REASON_PROTECTED_RENAME, protected->rule_id,
1510 0, 0, 0, 0, &key, dentry_name_ptr(old_dentry), 0,
1511 0, 0, 0, 0, 0, 0);
1512 return lsm_result_from_verdict(verdict);
1513}
1514
1515SEC("tp/sched/sched_process_exit")
1516int rg_sched_process_exit(struct trace_event_raw_sched_process_template *ctx)
1517{
1518 __u64 ids = bpf_get_current_pid_tgid();
1519 __u32 pid = (__u32)ids;
1520 __u32 tgid = (__u32)(ids >> 32);
1521
1522 if (pid == tgid)
1523 bpf_map_delete_elem(&exec_contexts, &tgid);
1524 bpf_map_delete_elem(&pending_paths, &ids);
1525 return 0;
1526}
1527
1528SEC("lsm/inode_link")
1529int BPF_PROG(rg_inode_link, struct dentry *old_dentry,
1530 struct inode *dir, struct dentry *new_dentry, int ret)
1531{
1532 struct rg_file_key key = {};
1533 struct rg_protected_value *protected;
1534 __u32 verdict;
1535
1536 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1537 if (ret)
1538 return -EPERM;
1539 ret = guard_current_root_provenance(0, 0, 0);
1540 /* Preserve an earlier LSM denial with a verifier-safe errno. */
1541 if (ret)
1542 return -EPERM;
1543 if (!file_key_from_dentry(old_dentry, &key))
1544 return 0;
1545 protected = bpf_map_lookup_elem(&protected_inodes, &key);
1546 if (!protected || !(protected->flags & RG_PROTECT_HARDLINK))
1547 return 0;
1548
1549 verdict = metadata_verdict(true,
1550 (protected->flags & RG_PROTECT_AUDIT_ONLY) != 0, false);
1552 RG_REASON_PROTECTED_HARDLINK, protected->rule_id,
1553 0, 0, 0, 0, &key, dentry_name_ptr(old_dentry), 0,
1554 0, 0, 0, 0, 0, 0);
1555 return lsm_result_from_verdict(verdict);
1556}
static __always_inline int xattr_kind(const char *name)
static __always_inline bool file_key_from_file(const struct file *file, struct rg_file_key *key)
static __always_inline bool runtime_path_is_run(const char *path)
static __always_inline bool consume_pending_path(const struct rg_file_key *file_key, char *destination, __u32 *path_resolution)
int BPF_PROG(rg_task_alloc, struct task_struct *task, unsigned long clone_flags, int ret)
#define RG_METADATA_DECISION_BURST
#define ATTR_GID
#define S_ISUID
static __always_inline bool file_key_from_inode(const struct inode *inode, struct rg_file_key *key)
static __always_inline void emit_event(__u32 type, __u32 verdict, __u32 reason_flags, __u32 rule_id, __u32 old_euid, __u32 new_euid, __s32 flags, __u32 auxiliary, const struct rg_file_key *file_key, const char *kernel_filename, const char *kernel_xattr, __u32 old_mode, __u32 new_mode, __u32 old_uid, __u32 new_uid, __u32 old_gid, __u32 new_gid)
#define ATTR_MODE
static __always_inline __u32 cred_uid(const struct cred *cred)
static __always_inline int handle_xattr_change(struct dentry *dentry, const char *name, int operation_flags)
static __always_inline void inode_metadata(const struct inode *inode, __u32 *mode, __u32 *uid, __u32 *gid)
static __always_inline bool pending_path_is_under_run(const struct rg_file_key *file_key)
#define S_IWOTH
static __always_inline bool parent_executable_key(struct rg_file_key *key)
static __always_inline bool file_key_equal(const struct rg_file_key *left, const struct rg_file_key *right)
static __always_inline const struct rg_kernel_config * kernel_config(void)
static __always_inline __u64 task_key(const struct task_struct *task)
static __always_inline void record_pending_path(const struct path *path, __u32 operation)
#define ATTR_UID
rg_pending_operation
@ RG_PENDING_NONE
@ RG_PENDING_CHOWN
@ RG_PENDING_CHMOD
static __always_inline __u32 decide_escalation(const struct rg_file_key *file_key, bool have_file_key, bool authorized, __u32 *reason_flags, __u32 *rule_id)
__type(key, __u32)
#define S_IRUSR
static __always_inline bool current_executable_key(struct rg_file_key *key)
static __always_inline const struct cred * current_cred_ptr(void)
static __always_inline void mark_current_authorized_root(void)
static __always_inline int guard_current_root_provenance(__u32 auxiliary, __s32 operation_flags, const struct rg_file_key *file_key)
static __always_inline __u32 metadata_verdict(bool protected_change, bool protected_audit_only, bool global_change)
#define S_ISGID
static __always_inline int lsm_result_from_verdict(__u32 verdict)
static __always_inline void observe_task(struct task_struct *task, __u32 uid, __u32 euid)
static __always_inline void enrich_event_actor(struct rg_event *event)
__uint(type, BPF_MAP_TYPE_RINGBUF)
static __always_inline __u32 observe_unprotected_metadata(bool count_burst_candidate, bool request_sample, __u32 *burst_count)
static __always_inline void observe_current_task(const struct cred *cred)
#define RG_METADATA_DECISION_SAMPLE
static __always_inline void current_ids(__u32 *pid, __u32 *tgid, __u32 *ppid)
struct rg_task_state SEC
int BPF_PROG2(rg_path_chown, const struct path *, path, kuid_t, uid, kgid_t, gid, int, ret)
int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz) __weak __ksym
static __always_inline __u32 current_euid_value(void)
int rg_sched_process_exit(struct trace_event_raw_sched_process_template *ctx)
#define S_IWUSR
static __always_inline bool consume_token_for(__u32 tgid, __u32 ppid, __u32 target_euid)
static __always_inline void capture_exec_context(struct linux_binprm *bprm, const struct rg_file_key *key)
#define EPERM
static __always_inline void copy_map_string(char *destination, const char *source, __u32 size)
#define S_IWGRP
static __always_inline __u32 cred_euid(const struct cred *cred)
static __always_inline void discard_pending_path(void)
static __always_inline const char * dentry_name_ptr(const struct dentry *dentry)
static __always_inline bool file_key_from_dentry(const struct dentry *dentry, struct rg_file_key *key)
@ RG_ACTION_BLOCK
@ RG_REASON_PROTECTED_OWNER
@ RG_REASON_PROTECTED_UNLINK
@ RG_REASON_SECURITY_LABEL
@ RG_REASON_TRUSTED_EXEC
@ RG_REASON_PROTECTED_ACL
@ RG_REASON_AUTH_TOKEN
@ RG_REASON_SECURITY_CAPABILITY
@ RG_REASON_ROOT_OWNED_WRITE_EXPOSURE
@ RG_REASON_FILE_ID_UNAVAILABLE
@ RG_REASON_DIRECT_CRED_ANOMALY
@ RG_REASON_METADATA_BURST
@ RG_REASON_INTEGRITY_XATTR
@ RG_REASON_NONE
@ RG_REASON_DENIED_EXEC
@ RG_REASON_OWNER_TO_ROOT
@ RG_REASON_UNKNOWN_EXEC
@ RG_REASON_PROTECTED_HARDLINK
@ RG_REASON_GLOBAL_METADATA
@ RG_REASON_SETID_ADDED
@ RG_REASON_PROTECTED_MODE
@ RG_REASON_PROTECTED_RENAME
#define RG_XATTR_NAME_LEN
@ RG_MODE_ENFORCE
@ RG_EVENT_PROTECTED_RENAME
@ RG_EVENT_EXEC_PRIV
@ RG_EVENT_OWNER_CHANGE
@ RG_EVENT_ACL_CHANGE
@ RG_EVENT_PROTECTED_UNLINK
@ RG_EVENT_SETUID
@ RG_EVENT_PROTECTED_HARDLINK
@ RG_EVENT_CREDENTIAL_ANOMALY
@ RG_EVENT_XATTR_CHANGE
@ RG_EVENT_PERMISSION_CHANGE
@ RG_PATH_LSM_EXACT
@ RG_PATH_BASENAME
#define RG_FILENAME_LEN
@ RG_PROTECT_HARDLINK
@ RG_PROTECT_UNLINK
@ RG_PROTECT_MODE
@ RG_PROTECT_INTEGRITY_XATTR
@ RG_PROTECT_AUDIT_ONLY
@ RG_PROTECT_ACL
@ RG_PROTECT_CAPABILITY_XATTR
@ RG_PROTECT_SECURITY_LABEL
@ RG_PROTECT_OWNER
@ RG_PROTECT_RENAME
#define RG_COMM_LEN
@ RG_VERDICT_TRUSTED
@ RG_VERDICT_AUDIT
@ RG_VERDICT_AUTHORIZED
@ RG_VERDICT_BLOCKED
struct rg_file_key actor_file
char filename[RG_FILENAME_LEN]
char xattr_name[RG_XATTR_NAME_LEN]
char parent_executable[RG_FILENAME_LEN]
char comm[RG_COMM_LEN]
char actor_executable[RG_FILENAME_LEN]
__u64 actor_start_boottime_ns
char parent_comm[RG_COMM_LEN]
__u32 path_resolution
struct rg_file_key executable
char executable_path[RG_FILENAME_LEN]
__u32 monitor_global_privilege_metadata
__u32 detect_global_metadata_bursts
struct rg_file_key parent
struct rg_file_key actor
struct bpf_spin_lock lock
struct rg_file_key file
char path[RG_FILENAME_LEN]
__u32 anomaly_reported
__u32 observed_nonroot
__u32 authorized_root