// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2026 eunomia-bpf org. */
//
// ActPlane in-kernel taint enforcer. Each hook propagates taint and evaluates
// the compiled rules; the ONLY event emitted is a TAINT_VIOLATION, via the
// single emit_violation() function, when a rule matches.

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "process.h"
#include "taint_engine.bpf.h"

char LICENSE[] SEC("license") = "Dual BSD/GPL";

const volatile unsigned int enforce_mode = 0;

#ifndef EPERM
#define EPERM 1
#endif
#ifndef SIGKILL
#define SIGKILL 9
#endif
#ifndef AF_INET
#define AF_INET 2
#endif
#ifndef O_ACCMODE
#define O_ACCMODE 00000003
#endif
#ifndef O_RDONLY
#define O_RDONLY 00000000
#endif
#ifndef O_WRONLY
#define O_WRONLY 00000001
#endif
#ifndef O_RDWR
#define O_RDWR 00000002
#endif
#ifndef O_CREAT
#define O_CREAT 00000100
#endif
#ifndef O_TRUNC
#define O_TRUNC 00001000
#endif
#ifndef MAY_EXEC
#define MAY_EXEC 1
#endif
#ifndef MAY_WRITE
#define MAY_WRITE 2
#endif
#ifndef MAY_READ
#define MAY_READ 4
#endif

#define TE_MODE_NOTIFY 0
#define TE_MODE_BLOCK  1
#define TE_MODE_KILL   2
#define TE_MODE_UNSUPPORTED 3

#define TE_ACCESS_READ    (1U << 0)
#define TE_ACCESS_WRITE   (1U << 1)
#define TE_ACCESS_EXEC    (1U << 2)
#define TE_ACCESS_CONNECT (1U << 3)

#define TE_OBJ_EXEC     1
#define TE_OBJ_FILE     2
#define TE_OBJ_ENDPOINT 3

#define TE_REF_FILE          1
#define TE_REF_PATH          2
#define TE_REF_PATH_DENTRY   3
#define TE_REF_USER_PATH     4
#define TE_REF_BPRM          5
#define TE_REF_STRINGS       6
#define TE_REF_SOCKADDR_KERN 7
#define TE_REF_SOCKADDR_USER 8

#include "channel.bpf.h"

struct te_event {
	pid_t pid;
	__u32 obj_kind;
	__u32 access;
	__u32 mode;
	const char *target;
	const char *display;
	__u32 ip;
};

struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 256 * 1024);
} rb SEC(".maps");

/* Pending open(at) args, stashed at sys_enter and consumed at sys_exit. The
 * sys_enter tracepoint fires before the kernel's copy_from_user faults the path
 * page in, so a non-faulting read of the path there can EFAULT and silently drop
 * the open (notably a fresh exec's own .rodata path, touched first at open()).
 * By sys_exit the page is resident, so the read is reliable. Keyed by tid. */
struct open_pend {
	__u64 path_ptr;
	__u32 flags;
};
struct {
	__uint(type, BPF_MAP_TYPE_LRU_HASH);
	__uint(max_entries, 16384);
	__type(key, __u64);
	__type(value, struct open_pend);
} ts_openpend SEC(".maps");

struct exec_scratch {
	char match[TAINT_TEXT_BUF];       /* >= PAT_LEN+SUF_MAX for suffix tail copy */
	char display[MAX_FILENAME_LEN];
};
struct {
	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
	__uint(max_entries, 1);
	__type(key, __u32);
	__type(value, struct exec_scratch);
} ts_exec_scratch SEC(".maps");

struct file_scratch {
	char path[MAX_FILENAME_LEN];
	struct file_id fid;
};
struct {
	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
	__uint(max_entries, 1);
	__type(key, __u32);
	__type(value, struct file_scratch);
} ts_file_scratch SEC(".maps");

static __always_inline struct exec_scratch *exec_scratch_buf(void)
{
	__u32 key = 0;

	return bpf_map_lookup_elem(&ts_exec_scratch, &key);
}

static __always_inline struct file_scratch *file_scratch_buf(void)
{
	__u32 key = 0;

	return bpf_map_lookup_elem(&ts_file_scratch, &key);
}

/* The one and only output channel. */
static __always_inline void fill_violation_provenance(struct event *v, pid_t pid,
						      __u64 matched_req)
{
	v->matched_label = 0;
	v->prov_label = 0;
	v->prov_timestamp_ns = 0;
	v->prov_pid = 0;
	v->prov_op = 0;
	v->prov_ip = 0;
	v->prov_target[0] = '\0';

	for (int i = 0; i < MAX_TAINT_LABELS; i++) {
		__u64 bit = 1ULL << i;
		if (!(matched_req & bit))
			continue;
		if (!v->matched_label)
			v->matched_label = bit;
		struct te_prov *p = te_lookup_proc_prov(pid, bit);
		if (!p)
			continue;
		v->prov_label = p->label;
		v->prov_timestamp_ns = p->timestamp_ns;
		v->prov_pid = p->pid;
		v->prov_op = p->op;
		v->prov_ip = p->ip;
		for (int j = 0; j < MAX_FILENAME_LEN; j++)
			v->prov_target[j] = p->target[j];
		v->prov_target[MAX_FILENAME_LEN - 1] = '\0';
		return;
	}
}

static __always_inline void emit_violation(pid_t pid, unsigned int rule_id,
					   const char *target, u32 conn_ip,
					   __u64 matched_req,
					   unsigned int blocked,
					   unsigned int killed,
					   unsigned int effect)
{
	struct task_struct *task = (struct task_struct *)bpf_get_current_task();
	struct event *v = bpf_ringbuf_reserve(&rb, sizeof(*v), 0);
	if (!v)
		return;
	v->type = EVENT_TYPE_TAINT_VIOLATION;
	v->pid = pid;
	v->ppid = BPF_CORE_READ(task, real_parent, tgid);
	v->blocked = blocked;
	v->killed = killed;
	v->effect = effect;
	v->timestamp_ns = bpf_ktime_get_ns();
	v->taint_rule_id = rule_id;
	v->conn_ip = conn_ip;
	v->taint_label = te_labels(pid);
	fill_violation_provenance(v, pid, matched_req);
	bpf_get_current_comm(&v->comm, sizeof(v->comm));
	v->filename[0] = '\0';
	if (target)
		bpf_probe_read_kernel_str(&v->filename, sizeof(v->filename), target);
	bpf_ringbuf_submit(v, 0);
}

static __always_inline int file_path(struct file *file, char *path, int path_sz)
{
	if (bpf_d_path(&file->f_path, path, path_sz) > 0)
		return 0;

	struct dentry *de = BPF_CORE_READ(file, f_path.dentry);
	const unsigned char *name = BPF_CORE_READ(de, d_name.name);
	if (name && bpf_probe_read_kernel_str(path, path_sz, name) > 0)
		return 0;
	return -1;
}

static __always_inline int path_to_str(const struct path *src, char *path,
				       int path_sz)
{
	if (bpf_d_path((struct path *)src, path, path_sz) > 0)
		return 0;
	return -1;
}

static __always_inline void append_dentry_name(char *path, struct dentry *dentry)
{
	char name_buf[TAINT_PAT_LEN] = {};
	const unsigned char *name = BPF_CORE_READ(dentry, d_name.name);
	int off = 0;

	if (!name || bpf_probe_read_kernel_str(name_buf, sizeof(name_buf), name) < 0)
		return;
	for (int i = 0; i < MAX_FILENAME_LEN; i++) {
		if (path[i] == '\0') {
			off = i;
			break;
		}
	}
	if (off <= 0 || off >= MAX_FILENAME_LEN - 1)
		return;
	if (path[off - 1] != '/')
		path[off++] = '/';
	for (int j = 0; j < TAINT_PAT_LEN; j++) {
		if (off + j >= MAX_FILENAME_LEN)
			break;
		path[off + j] = name_buf[j];
		if (name_buf[j] == '\0')
			break;
	}
	path[MAX_FILENAME_LEN - 1] = '\0';
}

static __always_inline int path_dentry_to_str(const struct path *dir,
					      struct dentry *dentry,
					      char *path, int path_sz)
{
	if (path_to_str(dir, path, path_sz) < 0)
		return -1;
	append_dentry_name(path, dentry);
	return 0;
}

static __always_inline int bprm_basename(struct linux_binprm *bprm, char *base,
					 int base_sz)
{
	struct file *file = BPF_CORE_READ(bprm, file);
	struct dentry *de = BPF_CORE_READ(file, f_path.dentry);
	const unsigned char *name = BPF_CORE_READ(de, d_name.name);

	if (name && bpf_probe_read_kernel_str(base, base_sz, name) > 0)
		return 0;
	return -1;
}

static __always_inline int bprm_filename(struct linux_binprm *bprm, char *path,
					 int path_sz)
{
	const char *filename = BPF_CORE_READ(bprm, filename);

	if (filename && bpf_probe_read_kernel_str(path, path_sz, filename) > 0)
		return 0;
	return -1;
}

static __always_inline __u32 te_access_from_open_flags(unsigned int flags)
{
	int acc = flags & O_ACCMODE;
	__u32 access = 0;

	if (acc != O_WRONLY)
		access |= TE_ACCESS_READ;
	if (acc != O_RDONLY || (flags & (O_CREAT | O_TRUNC)))
		access |= TE_ACCESS_WRITE;
	return access;
}

static __always_inline __u32 te_access_from_perm_mask(int mask)
{
	__u32 access = 0;

	if (mask & MAY_READ)
		access |= TE_ACCESS_READ;
	if (mask & MAY_WRITE)
		access |= TE_ACCESS_WRITE;
	return access;
}

static __always_inline __u32 te_tracepoint_mode(void)
{
	return TE_MODE_NOTIFY;
}

static __always_inline __u32 te_effect_mode(__u32 backend_mode, __u32 effect)
{
	if (effect == TEFFECT_NOTIFY)
		return TE_MODE_NOTIFY;
	if (effect == TEFFECT_KILL)
		return TE_MODE_KILL;
	if (effect == TEFFECT_BLOCK && backend_mode == TE_MODE_BLOCK)
		return TE_MODE_BLOCK;
	return TE_MODE_UNSUPPORTED;
}

static __always_inline __u32 te_supported_effects(__u32 backend_mode)
{
	if (backend_mode == TE_MODE_BLOCK)
		return (1U << TEFFECT_NOTIFY) |
		       (1U << TEFFECT_BLOCK) |
		       (1U << TEFFECT_KILL);
	return (1U << TEFFECT_NOTIFY) | (1U << TEFFECT_KILL);
}

static __always_inline int te_better_match(int candidate_rule, __u32 candidate_effect,
					   int current_rule, __u32 current_effect)
{
	return candidate_rule >= 0 &&
	       (current_rule < 0 || candidate_effect > current_effect);
}

static __always_inline int te_resolve_file_ref(__u32 ref_kind, const void *a,
					       const void *b, char *path,
					       int path_sz)
{
	switch (ref_kind) {
	case TE_REF_FILE:
		return file_path((struct file *)a, path, path_sz);
	case TE_REF_PATH:
		return path_to_str((const struct path *)a, path, path_sz);
	case TE_REF_PATH_DENTRY:
		return path_dentry_to_str((const struct path *)a, (struct dentry *)b,
					  path, path_sz);
	case TE_REF_USER_PATH:
		return bpf_probe_read_user_str(path, path_sz, a) > 0 ? 0 : -1;
	default:
		return -1;
	}
}

/* Resolve the file's object identity from the same ref. With a real inode
 * (LSM hooks) we use (i_ino, s_dev); the tracepoint USER_PATH path has no
 * inode, so we fall back to (0, fnv1a(path)) — identical to the old path-keyed
 * behavior. The key is fully zeroed first (HASH compares raw key bytes). */
static __always_inline void te_resolve_file_id(__u32 ref_kind, const void *a,
					       const void *b, const char *path,
					       struct file_id *fid)
{
	struct inode *inode = NULL;

	switch (ref_kind) {
	case TE_REF_FILE:
		inode = BPF_CORE_READ((struct file *)a, f_inode);
		break;
	case TE_REF_PATH:
		inode = BPF_CORE_READ((const struct path *)a, dentry, d_inode);
		break;
	case TE_REF_PATH_DENTRY:
		inode = BPF_CORE_READ((struct dentry *)b, d_inode);
		break;
	default:
		break; /* TE_REF_USER_PATH: no inode available */
	}
	fid->ino = 0;
	fid->dev = 0;
	fid->_pad = 0;
	if (inode) {
		fid->ino = BPF_CORE_READ(inode, i_ino);
		fid->dev = BPF_CORE_READ(inode, i_sb, s_dev);
	}
	if (!fid->ino)
		fid->ino = te_fnv1a(path);
}

static __always_inline int te_resolve_sockaddr(__u32 ref_kind, const void *addr,
					       __u32 *ip)
{
	struct sockaddr_in sa = {};
	u16 family = 0;

	if (ref_kind == TE_REF_SOCKADDR_USER) {
		if (bpf_probe_read_user(&family, sizeof(family), addr) < 0)
			return -1;
		if (family != AF_INET)
			return -1;
		if (bpf_probe_read_user(&sa, sizeof(sa), addr) < 0)
			return -1;
	} else {
		if (bpf_probe_read_kernel(&family, sizeof(family), addr) < 0)
			return -1;
		if (family != AF_INET)
			return -1;
		if (bpf_probe_read_kernel(&sa, sizeof(sa), addr) < 0)
			return -1;
	}
	*ip = sa.sin_addr.s_addr;
	return 0;
}

/* `fid` is the file object identity for TE_OBJ_FILE events (NULL otherwise);
 * it is only dereferenced in the FILE branches, which the verifier prunes for
 * the connect/exec programs (obj_kind is a compile-time constant per caller). */
static __always_inline int te_handle_event(struct te_event *ev, struct file_id *fid)
{
	__u64 labels = te_labels(ev->pid);
	struct te_rule_eval eval = {};
	const char *display = ev->display ? ev->display : ev->target;
	__u32 effect = TEFFECT_NOTIFY;
	__u32 action = TE_MODE_NOTIFY;
	__u64 matched_req = 0;
	int rid = -1;
	int candidate = -1;

	if (ev->obj_kind == TE_OBJ_FILE && (ev->access & TE_ACCESS_READ))
		labels |= te_file_labels(fid, ev->target);
	if (ev->obj_kind == TE_OBJ_ENDPOINT && (ev->access & TE_ACCESS_CONNECT))
		labels |= te_update_add(TOP_CONNECT, 0, ev->ip);

	if (ev->obj_kind == TE_OBJ_EXEC) {
		eval.pid = ev->pid;
		eval.labels = labels;
		eval.effect = TEFFECT_BLOCK;
		eval.effect_mask = te_supported_effects(ev->mode);
		eval.op = TOP_EXEC;
		eval.target = ev->target;
		rid = te_check_labels(&eval);
		effect = eval.effect;
		matched_req = eval.matched_req;
	} else if (ev->obj_kind == TE_OBJ_FILE) {
		eval.pid = ev->pid;
		eval.labels = labels;
		eval.effect = TEFFECT_BLOCK;
		eval.effect_mask = te_supported_effects(ev->mode);
		eval.target = ev->target;
		if (ev->access & TE_ACCESS_READ) {
			eval.op = TOP_OPEN;
			candidate = te_check_labels(&eval);
			if (te_better_match(candidate, eval.effect, rid, effect)) {
				rid = candidate;
				effect = eval.effect;
				matched_req = eval.matched_req;
			}
		}
		if (ev->access & TE_ACCESS_WRITE) {
			eval.effect = TEFFECT_BLOCK;
			eval.op = TOP_WRITE;
			candidate = te_check_labels(&eval);
			if (te_better_match(candidate, eval.effect, rid, effect)) {
				rid = candidate;
				effect = eval.effect;
				matched_req = eval.matched_req;
			}
		}
	} else if (ev->obj_kind == TE_OBJ_ENDPOINT) {
		eval.pid = ev->pid;
		eval.labels = labels;
		eval.effect = TEFFECT_BLOCK;
		eval.effect_mask = te_supported_effects(ev->mode);
		eval.op = TOP_CONNECT;
		eval.ip = ev->ip;
		rid = te_check_labels(&eval);
		effect = eval.effect;
		matched_req = eval.matched_req;
	}

	if (rid >= 0) {
		action = te_effect_mode(ev->mode, effect);
		if (action != TE_MODE_UNSUPPORTED)
			emit_violation(ev->pid, rid, display,
				       ev->obj_kind == TE_OBJ_ENDPOINT ? ev->ip : 0,
				       matched_req,
				       action == TE_MODE_BLOCK ||
					       (action == TE_MODE_KILL && ev->mode == TE_MODE_BLOCK),
				       action == TE_MODE_KILL, effect);
		if (action == TE_MODE_BLOCK)
			return -EPERM;
		if (action == TE_MODE_KILL) {
			bpf_send_signal(SIGKILL);
			if (ev->mode == TE_MODE_BLOCK)
				return -EPERM;
		}
	}

	if (ev->obj_kind == TE_OBJ_FILE) {
		if (ev->access & TE_ACCESS_READ)
			te_read(ev->pid, fid, ev->target);
		if (ev->access & TE_ACCESS_WRITE)
			te_write_flow(ev->pid, fid, ev->target);
	} else if (ev->obj_kind == TE_OBJ_ENDPOINT) {
		__u64 src_labels = te_update_add(TOP_CONNECT, 0, ev->ip);
		te_add_labels(ev->pid, src_labels);
		if (src_labels)
			te_record_proc_prov_mask(ev->pid, src_labels, TOP_CONNECT, 0, ev->ip);
		te_connect_flow(ev->ip, ev->pid);
	}

	return 0;
}

static __always_inline int te_handle_file_event(pid_t pid, const char *target,
						struct file_id *fid, __u32 access,
						__u32 mode)
{
	__u64 labels = te_labels(pid);
	struct te_rule_eval eval = {};
	__u32 effect = TEFFECT_NOTIFY;
	__u32 action = TE_MODE_NOTIFY;
	__u64 matched_req = 0;
	int rid = -1;
	int candidate = -1;

	if (access & TE_ACCESS_READ)
		labels |= te_file_labels(fid, target);

	eval.pid = pid;
	eval.labels = labels;
	eval.effect = TEFFECT_BLOCK;
	eval.effect_mask = te_supported_effects(mode);
	eval.target = target;
	if (access & TE_ACCESS_READ) {
		eval.op = TOP_OPEN;
		candidate = te_check_labels(&eval);
		if (te_better_match(candidate, eval.effect, rid, effect)) {
			rid = candidate;
			effect = eval.effect;
			matched_req = eval.matched_req;
		}
	}
	if (access & TE_ACCESS_WRITE) {
		eval.effect = TEFFECT_BLOCK;
		eval.op = TOP_WRITE;
		candidate = te_check_labels(&eval);
		if (te_better_match(candidate, eval.effect, rid, effect)) {
			rid = candidate;
			effect = eval.effect;
			matched_req = eval.matched_req;
		}
	}

	if (rid >= 0) {
		action = te_effect_mode(mode, effect);
		if (action != TE_MODE_UNSUPPORTED)
			emit_violation(pid, rid, target, 0, matched_req,
				       action == TE_MODE_BLOCK ||
					       (action == TE_MODE_KILL && mode == TE_MODE_BLOCK),
				       action == TE_MODE_KILL, effect);
		if (action == TE_MODE_BLOCK)
			return -EPERM;
		if (action == TE_MODE_KILL) {
			bpf_send_signal(SIGKILL);
			if (mode == TE_MODE_BLOCK)
				return -EPERM;
		}
	}

	if (access & TE_ACCESS_READ)
		te_read(pid, fid, target);
	if (access & TE_ACCESS_WRITE)
		te_write_flow(pid, fid, target);
	return 0;
}

static __always_inline int te_handle_file(__u32 ref_kind, const void *a,
					  const void *b, __u32 access,
					  __u32 mode)
{
	struct file_scratch *scratch = file_scratch_buf();
	pid_t pid;

	if ((mode == TE_MODE_BLOCK && !enforce_mode) ||
	    ((mode == TE_MODE_NOTIFY || mode == TE_MODE_KILL) && enforce_mode))
		return 0;
	if (!access)
		return 0;
	if (!scratch)
		return 0;
	__builtin_memset(scratch, 0, sizeof(*scratch));
	if (te_resolve_file_ref(ref_kind, a, b, scratch->path, sizeof(scratch->path)) < 0)
		return 0;
	te_resolve_file_id(ref_kind, a, b, scratch->path, &scratch->fid);

	pid = bpf_get_current_pid_tgid() >> 32;
	return te_handle_file_event(pid, scratch->path, &scratch->fid, access, mode);
}

static __always_inline int te_handle_net(__u32 ref_kind, const void *a,
					 const void *b, __u32 access,
					 __u32 mode)
{
	char target[TAINT_PAT_LEN] = {};
	struct te_event ev = {};
	__u32 ip = 0;

	(void)b;
	if ((mode == TE_MODE_BLOCK && !enforce_mode) ||
	    ((mode == TE_MODE_NOTIFY || mode == TE_MODE_KILL) && enforce_mode))
		return 0;
	if (!(access & TE_ACCESS_CONNECT))
		return 0;
	if (te_resolve_sockaddr(ref_kind, a, &ip) < 0)
		return 0;

	ev.pid = bpf_get_current_pid_tgid() >> 32;
	ev.obj_kind = TE_OBJ_ENDPOINT;
	ev.access = access;
	ev.mode = mode;
	ev.target = target;
	ev.ip = ip;
	return te_handle_event(&ev, 0);
}

static __always_inline int te_handle_channel(int fd, __u32 access, __u32 mode)
{
	struct file_scratch *scratch = file_scratch_buf();
	pid_t pid;

	if ((mode == TE_MODE_BLOCK && !enforce_mode) ||
	    ((mode == TE_MODE_NOTIFY || mode == TE_MODE_KILL) && enforce_mode))
		return 0;
	if (!scratch)
		return 0;
	__builtin_memset(scratch, 0, sizeof(*scratch));
	if (!chan_fd_target(fd, access, scratch->path, sizeof(scratch->path)))
		return 0;

	scratch->fid.ino = te_fnv1a(scratch->path);
	pid = bpf_get_current_pid_tgid() >> 32;
	return te_handle_file_event(pid, scratch->path, &scratch->fid, access, mode);
}

static __always_inline int te_handle_exec(__u32 ref_kind, const void *a,
					  const void *b, __u32 mode)
{
	const char *target;
	const char *shown;
	struct te_event ev = {};

	if ((mode == TE_MODE_BLOCK && !enforce_mode) ||
	    ((mode == TE_MODE_NOTIFY || mode == TE_MODE_KILL) && enforce_mode))
		return 0;
	if (ref_kind == TE_REF_BPRM) {
		struct exec_scratch *scratch = exec_scratch_buf();
		if (!scratch)
			return 0;
		__builtin_memset(scratch, 0, sizeof(*scratch));
		if (bprm_basename((struct linux_binprm *)a, scratch->match,
				  sizeof(scratch->match)) < 0)
			return 0;
		if (bprm_filename((struct linux_binprm *)a, scratch->display,
				  sizeof(scratch->display)) < 0)
			__builtin_memcpy(scratch->display, scratch->match,
					 sizeof(scratch->match));
		target = scratch->match;
		shown = scratch->display;
	} else if (ref_kind == TE_REF_STRINGS) {
		target = a;
		shown = b ? b : a;
		if (!target)
			return 0;
	} else {
		return 0;
	}

	ev.pid = bpf_get_current_pid_tgid() >> 32;
	ev.obj_kind = TE_OBJ_EXEC;
	ev.access = TE_ACCESS_EXEC;
	ev.mode = mode;
	ev.target = target;
	ev.display = shown;
	return te_handle_event(&ev, 0);
}

SEC("lsm/bprm_check_security")
int BPF_PROG(enforce_bprm_check_security, struct linux_binprm *bprm)
{
	return te_handle_exec(TE_REF_BPRM, bprm, 0, TE_MODE_BLOCK);
}

SEC("lsm/file_open")
int BPF_PROG(enforce_file_open, struct file *file)
{
	return te_handle_file(TE_REF_FILE, file, 0,
			      te_access_from_open_flags(BPF_CORE_READ(file, f_flags)),
			      TE_MODE_BLOCK);
}

SEC("lsm/file_permission")
int BPF_PROG(enforce_file_permission, struct file *file, int mask)
{
	(void)file;
	(void)mask;
	return 0;
}

SEC("lsm/file_truncate")
int BPF_PROG(enforce_file_truncate, struct file *file)
{
	(void)file;
	return 0;
}

SEC("lsm/path_truncate")
int BPF_PROG(enforce_path_truncate, const struct path *path_arg)
{
	(void)path_arg;
	return 0;
}

SEC("lsm/path_unlink")
int BPF_PROG(enforce_path_unlink, const struct path *dir, struct dentry *dentry)
{
	(void)dir;
	(void)dentry;
	return 0;
}

SEC("lsm/path_rename")
int BPF_PROG(enforce_path_rename, const struct path *old_dir,
	     struct dentry *old_dentry, const struct path *new_dir,
	     struct dentry *new_dentry, unsigned int flags)
{
	(void)old_dir;
	(void)old_dentry;
	(void)new_dir;
	(void)new_dentry;
	(void)flags;
	return 0;
}

SEC("lsm/socket_connect")
int BPF_PROG(enforce_socket_connect, struct socket *sock, struct sockaddr *address,
	     int addrlen)
{
	(void)sock;
	(void)addrlen;
	return te_handle_net(TE_REF_SOCKADDR_KERN, address, 0,
			     TE_ACCESS_CONNECT, TE_MODE_BLOCK);
}

SEC("tp/sched/sched_process_fork")
int handle_fork(struct trace_event_raw_sched_process_fork *ctx)
{
	te_fork(ctx->parent_pid, ctx->child_pid);
	return 0;
}

SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
	pid_t pid = bpf_get_current_pid_tgid() >> 32;
	struct task_struct *task = (struct task_struct *)bpf_get_current_task();
	struct exec_scratch *scratch = exec_scratch_buf();
	unsigned fname_off;
	int alen = 0;

	if (!scratch)
		return 0;
	__builtin_memset(scratch, 0, sizeof(*scratch));

	bpf_get_current_comm(&scratch->match, TASK_COMM_LEN);
	te_exec_update(pid, scratch->match);

	/* read argv blob (NUL-separated) into per-CPU scratch, then tokenize into
	 * fixed slots there for @arg matching — see te_tokenize_args_eng / taint_arg_match. */
	struct te_argslots *as = te_argslots_buf();
	if (as) {
		struct mm_struct *mm = BPF_CORE_READ(task, mm);
		unsigned long a0 = BPF_CORE_READ(mm, arg_start);
		unsigned long a1 = BPF_CORE_READ(mm, arg_end);
		unsigned long len = a1 - a0;
		if (len > TAINT_ARGV_CAP - 1)
			len = TAINT_ARGV_CAP - 1;
		__builtin_memset(as, 0, sizeof(*as));
		if (len > 0 && bpf_probe_read_user(as->blob, len, (void *)a0) == 0)
			alen = (int)len;
		te_tokenize_args_eng(alen);
	}

	fname_off = ctx->__data_loc_filename & 0xFFFF;
	bpf_probe_read_str(scratch->display, sizeof(scratch->display), (void *)ctx + fname_off);
	te_handle_exec(TE_REF_STRINGS, scratch->match, scratch->display, te_tracepoint_mode());
	return 0;
}

SEC("tp/sched/sched_process_exit")
int handle_exit(struct trace_event_raw_sched_process_template *ctx)
{
	u64 id = bpf_get_current_pid_tgid();
	pid_t pid = id >> 32;
	struct task_struct *task = (struct task_struct *)bpf_get_current_task();
	int exit_code = BPF_CORE_READ(task, exit_code);

	(void)ctx;
	if (pid != (u32)id)
		return 0;
	te_exit(pid, exit_code);
	return 0;
}

/* Stash the path pointer + flags; the actual handling happens at sys_exit, once
 * the path page is resident (see ts_openpend). Tracking opens at exit also means
 * we only act on opens that actually entered the kernel, which is fine for the
 * notify/kill model (kill still terminates the offending process). */
static __always_inline int stash_open(const void *path_ptr, unsigned int flags)
{
	__u64 tid = bpf_get_current_pid_tgid();
	struct open_pend p = { .path_ptr = (__u64)path_ptr, .flags = flags };

	bpf_map_update_elem(&ts_openpend, &tid, &p, BPF_ANY);
	return 0;
}

static __always_inline int handle_open_exit(long ret)
{
	__u64 tid = bpf_get_current_pid_tgid();
	struct open_pend *p = bpf_map_lookup_elem(&ts_openpend, &tid);
	int rc = 0;

	if (!p)
		return 0;
	/* On success the kernel has copied the path in, so the user page is now
	 * resident and the read in te_handle_file is reliable. */
	if (ret >= 0)
		rc = te_handle_file(TE_REF_USER_PATH, (const void *)p->path_ptr, 0,
				    te_access_from_open_flags(p->flags),
				    te_tracepoint_mode());
	bpf_map_delete_elem(&ts_openpend, &tid);
	return rc;
}

SEC("tp/syscalls/sys_enter_openat")
int trace_openat(struct trace_event_raw_sys_enter *ctx)
{
	return stash_open((const void *)ctx->args[1], (unsigned int)ctx->args[2]);
}

SEC("tp/syscalls/sys_exit_openat")
int trace_openat_exit(struct trace_event_raw_sys_exit *ctx)
{
	return handle_open_exit(ctx->ret);
}

