Commit f06b71fe authored by Eric W. Biederman's avatar Eric W. Biederman

Make the user mode driver code a better citizen

This is the third round of my changeset to split the user mode driver
code from the user mode helper code, and to make the code use common
facilities to get things done instead of recreating them just
for the user mode driver code.

I have split the changes into small enough pieces so they should be
easily readable and testable.

The changes lean into the preexisting interfaces in the kernel and
remove special cases for user mode driver code in favor of solutions
that don't need special cases.  This results in smaller code with fewer
bugs.

At a practical level this removes the maintenance burden of the user
mode drivers from the user mode helper code and from exec as the special
cases are removed.

Similarly the LSM interaction bugs are fixed by not having unnecessary
special cases for user mode drivers.

I have tested thes changes by booting with the code compiled in and
by killing "bpfilter_umh" and "running iptables -vnL" to restart
the userspace driver, also by running "while true; do iptables -L;rmmod
bpfilter; done" to verify the module load and unload work properly.

I have compiled tested each change with and without CONFIG_BPFILTER
enabled.

From v2 to v3 I have made two siginficant changes.
- I factored thread_group_exit out of pidfd_poll to allow the test
  to be used by the bpfilter code.
- I renamed umd.c and umd.h to usermode_driver.c and usermode_driver.h
  respectively.

I made a few very small changes from v1 to v2:
- Updated the function name in a comment when the function is renamed
- Moved some more code so that the the !CONFIG_BPFILTER case continues
  to compile when I moved the code into umd.c
- A fix for the module loading case to really flush the file descriptor.
- Removed split_argv entirely from fork_usermode_driver.
  There was nothing to split so it was just confusing.

Please let me know if you see any bugs.  Once the code review is
finished I plan to place the code in a non-rebasing branch
so I can pull it into my tree and so it can also be pulled into
the bpf-next tree.

v1: https://lkml.kernel.org/r/87pn9mgfc2.fsf_-_@x220.int.ebiederm.org
v2: https://lkml.kernel.org/r/87bll17ili.fsf_-_@x220.int.ebiederm.org

Eric W. Biederman (16):
      umh: Capture the pid in umh_pipe_setup
      umh: Move setting PF_UMH into umh_pipe_setup
      umh: Rename the user mode driver helpers for clarity
      umh: Remove call_usermodehelper_setup_file.
      umh: Separate the user mode driver and the user mode helper support
      umd: For clarity rename umh_info umd_info
      umd: Rename umd_info.cmdline umd_info.driver_name
      umd: Transform fork_usermode_blob into fork_usermode_driver
      umh: Stop calling do_execve_file
      exec: Remove do_execve_file
      bpfilter: Move bpfilter_umh back into init data
      umd: Track user space drivers with struct pid
      exit: Factor thread_group_exited out of pidfd_poll
      bpfilter: Take advantage of the facilities of struct pid
      umd: Remove exit_umh
      umd: Stop using split_argv

