| // 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 umd_info *info = &bpfilter_ops.info; |
| struct pid *tgid = info->tgid; |
| |
| if (tgid) { |
| kill_pid(tgid, SIGKILL, 1); |
| wait_event(tgid->wait_pidfd, thread_group_exited(tgid)); |
| bpfilter_umh_cleanup(info); |
| } |
| } |
| |
| static void __stop_umh(void) |
| { |
| if (IS_ENABLED(CONFIG_INET)) |
| shutdown_umh(); |
| } |
| |
| static int bpfilter_send_req(struct mbox_request *req) |
| { |
| struct mbox_reply reply; |
| loff_t pos = 0; |
| ssize_t n; |
| |
| if (!bpfilter_ops.info.tgid) |
| return -EFAULT; |
| pos = 0; |
| n = kernel_write(bpfilter_ops.info.pipe_to_umh, req, sizeof(*req), |
| &pos); |
| if (n != sizeof(*req)) { |
| pr_err("write fail %zd\n", n); |
| goto stop; |
| } |
| 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); |
| goto stop; |
| } |
| return reply.status; |
| stop: |
| __stop_umh(); |
| return -EFAULT; |
| } |
| |
| static int bpfilter_process_sockopt(struct sock *sk, int optname, |
| sockptr_t optval, unsigned int optlen, |
| bool is_set) |
| { |
| struct mbox_request req = { |
| .is_set = is_set, |
| .pid = current->pid, |
| .cmd = optname, |
| .addr = (uintptr_t)optval.user, |
| .len = optlen, |
| }; |
| if (uaccess_kernel() || sockptr_is_kernel(optval)) { |
| pr_err("kernel access not supported\n"); |
| return -EFAULT; |
| } |
| return bpfilter_send_req(&req); |
| } |
| |
| static int start_umh(void) |
| { |
| struct mbox_request req = { .pid = current->pid }; |
| int err; |
| |
| /* fork usermode process */ |
| err = fork_usermode_driver(&bpfilter_ops.info); |
| if (err) |
| return err; |
| pr_info("Loaded bpfilter_umh pid %d\n", pid_nr(bpfilter_ops.info.tgid)); |
| |
| /* health check that usermode process started correctly */ |
| if (bpfilter_send_req(&req) != 0) { |
| shutdown_umh(); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int __init load_umh(void) |
| { |
| int err; |
| |
| err = umd_load_blob(&bpfilter_ops.info, |
| &bpfilter_umh_start, |
| &bpfilter_umh_end - &bpfilter_umh_start); |
| if (err) |
| return err; |
| |
| mutex_lock(&bpfilter_ops.lock); |
| err = start_umh(); |
| if (!err && IS_ENABLED(CONFIG_INET)) { |
| bpfilter_ops.sockopt = &bpfilter_process_sockopt; |
| bpfilter_ops.start = &start_umh; |
| } |
| mutex_unlock(&bpfilter_ops.lock); |
| if (err) |
| umd_unload_blob(&bpfilter_ops.info); |
| 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); |
| |
| umd_unload_blob(&bpfilter_ops.info); |
| } |
| module_init(load_umh); |
| module_exit(fini_umh); |
| MODULE_LICENSE("GPL"); |
| MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); |