SEC("tp/syscalls/sys_enter_open")
int trace_open(struct trace_event_raw_sys_enter *ctx)
{
	return stash_open((const void *)ctx->args[0], (unsigned int)ctx->args[1]);
}

SEC("tp/syscalls/sys_exit_open")
int trace_open_exit(struct trace_event_raw_sys_exit *ctx)
{
	return handle_open_exit(ctx->ret);
}

/* openat2(dfd, path, struct open_how *, size): flags live in open_how, not a
 * scalar arg. Without this hook a write/read via openat2 bypasses detection. */
SEC("tp/syscalls/sys_enter_openat2")
int trace_openat2(struct trace_event_raw_sys_enter *ctx)
{
	struct open_how how = {};
	bpf_probe_read_user(&how, sizeof(how), (void *)ctx->args[2]);
	return stash_open((const void *)ctx->args[1], (unsigned int)how.flags);
}
SEC("tp/syscalls/sys_exit_openat2")
int trace_openat2_exit(struct trace_event_raw_sys_exit *ctx)
{
	return handle_open_exit(ctx->ret);
}

/* creat(path, mode) == open(path, O_WRONLY|O_CREAT|O_TRUNC): a file write. */
SEC("tp/syscalls/sys_enter_creat")
int trace_creat(struct trace_event_raw_sys_enter *ctx)
{
	return stash_open((const void *)ctx->args[0], O_WRONLY | O_CREAT | O_TRUNC);
}
SEC("tp/syscalls/sys_exit_creat")
int trace_creat_exit(struct trace_event_raw_sys_exit *ctx)
{
	return handle_open_exit(ctx->ret);
}

/* truncate(path, len): a file write (size change). */
SEC("tp/syscalls/sys_enter_truncate")
int trace_truncate(struct trace_event_raw_sys_enter *ctx)
{
	return stash_open((const void *)ctx->args[0], O_WRONLY);
}
SEC("tp/syscalls/sys_exit_truncate")
int trace_truncate_exit(struct trace_event_raw_sys_exit *ctx)
{
	return handle_open_exit(ctx->ret);
}
/* renameat2 is already hooked below (sys_enter_renameat2 -> write on new path). */

SEC("tp/syscalls/sys_enter_unlink")
int trace_unlink(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_file(TE_REF_USER_PATH, (const void *)ctx->args[0], 0,
			      TE_ACCESS_WRITE, te_tracepoint_mode());
}
SEC("tp/syscalls/sys_enter_unlinkat")
int trace_unlinkat(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_file(TE_REF_USER_PATH, (const void *)ctx->args[1], 0,
			      TE_ACCESS_WRITE, te_tracepoint_mode());
}
SEC("tp/syscalls/sys_enter_rename")
int trace_rename(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_file(TE_REF_USER_PATH, (const void *)ctx->args[1], 0,
			      TE_ACCESS_WRITE, te_tracepoint_mode());
}
SEC("tp/syscalls/sys_enter_renameat")
int trace_renameat(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_file(TE_REF_USER_PATH, (const void *)ctx->args[3], 0,
			      TE_ACCESS_WRITE, te_tracepoint_mode());
}
SEC("tp/syscalls/sys_enter_renameat2")
int trace_renameat2(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_file(TE_REF_USER_PATH, (const void *)ctx->args[3], 0,
			      TE_ACCESS_WRITE, te_tracepoint_mode());
}

/* connect: numeric IPv4 matching (compiler lowers host/IP patterns to net+mask;
 * no in-kernel string formatting, so no verifier-rejected pointer arithmetic).
 * The reported IP is formatted by the userspace loader from conn_ip. */
SEC("tp/syscalls/sys_enter_connect")
int trace_connect(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_net(TE_REF_SOCKADDR_USER, (const void *)ctx->args[1], 0,
			     TE_ACCESS_CONNECT, te_tracepoint_mode());
}

SEC("tp/syscalls/sys_enter_read")
int trace_read(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_channel((int)ctx->args[0], TE_ACCESS_READ, te_tracepoint_mode());
}

SEC("tp/syscalls/sys_enter_write")
int trace_write(struct trace_event_raw_sys_enter *ctx)
{
	return te_handle_channel((int)ctx->args[0], TE_ACCESS_WRITE, te_tracepoint_mode());
}

SEC("tp/syscalls/sys_enter_getpid")
int cap_drain_tick(struct trace_event_raw_sys_enter *ctx)
{
	(void)ctx;
	cap_drain_current();
	return 0;
}