Link: https://lkml.kernel.org/r/87y2o1swee.fsf_-_@x220.int.ebiederm.orgSigned-off-by: default avatar"Eric W. Biederman" <ebiederm@xmission.com>
parents b3a9e3b9 33c32601
...@@ -1818,13 +1818,14 @@ static int exec_binprm(struct linux_binprm *bprm) ...@@ -1818,13 +1818,14 @@ static int exec_binprm(struct linux_binprm *bprm)
/* /*
* sys_execve() executes a new program. * sys_execve() executes a new program.
*/ */
static int __do_execve_file(int fd, struct filename *filename, static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv, struct user_arg_ptr argv,
struct user_arg_ptr envp, struct user_arg_ptr envp,
int flags, struct file *file) int flags)
{ {
char *pathbuf = NULL; char *pathbuf = NULL;
struct linux_binprm *bprm; struct linux_binprm *bprm;
struct file *file;
struct files_struct *displaced; struct files_struct *displaced;
int retval; int retval;
...@@ -1863,8 +1864,7 @@ static int __do_execve_file(int fd, struct filename *filename, ...@@ -1863,8 +1864,7 @@ static int __do_execve_file(int fd, struct filename *filename,
check_unsafe_exec(bprm); check_unsafe_exec(bprm);
current->in_execve = 1; current->in_execve = 1;
if (!file) file = do_open_execat(fd, filename, flags);
file = do_open_execat(fd, filename, flags);
retval = PTR_ERR(file); retval = PTR_ERR(file);
if (IS_ERR(file)) if (IS_ERR(file))
goto out_unmark; goto out_unmark;
...@@ -1872,9 +1872,7 @@ static int __do_execve_file(int fd, struct filename *filename, ...@@ -1872,9 +1872,7 @@ static int __do_execve_file(int fd, struct filename *filename,
sched_exec(); sched_exec();
bprm->file = file; bprm->file = file;
if (!filename) { if (fd == AT_FDCWD || filename->name[0] == '/') {
bprm->filename = "none";
} else if (fd == AT_FDCWD || filename->name[0] == '/') {
bprm->filename = filename->name; bprm->filename = filename->name;
} else { } else {
if (filename->name[0] == '\0') if (filename->name[0] == '\0')
...@@ -1935,8 +1933,7 @@ static int __do_execve_file(int fd, struct filename *filename, ...@@ -1935,8 +1933,7 @@ static int __do_execve_file(int fd, struct filename *filename,
task_numa_free(current, false); task_numa_free(current, false);
free_bprm(bprm); free_bprm(bprm);
kfree(pathbuf); kfree(pathbuf);
if (filename) putname(filename);
putname(filename);
if (displaced) if (displaced)
put_files_struct(displaced); put_files_struct(displaced);
return retval; return retval;
...@@ -1967,27 +1964,10 @@ static int __do_execve_file(int fd, struct filename *filename, ...@@ -1967,27 +1964,10 @@ static int __do_execve_file(int fd, struct filename *filename,
if (displaced) if (displaced)
reset_files_struct(displaced); reset_files_struct(displaced);
out_ret: out_ret:
if (filename) putname(filename);
putname(filename);
return retval; return retval;
} }
static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags)
{
return __do_execve_file(fd, filename, argv, envp, flags, NULL);
}
int do_execve_file(struct file *file, void *__argv, void *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return __do_execve_file(AT_FDCWD, NULL, argv, envp, 0, file);
}
int do_execve(struct filename *filename, int do_execve(struct filename *filename,
const char __user *const __user *__argv, const char __user *const __user *__argv,
const char __user *const __user *__envp) const char __user *const __user *__envp)
......
...@@ -141,6 +141,5 @@ extern int do_execveat(int, struct filename *, ...@@ -141,6 +141,5 @@ extern int do_execveat(int, struct filename *,
const char __user * const __user *, const char __user * const __user *,
const char __user * const __user *, const char __user * const __user *,
int); int);
int do_execve_file(struct file *file, void *__argv, void *__envp);
#endif /* _LINUX_BINFMTS_H */ #endif /* _LINUX_BINFMTS_H */
...@@ -3,22 +3,23 @@ ...@@ -3,22 +3,23 @@
#define _LINUX_BPFILTER_H #define _LINUX_BPFILTER_H
#include <uapi/linux/bpfilter.h> #include <uapi/linux/bpfilter.h>
#include <linux/umh.h> #include <linux/usermode_driver.h>
struct sock; struct sock;
int bpfilter_ip_set_sockopt(struct sock *sk, int optname, char __user *optval, int bpfilter_ip_set_sockopt(struct sock *sk, int optname, char __user *optval,
unsigned int optlen); unsigned int optlen);
int bpfilter_ip_get_sockopt(struct sock *sk, int optname, char __user *optval, int bpfilter_ip_get_sockopt(struct sock *sk, int optname, char __user *optval,
int __user *optlen); int __user *optlen);
void bpfilter_umh_cleanup(struct umd_info *info);
struct bpfilter_umh_ops { struct bpfilter_umh_ops {
struct umh_info info; struct umd_info info;
/* since ip_getsockopt() can run in parallel, serialize access to umh */ /* since ip_getsockopt() can run in parallel, serialize access to umh */
struct mutex lock; struct mutex lock;
int (*sockopt)(struct sock *sk, int optname, int (*sockopt)(struct sock *sk, int optname,
char __user *optval, char __user *optval,
unsigned int optlen, bool is_set); unsigned int optlen, bool is_set);
int (*start)(void); int (*start)(void);
bool stop;
}; };
extern struct bpfilter_umh_ops bpfilter_ops; extern struct bpfilter_umh_ops bpfilter_ops;
#endif #endif
...@@ -1511,7 +1511,6 @@ extern struct pid *cad_pid; ...@@ -1511,7 +1511,6 @@ extern struct pid *cad_pid;
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */ #define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */ #define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_UMH 0x02000000 /* I'm an Usermodehelper process */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */ #define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */ #define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */ #define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */
...@@ -2020,14 +2019,6 @@ static inline void rseq_execve(struct task_struct *t) ...@@ -2020,14 +2019,6 @@ static inline void rseq_execve(struct task_struct *t)
#endif #endif
void __exit_umh(struct task_struct *tsk);
static inline void exit_umh(struct task_struct *tsk)
{
if (unlikely(tsk->flags & PF_UMH))
__exit_umh(tsk);
}
#ifdef CONFIG_DEBUG_RSEQ #ifdef CONFIG_DEBUG_RSEQ
void rseq_syscall(struct pt_regs *regs); void rseq_syscall(struct pt_regs *regs);
......
...@@ -674,6 +674,8 @@ static inline int thread_group_empty(struct task_struct *p) ...@@ -674,6 +674,8 @@ static inline int thread_group_empty(struct task_struct *p)
#define delay_group_leader(p) \ #define delay_group_leader(p) \
(thread_group_leader(p) && !thread_group_empty(p)) (thread_group_leader(p) && !thread_group_empty(p))
extern bool thread_group_exited(struct pid *pid);
extern struct sighand_struct *__lock_task_sighand(struct task_struct *task, extern struct sighand_struct *__lock_task_sighand(struct task_struct *task,
unsigned long *flags); unsigned long *flags);
......
...@@ -22,10 +22,8 @@ struct subprocess_info { ...@@ -22,10 +22,8 @@ struct subprocess_info {
const char *path; const char *path;
char **argv; char **argv;
char **envp; char **envp;
struct file *file;
int wait; int wait;
int retval; int retval;
pid_t pid;
int (*init)(struct subprocess_info *info, struct cred *new); int (*init)(struct subprocess_info *info, struct cred *new);
void (*cleanup)(struct subprocess_info *info); void (*cleanup)(struct subprocess_info *info);
void *data; void *data;
...@@ -40,19 +38,6 @@ call_usermodehelper_setup(const char *path, char **argv, char **envp, ...@@ -40,19 +38,6 @@ call_usermodehelper_setup(const char *path, char **argv, char **envp,
int (*init)(struct subprocess_info *info, struct cred *new), int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *), void *data); void (*cleanup)(struct subprocess_info *), void *data);
struct subprocess_info *call_usermodehelper_setup_file(struct file *file,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *), void *data);
struct umh_info {
const char *cmdline;
struct file *pipe_to_umh;
struct file *pipe_from_umh;
struct list_head list;
void (*cleanup)(struct umh_info *info);
pid_t pid;
};
int fork_usermode_blob(void *data, size_t len, struct umh_info *info);
extern int extern int
call_usermodehelper_exec(struct subprocess_info *info, int wait); call_usermodehelper_exec(struct subprocess_info *info, int wait);
......
#ifndef __LINUX_USERMODE_DRIVER_H__
#define __LINUX_USERMODE_DRIVER_H__
#include <linux/umh.h>
#include <linux/path.h>
struct umd_info {
const char *driver_name;
struct file *pipe_to_umh;
struct file *pipe_from_umh;
struct path wd;
struct pid *tgid;
};
int umd_load_blob(struct umd_info *info, const void *data, size_t len);
int umd_unload_blob(struct umd_info *info);
int fork_usermode_driver(struct umd_info *info);
#endif /* __LINUX_USERMODE_DRIVER_H__ */
...@@ -12,6 +12,7 @@ obj-y = fork.o exec_domain.o panic.o \ ...@@ -12,6 +12,7 @@ obj-y = fork.o exec_domain.o panic.o \
notifier.o ksysfs.o cred.o reboot.o \ notifier.o ksysfs.o cred.o reboot.o \
async.o range.o smpboot.o ucount.o async.o range.o smpboot.o ucount.o
obj-$(CONFIG_BPFILTER) += usermode_driver.o
obj-$(CONFIG_MODULES) += kmod.o obj-$(CONFIG_MODULES) += kmod.o
obj-$(CONFIG_MULTIUSER) += groups.o obj-$(CONFIG_MULTIUSER) += groups.o
......
...@@ -804,7 +804,6 @@ void __noreturn do_exit(long code) ...@@ -804,7 +804,6 @@ void __noreturn do_exit(long code)
exit_task_namespaces(tsk); exit_task_namespaces(tsk);
exit_task_work(tsk); exit_task_work(tsk);
exit_thread(tsk); exit_thread(tsk);
exit_umh(tsk);
/* /*
* Flush inherited counters to the parent - before the parent * Flush inherited counters to the parent - before the parent
...@@ -1711,6 +1710,30 @@ COMPAT_SYSCALL_DEFINE5(waitid, ...@@ -1711,6 +1710,30 @@ COMPAT_SYSCALL_DEFINE5(waitid,
} }
#endif #endif
/**
* thread_group_exited - check that a thread group has exited
* @pid: tgid of thread group to be checked.
*
* Test if the thread group represented by tgid has exited (all
* threads are zombies, dead or completely gone).
*
* Return: true if the thread group has exited. false otherwise.
*/
bool thread_group_exited(struct pid *pid)
{
struct task_struct *task;
bool exited;
rcu_read_lock();
task = pid_task(pid, PIDTYPE_PID);
exited = !task ||
(READ_ONCE(task->exit_state) && thread_group_empty(task));
rcu_read_unlock();
return exited;
}
EXPORT_SYMBOL(thread_group_exited);
__weak void abort(void) __weak void abort(void)
{ {
BUG(); BUG();
......
...@@ -1787,22 +1787,18 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f) ...@@ -1787,22 +1787,18 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
*/ */
static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts) static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
{ {
struct task_struct *task;
struct pid *pid = file->private_data; struct pid *pid = file->private_data;
__poll_t poll_flags = 0; __poll_t poll_flags = 0;
poll_wait(file, &pid->wait_pidfd, pts); poll_wait(file, &pid->wait_pidfd, pts);
rcu_read_lock();
task = pid_task(pid, PIDTYPE_PID);
/* /*
* Inform pollers only when the whole thread group exits. * Inform pollers only when the whole thread group exits.
* If the thread group leader exits before all other threads in the * If the thread group leader exits before all other threads in the
* group, then poll(2) should block, similar to the wait(2) family. * group, then poll(2) should block, similar to the wait(2) family.
*/ */
if (!task || (task->exit_state && thread_group_empty(task))) if (thread_group_exited(pid))
poll_flags = EPOLLIN | EPOLLRDNORM; poll_flags = EPOLLIN | EPOLLRDNORM;
rcu_read_unlock();
return poll_flags; return poll_flags;
} }
......
...@@ -26,8 +26,6 @@ ...@@ -26,8 +26,6 @@
#include <linux/ptrace.h> #include <linux/ptrace.h>
#include <linux/async.h> #include <linux/async.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/shmem_fs.h>
#include <linux/pipe_fs_i.h>
#include <trace/events/module.h> #include <trace/events/module.h>
...@@ -38,8 +36,6 @@ static kernel_cap_t usermodehelper_bset = CAP_FULL_SET; ...@@ -38,8 +36,6 @@ static kernel_cap_t usermodehelper_bset = CAP_FULL_SET;
static kernel_cap_t usermodehelper_inheritable = CAP_FULL_SET; static kernel_cap_t usermodehelper_inheritable = CAP_FULL_SET;
static DEFINE_SPINLOCK(umh_sysctl_lock); static DEFINE_SPINLOCK(umh_sysctl_lock);
static DECLARE_RWSEM(umhelper_sem); static DECLARE_RWSEM(umhelper_sem);
static LIST_HEAD(umh_list);
static DEFINE_MUTEX(umh_list_lock);
static void call_usermodehelper_freeinfo(struct subprocess_info *info) static void call_usermodehelper_freeinfo(struct subprocess_info *info)
{ {
...@@ -102,16 +98,9 @@ static int call_usermodehelper_exec_async(void *data) ...@@ -102,16 +98,9 @@ static int call_usermodehelper_exec_async(void *data)
commit_creds(new); commit_creds(new);
sub_info->pid = task_pid_nr(current); retval = do_execve(getname_kernel(sub_info->path),
if (sub_info->file) { (const char __user *const __user *)sub_info->argv,
retval = do_execve_file(sub_info->file, (const char __user *const __user *)sub_info->envp);
sub_info->argv, sub_info->envp);
if (!retval)
current->flags |= PF_UMH;
} else
retval = do_execve(getname_kernel(sub_info->path),
(const char __user *const __user *)sub_info->argv,
(const char __user *const __user *)sub_info->envp);
out: out:
sub_info->retval = retval; sub_info->retval = retval;
/* /*
...@@ -405,140 +394,6 @@ struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv, ...@@ -405,140 +394,6 @@ struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
} }
EXPORT_SYMBOL(call_usermodehelper_setup); EXPORT_SYMBOL(call_usermodehelper_setup);
struct subprocess_info *call_usermodehelper_setup_file(struct file *file,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *info), void *data)
{
struct subprocess_info *sub_info;
struct umh_info *info = data;
const char *cmdline = (info->cmdline) ? info->cmdline : "usermodehelper";
sub_info = kzalloc(sizeof(struct subprocess_info), GFP_KERNEL);
if (!sub_info)
return NULL;
sub_info->argv = argv_split(GFP_KERNEL, cmdline, NULL);
if (!sub_info->argv) {
kfree(sub_info);
return NULL;
}
INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);
sub_info->path = "none";
sub_info->file = file;
sub_info->init = init;
sub_info->cleanup = cleanup;
sub_info->data = data;
return sub_info;
}
static int umh_pipe_setup(struct subprocess_info *info, struct cred *new)
{
struct umh_info *umh_info = info->data;
struct file *from_umh[2];
struct file *to_umh[2];
int err;
/* create pipe to send data to umh */
err = create_pipe_files(to_umh, 0);
if (err)
return err;
err = replace_fd(0, to_umh[0], 0);
fput(to_umh[0]);
if (err < 0) {
fput(to_umh[1]);
return err;
}
/* create pipe to receive data from umh */
err = create_pipe_files(from_umh, 0);
if (err) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
return err;
}
err = replace_fd(1, from_umh[1], 0);
fput(from_umh[1]);
if (err < 0) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
fput(from_umh[0]);
return err;
}
umh_info->pipe_to_umh = to_umh[1];
umh_info->pipe_from_umh = from_umh[0];
return 0;
}
static void umh_clean_and_save_pid(struct subprocess_info *info)
{
struct umh_info *umh_info = info->data;
/* cleanup if umh_pipe_setup() was successful but exec failed */
if (info->pid && info->retval) {
fput(umh_info->pipe_to_umh);
fput(umh_info->pipe_from_umh);
}
argv_free(info->argv);
umh_info->pid = info->pid;
}
/**
* fork_usermode_blob - fork a blob of bytes as a usermode process
* @data: a blob of bytes that can be do_execv-ed as a file
* @len: length of the blob
* @info: information about usermode process (shouldn't be NULL)
*
* If info->cmdline is set it will be used as command line for the
* user process, else "usermodehelper" is used.
*
* Returns either negative error or zero which indicates success
* in executing a blob of bytes as a usermode process. In such
* case 'struct umh_info *info' is populated with two pipes
* and a pid of the process. The caller is responsible for health
* check of the user process, killing it via pid, and closing the
* pipes when user process is no longer needed.
*/
int fork_usermode_blob(void *data, size_t len, struct umh_info *info)
{
struct subprocess_info *sub_info;
struct file *file;
ssize_t written;
loff_t pos = 0;
int err;
file = shmem_kernel_file_setup("", len, 0);
if (IS_ERR(file))
return PTR_ERR(file);
written = kernel_write(file, data, len, &pos);
if (written != len) {
err = written;
if (err >= 0)
err = -ENOMEM;
goto out;
}
err = -ENOMEM;
sub_info = call_usermodehelper_setup_file(file, umh_pipe_setup,
umh_clean_and_save_pid, info);
if (!sub_info)
goto out;
err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
if (!err) {
mutex_lock(&umh_list_lock);
list_add(&info->list, &umh_list);
mutex_unlock(&umh_list_lock);
}
out:
fput(file);
return err;
}
EXPORT_SYMBOL_GPL(fork_usermode_blob);
/** /**
* call_usermodehelper_exec - start a usermode application * call_usermodehelper_exec - start a usermode application
* @sub_info: information about the subprocessa * @sub_info: information about the subprocessa
...@@ -700,26 +555,6 @@ static int proc_cap_handler(struct ctl_table *table, int write, ...@@ -700,26 +555,6 @@ static int proc_cap_handler(struct ctl_table *table, int write,
return 0; return 0;
} }
void __exit_umh(struct task_struct *tsk)
{
struct umh_info *info;
pid_t pid = tsk->pid;
mutex_lock(&umh_list_lock);
list_for_each_entry(info, &umh_list, list) {
if (info->pid == pid) {
list_del(&info->list);
mutex_unlock(&umh_list_lock);
goto out;
}
}
mutex_unlock(&umh_list_lock);
return;
out:
if (info->cleanup)
info->cleanup(info);
}
struct ctl_table usermodehelper_table[] = { struct ctl_table usermodehelper_table[] = {
{ {
.procname = "bset", .procname = "bset",
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* umd - User mode driver support
*/
#include <linux/shmem_fs.h>
#include <linux/pipe_fs_i.h>
#include <linux/mount.h>
#include <linux/fs_struct.h>
#include <linux/task_work.h>
#include <linux/usermode_driver.h>
static struct vfsmount *blob_to_mnt(const void *data, size_t len, const char *name)
{
struct file_system_type *type;
struct vfsmount *mnt;
struct file *file;
ssize_t written;
loff_t pos = 0;
type = get_fs_type("tmpfs");
if (!type)
return ERR_PTR(-ENODEV);
mnt = kern_mount(type);
put_filesystem(type);
if (IS_ERR(mnt))
return mnt;
file = file_open_root(mnt->mnt_root, mnt, name, O_CREAT | O_WRONLY, 0700);
if (IS_ERR(file)) {
mntput(mnt);
return ERR_CAST(file);
}
written = kernel_write(file, data, len, &pos);
if (written != len) {
int err = written;
if (err >= 0)
err = -ENOMEM;
filp_close(file, NULL);
mntput(mnt);
return ERR_PTR(err);
}
fput(file);
/* Flush delayed fput so exec can open the file read-only */
flush_delayed_fput();
task_work_run();
return mnt;
}
/**
* umd_load_blob - Remember a blob of bytes for fork_usermode_driver
* @info: information about usermode driver
* @data: a blob of bytes that can be executed as a file
* @len: The lentgh of the blob
*
*/
int umd_load_blob(struct umd_info *info, const void *data, size_t len)
{
struct vfsmount *mnt;
if (WARN_ON_ONCE(info->wd.dentry || info->wd.mnt))
return -EBUSY;
mnt = blob_to_mnt(data, len, info->driver_name);
if (IS_ERR(mnt))
return PTR_ERR(mnt);
info->wd.mnt = mnt;
info->wd.dentry = mnt->mnt_root;
return 0;
}
EXPORT_SYMBOL_GPL(umd_load_blob);
/**
* umd_unload_blob - Disassociate @info from a previously loaded blob
* @info: information about usermode driver
*
*/
int umd_unload_blob(struct umd_info *info)
{
if (WARN_ON_ONCE(!info->wd.mnt ||
!info->wd.dentry ||
info->wd.mnt->mnt_root != info->wd.dentry))
return -EINVAL;
kern_unmount(info->wd.mnt);
info->wd.mnt = NULL;
info->wd.dentry = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(umd_unload_blob);
static int umd_setup(struct subprocess_info *info, struct cred *new)
{
struct umd_info *umd_info = info->data;
struct file *from_umh[2];
struct file *to_umh[2];
int err;
/* create pipe to send data to umh */
err = create_pipe_files(to_umh, 0);
if (err)
return err;
err = replace_fd(0, to_umh[0], 0);
fput(to_umh[0]);
if (err < 0) {
fput(to_umh[1]);
return err;
}
/* create pipe to receive data from umh */
err = create_pipe_files(from_umh, 0);
if (err) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
return err;
}
err = replace_fd(1, from_umh[1], 0);
fput(from_umh[1]);
if (err < 0) {
fput(to_umh[1]);
replace_fd(0, NULL, 0);
fput(from_umh[0]);
return err;
}
set_fs_pwd(current->fs, &umd_info->wd);
umd_info->pipe_to_umh = to_umh[1];
umd_info->pipe_from_umh = from_umh[0];
umd_info->tgid = get_pid(task_tgid(current));
return 0;
}
static void umd_cleanup(struct subprocess_info *info)
{
struct umd_info *umd_info = info->data;
/* cleanup if umh_setup() was successful but exec failed */
if (info->retval) {
fput(umd_info->pipe_to_umh);
fput(umd_info->pipe_from_umh);
put_pid(umd_info->tgid);
umd_info->tgid = NULL;
}
}
/**
* fork_usermode_driver - fork a usermode driver
* @info: information about usermode driver (shouldn't be NULL)
*
* Returns either negative error or zero which indicates success in
* executing a usermode driver. In such case 'struct umd_info *info'
* is populated with two pipes and a tgid of the process. The caller is
* responsible for health check of the user process, killing it via
* tgid, and closing the pipes when user process is no longer needed.
*/
int fork_usermode_driver(struct umd_info *info)
{
struct subprocess_info *sub_info;
const char *argv[] = { info->driver_name, NULL };
int err;
if (WARN_ON_ONCE(info->tgid))
return -EBUSY;
err = -ENOMEM;
sub_info = call_usermodehelper_setup(info->driver_name,
(char **)argv, NULL, GFP_KERNEL,
umd_setup, umd_cleanup, info);
if (!sub_info)
goto out;
err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
out:
return err;
}
EXPORT_SYMBOL_GPL(fork_usermode_driver);
...@@ -15,15 +15,13 @@ extern char bpfilter_umh_end; ...@@ -15,15 +15,13 @@ extern char bpfilter_umh_end;
static void shutdown_umh(void) static void shutdown_umh(void)
{ {
struct task_struct *tsk; struct umd_info *info = &bpfilter_ops.info;
struct pid *tgid = info->tgid;
if (bpfilter_ops.stop) if (tgid) {
return; kill_pid(tgid, SIGKILL, 1);
wait_event(tgid->wait_pidfd, thread_group_exited(tgid));
tsk = get_pid_task(find_vpid(bpfilter_ops.info.pid), PIDTYPE_PID); bpfilter_umh_cleanup(info);
if (tsk) {
send_sig(SIGKILL, tsk, 1);
put_task_struct(tsk);
} }
} }
...@@ -48,7 +46,7 @@ static int __bpfilter_process_sockopt(struct sock *sk, int optname, ...@@ -48,7 +46,7 @@ static int __bpfilter_process_sockopt(struct sock *sk, int optname,
req.cmd = optname; req.cmd = optname;
req.addr = (long __force __user)optval; req.addr = (long __force __user)optval;
req.len = optlen; req.len = optlen;
if (!bpfilter_ops.info.pid) if (!bpfilter_ops.info.tgid)
goto out; goto out;
n = __kernel_write(bpfilter_ops.info.pipe_to_umh, &req, sizeof(req), n = __kernel_write(bpfilter_ops.info.pipe_to_umh, &req, sizeof(req),
&pos); &pos);
...@@ -77,13 +75,10 @@ static int start_umh(void) ...@@ -77,13 +75,10 @@ static int start_umh(void)
int err; int err;
/* fork usermode process */ /* fork usermode process */
err = fork_usermode_blob(&bpfilter_umh_start, err = fork_usermode_driver(&bpfilter_ops.info);
&bpfilter_umh_end - &bpfilter_umh_start,
&bpfilter_ops.info);
if (err) if (err)
return err; return err;
bpfilter_ops.stop = false; pr_info("Loaded bpfilter_umh pid %d\n", pid_nr(bpfilter_ops.info.tgid));
pr_info("Loaded bpfilter_umh pid %d\n", bpfilter_ops.info.pid);
/* health check that usermode process started correctly */ /* health check that usermode process started correctly */
if (__bpfilter_process_sockopt(NULL, 0, NULL, 0, 0) != 0) { if (__bpfilter_process_sockopt(NULL, 0, NULL, 0, 0) != 0) {
...@@ -98,18 +93,21 @@ static int __init load_umh(void) ...@@ -98,18 +93,21 @@ static int __init load_umh(void)
{ {
int err; 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); mutex_lock(&bpfilter_ops.lock);
if (!bpfilter_ops.stop) {
err = -EFAULT;
goto out;
}
err = start_umh(); err = start_umh();
if (!err && IS_ENABLED(CONFIG_INET)) { if (!err && IS_ENABLED(CONFIG_INET)) {
bpfilter_ops.sockopt = &__bpfilter_process_sockopt; bpfilter_ops.sockopt = &__bpfilter_process_sockopt;
bpfilter_ops.start = &start_umh; bpfilter_ops.start = &start_umh;
} }
out:
mutex_unlock(&bpfilter_ops.lock); mutex_unlock(&bpfilter_ops.lock);
if (err)
umd_unload_blob(&bpfilter_ops.info);
return err; return err;
} }
...@@ -122,6 +120,8 @@ static void __exit fini_umh(void) ...@@ -122,6 +120,8 @@ static void __exit fini_umh(void)
bpfilter_ops.sockopt = NULL; bpfilter_ops.sockopt = NULL;
} }
mutex_unlock(&bpfilter_ops.lock); mutex_unlock(&bpfilter_ops.lock);
umd_unload_blob(&bpfilter_ops.info);
} }
module_init(load_umh); module_init(load_umh);
module_exit(fini_umh); module_exit(fini_umh);
......
/* SPDX-License-Identifier: GPL-2.0 */ /* SPDX-License-Identifier: GPL-2.0 */
.section .rodata, "a" .section .init.rodata, "a"
.global bpfilter_umh_start .global bpfilter_umh_start
bpfilter_umh_start: bpfilter_umh_start:
.incbin "net/bpfilter/bpfilter_umh" .incbin "net/bpfilter/bpfilter_umh"
......
...@@ -12,15 +12,14 @@ ...@@ -12,15 +12,14 @@
struct bpfilter_umh_ops bpfilter_ops; struct bpfilter_umh_ops bpfilter_ops;
EXPORT_SYMBOL_GPL(bpfilter_ops); EXPORT_SYMBOL_GPL(bpfilter_ops);
static void bpfilter_umh_cleanup(struct umh_info *info) void bpfilter_umh_cleanup(struct umd_info *info)
{ {
mutex_lock(&bpfilter_ops.lock);
bpfilter_ops.stop = true;
fput(info->pipe_to_umh); fput(info->pipe_to_umh);
fput(info->pipe_from_umh); fput(info->pipe_from_umh);
info->pid = 0; put_pid(info->tgid);
mutex_unlock(&bpfilter_ops.lock); info->tgid = NULL;
} }
EXPORT_SYMBOL_GPL(bpfilter_umh_cleanup);
static int bpfilter_mbox_request(struct sock *sk, int optname, static int bpfilter_mbox_request(struct sock *sk, int optname,
char __user *optval, char __user *optval,
...@@ -38,7 +37,11 @@ static int bpfilter_mbox_request(struct sock *sk, int optname, ...@@ -38,7 +37,11 @@ static int bpfilter_mbox_request(struct sock *sk, int optname,
goto out; goto out;
} }
} }
if (bpfilter_ops.stop) { if (bpfilter_ops.info.tgid &&
thread_group_exited(bpfilter_ops.info.tgid))
bpfilter_umh_cleanup(&bpfilter_ops.info);
if (!bpfilter_ops.info.tgid) {
err = bpfilter_ops.start(); err = bpfilter_ops.start();
if (err) if (err)
goto out; goto out;
...@@ -69,9 +72,8 @@ int bpfilter_ip_get_sockopt(struct sock *sk, int optname, char __user *optval, ...@@ -69,9 +72,8 @@ int bpfilter_ip_get_sockopt(struct sock *sk, int optname, char __user *optval,
static int __init bpfilter_sockopt_init(void) static int __init bpfilter_sockopt_init(void)
{ {
mutex_init(&bpfilter_ops.lock); mutex_init(&bpfilter_ops.lock);
bpfilter_ops.stop = true; bpfilter_ops.info.tgid = NULL;
bpfilter_ops.info.cmdline = "bpfilter_umh"; bpfilter_ops.info.driver_name = "bpfilter_umh";
bpfilter_ops.info.cleanup = &bpfilter_umh_cleanup;
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment