Commit 3950e975 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'exec-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace

Pull execve updates from Eric Biederman:
 "During the development of v5.7 I ran into bugs and quality of
  implementation issues related to exec that could not be easily fixed
  because of the way exec is implemented. So I have been diggin into
  exec and cleaning up what I can.

  This cycle I have been looking at different ideas and different
  implementations to see what is possible to improve exec, and cleaning
  the way exec interfaces with in kernel users. Only cleaning up the
  interfaces of exec with rest of the kernel has managed to stabalize
  and make it through review in time for v5.9-rc1 resulting in 2 sets of
  changes this cycle.

   - Implement kernel_execve

   - Make the user mode driver code a better citizen

  With kernel_execve the code size got a little larger as the copying of
  parameters from userspace and copying of parameters from userspace is
  now separate. The good news is kernel threads no longer need to play
  games with set_fs to use exec. Which when combined with the rest of
  Christophs set_fs changes should security bugs with set_fs much more
  difficult"

* 'exec-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: (23 commits)
  exec: Implement kernel_execve
  exec: Factor bprm_stack_limits out of prepare_arg_pages
  exec: Factor bprm_execve out of do_execve_common
  exec: Move bprm_mm_init into alloc_bprm
  exec: Move initialization of bprm->filename into alloc_bprm
  exec: Factor out alloc_bprm
  exec: Remove unnecessary spaces from binfmts.h
  umd: Stop using split_argv
  umd: Remove exit_umh
  bpfilter: Take advantage of the facilities of struct pid
  exit: Factor thread_group_exited out of pidfd_poll
  umd: Track user space drivers with struct pid
  bpfilter: Move bpfilter_umh back into init data
  exec: Remove do_execve_file
  umh: Stop calling do_execve_file
  umd: Transform fork_usermode_blob into fork_usermode_driver
  umd: Rename umd_info.cmdline umd_info.driver_name
  umd: For clarity rename umh_info umd_info
  umh: Separate the user mode driver and the user mode helper support
  umh: Remove call_usermodehelper_setup_file.
  ...
parents fd76a74d 7fce69df
...@@ -854,7 +854,7 @@ SYM_CODE_START(ret_from_fork) ...@@ -854,7 +854,7 @@ SYM_CODE_START(ret_from_fork)
CALL_NOSPEC ebx CALL_NOSPEC ebx
/* /*
* A kernel thread is allowed to return here after successfully * A kernel thread is allowed to return here after successfully
* calling do_execve(). Exit to userspace to complete the execve() * calling kernel_execve(). Exit to userspace to complete the execve()
* syscall. * syscall.
*/ */
movl $0, PT_EAX(%esp) movl $0, PT_EAX(%esp)
......
...@@ -293,7 +293,7 @@ SYM_CODE_START(ret_from_fork) ...@@ -293,7 +293,7 @@ SYM_CODE_START(ret_from_fork)
CALL_NOSPEC rbx CALL_NOSPEC rbx
/* /*
* A kernel thread is allowed to return here after successfully * A kernel thread is allowed to return here after successfully
* calling do_execve(). Exit to userspace to complete the execve() * calling kernel_execve(). Exit to userspace to complete the execve()
* syscall. * syscall.
*/ */
movq $0, RAX(%rsp) movq $0, RAX(%rsp)
......
...@@ -275,7 +275,7 @@ bool unwind_next_frame(struct unwind_state *state) ...@@ -275,7 +275,7 @@ bool unwind_next_frame(struct unwind_state *state)
* This user_mode() check is slightly broader than a PF_KTHREAD * This user_mode() check is slightly broader than a PF_KTHREAD
* check because it also catches the awkward situation where a * check because it also catches the awkward situation where a
* newly forked kthread transitions into a user task by calling * newly forked kthread transitions into a user task by calling
* do_execve(), which eventually clears PF_KTHREAD. * kernel_execve(), which eventually clears PF_KTHREAD.
*/ */
if (!user_mode(regs)) if (!user_mode(regs))
goto the_end; goto the_end;
......
This diff is collapsed.
...@@ -45,17 +45,18 @@ struct linux_binprm { ...@@ -45,17 +45,18 @@ struct linux_binprm {
#ifdef __alpha__ #ifdef __alpha__
unsigned int taso:1; unsigned int taso:1;
#endif #endif
struct file * executable; /* Executable to pass to the interpreter */ struct file *executable; /* Executable to pass to the interpreter */
struct file * interpreter; struct file *interpreter;
struct file * file; struct file *file;
struct cred *cred; /* new credentials */ struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */ unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc; int argc, envc;
const char * filename; /* Name of binary as seen by procps */ const char *filename; /* Name of binary as seen by procps */
const char * interp; /* Name of the binary really executed. Most const char *interp; /* Name of the binary really executed. Most
of the time same as filename, but could be of the time same as filename, but could be
different for binfmt_{misc,script} */ different for binfmt_{misc,script} */
const char *fdpath; /* generated filename for execveat */
unsigned interp_flags; unsigned interp_flags;
int execfd; /* File descriptor of the executable */ int execfd; /* File descriptor of the executable */
unsigned long loader, exec; unsigned long loader, exec;
...@@ -134,13 +135,7 @@ int copy_string_kernel(const char *arg, struct linux_binprm *bprm); ...@@ -134,13 +135,7 @@ int copy_string_kernel(const char *arg, struct linux_binprm *bprm);
extern void set_binfmt(struct linux_binfmt *new); extern void set_binfmt(struct linux_binfmt *new);
extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t); extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t);
extern int do_execve(struct filename *, int kernel_execve(const char *filename,
const char __user * const __user *, const char *const *argv, const char *const *envp);
const char __user * const __user *);
extern int do_execveat(int, struct filename *,
const char __user * const __user *,
const char __user * const __user *,
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
...@@ -1507,7 +1507,6 @@ extern struct pid *cad_pid; ...@@ -1507,7 +1507,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 */
...@@ -2016,14 +2015,6 @@ static inline void rseq_execve(struct task_struct *t) ...@@ -2016,14 +2015,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__ */
...@@ -1331,9 +1331,7 @@ static int run_init_process(const char *init_filename) ...@@ -1331,9 +1331,7 @@ static int run_init_process(const char *init_filename)
pr_debug(" with environment:\n"); pr_debug(" with environment:\n");
for (p = envp_init; *p; p++) for (p = envp_init; *p; p++)
pr_debug(" %s\n", *p); pr_debug(" %s\n", *p);
return do_execve(getname_kernel(init_filename), return kernel_execve(init_filename, argv_init, envp_init);
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
} }
static int try_to_run_init_process(const char *init_filename) static int try_to_run_init_process(const char *init_filename)
......
...@@ -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
......
...@@ -805,7 +805,6 @@ void __noreturn do_exit(long code) ...@@ -805,7 +805,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
...@@ -1712,6 +1711,30 @@ COMPAT_SYSCALL_DEFINE5(waitid, ...@@ -1712,6 +1711,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();
......
...@@ -1792,22 +1792,18 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f) ...@@ -1792,22 +1792,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 = kernel_execve(sub_info->path,
if (sub_info->file) { (const char *const *)sub_info->argv,
retval = do_execve_file(sub_info->file, (const char *const *)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;
} }
......
...@@ -425,7 +425,7 @@ struct tomoyo_request_info { ...@@ -425,7 +425,7 @@ struct tomoyo_request_info {
struct tomoyo_obj_info *obj; struct tomoyo_obj_info *obj;
/* /*
* For holding parameters specific to execve() request. * For holding parameters specific to execve() request.
* NULL if not dealing do_execve(). * NULL if not dealing execve().
*/ */
struct tomoyo_execve *ee; struct tomoyo_execve *ee;
struct tomoyo_domain_info *domain; struct tomoyo_domain_info *domain;
......
...@@ -767,7 +767,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) ...@@ -767,7 +767,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)
/* /*
* Check for domain transition preference if "file execute" matched. * Check for domain transition preference if "file execute" matched.
* If preference is given, make do_execve() fail if domain transition * If preference is given, make execve() fail if domain transition
* has failed, for domain transition preference should be used with * has failed, for domain transition preference should be used with
* destination domain defined. * destination domain defined.
*/ */
...@@ -810,7 +810,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) ...@@ -810,7 +810,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)
snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>",
candidate->name); candidate->name);
/* /*
* Make do_execve() fail if domain transition across namespaces * Make execve() fail if domain transition across namespaces
* has failed. * has failed.
*/ */
reject_on_transition_failure = true; reject_on_transition_failure = true;
......
...@@ -93,7 +93,7 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm) ...@@ -93,7 +93,7 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
struct tomoyo_task *s = tomoyo_task(current); struct tomoyo_task *s = tomoyo_task(current);
/* /*
* Execute permission is checked against pathname passed to do_execve() * Execute permission is checked against pathname passed to execve()
* using current domain. * using current domain.
*/ */
if (!s->old_domain_info) { if (!s->old_domain_info) {
...@@ -307,7 +307,7 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd, ...@@ -307,7 +307,7 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
*/ */
static int tomoyo_file_open(struct file *f) static int tomoyo_file_open(struct file *f)
{ {
/* Don't check read permission here if called from do_execve(). */ /* Don't check read permission here if called from execve(). */
if (current->in_execve) if (current->in_execve)
return 0; return 0;
return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path,
......
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