// SPDX-License-Identifier: GPL-2.0 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include "msgfmt.h" extern char bpfilter_umh_start; extern char bpfilter_umh_end; static void shutdown_umh(void) { struct task_struct *tsk; if (bpfilter_ops.stop) return; tsk = get_pid_task(find_vpid(bpfilter_ops.info.pid), PIDTYPE_PID); if (tsk) { force_sig(SIGKILL, tsk); put_task_struct(tsk); } } static void __stop_umh(void) { if (IS_ENABLED(CONFIG_INET)) shutdown_umh(); } static int __bpfilter_process_sockopt(struct sock *sk, int optname, char __user *optval, unsigned int optlen, bool is_set) { struct mbox_request req; struct mbox_reply reply; loff_t pos; ssize_t n; int ret = -EFAULT; req.is_set = is_set; req.pid = current->pid; req.cmd = optname; req.addr = (long __force __user)optval; req.len = optlen; if (!bpfilter_ops.info.pid) goto out; n = __kernel_write(bpfilter_ops.info.pipe_to_umh, &req, sizeof(req), &pos); if (n != sizeof(req)) { pr_err("write fail %zd\n", n); __stop_umh(); ret = -EFAULT; goto out; } pos = 0; n = kernel_read(bpfilter_ops.info.pipe_from_umh, &reply, sizeof(reply), &pos); if (n != sizeof(reply)) { pr_err("read fail %zd\n", n); __stop_umh(); ret = -EFAULT; goto out; } ret = reply.status; out: return ret; } static int start_umh(void) { int err; /* fork usermode process */ err = fork_usermode_blob(&bpfilter_umh_start, &bpfilter_umh_end - &bpfilter_umh_start, &bpfilter_ops.info); if (err) return err; bpfilter_ops.stop = false; pr_info("Loaded bpfilter_umh pid %d\n", bpfilter_ops.info.pid); /* health check that usermode process started correctly */ if (__bpfilter_process_sockopt(NULL, 0, NULL, 0, 0) != 0) { shutdown_umh(); return -EFAULT; } return 0; } static int __init load_umh(void) { int err; mutex_lock(&bpfilter_ops.lock); if (!bpfilter_ops.stop) { err = -EFAULT; goto out; } err = start_umh(); if (!err && IS_ENABLED(CONFIG_INET)) { bpfilter_ops.sockopt = &__bpfilter_process_sockopt; bpfilter_ops.start = &start_umh; } out: mutex_unlock(&bpfilter_ops.lock); return err; } static void __exit fini_umh(void) { mutex_lock(&bpfilter_ops.lock); if (IS_ENABLED(CONFIG_INET)) { shutdown_umh(); bpfilter_ops.start = NULL; bpfilter_ops.sockopt = NULL; } mutex_unlock(&bpfilter_ops.lock); } module_init(load_umh); module_exit(fini_umh); MODULE_LICENSE("GPL");