| // SPDX-License-Identifier: GPL-2.0 |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/umh.h> |
| #include <linux/bpfilter.h> |
| #include <linux/sched.h> |
| #include <linux/sched/signal.h> |
| #include <linux/fs.h> |
| #include <linux/file.h> |
| #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) { |
| send_sig(SIGKILL, tsk, 1); |
| 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"); |