umd: Transform fork_usermode_blob into fork_usermode_driver

Instead of loading a binary blob into a temporary file with
shmem_kernel_file_setup load a binary blob into a temporary tmpfs
filesystem.  This means that the blob can be stored in an init section
and discared, and it means the binary blob will have a filename so can
be executed normally.

The only tricky thing about this code is that in the helper function
blob_to_mnt __fput_sync is used.  That is because a file can not be
executed if it is still open for write, and the ordinary delayed close
for kernel threads does not happen soon enough, which causes the
following exec to fail.  The function umd_load_blob is not called with
any locks so this should be safe.

Executing the blob normally winds up correcting several problems with
the user mode driver code discovered by Tetsuo Handa[1].  By passing
an ordinary filename into the exec, it is no longer necessary to
figure out how to turn a O_RDWR file descriptor into a properly
referende counted O_EXEC file descriptor that forbids all writes.  For
path based LSMs there are no new special cases.

[1] https://lore.kernel.org/linux-fsdevel/2a8775b4-1dd5-9d5c-aa42-9872445e0942@i-love.sakura.ne.jp/
v1: https://lkml.kernel.org/r/87d05mf0j9.fsf_-_@x220.int.ebiederm.org
v2: https://lkml.kernel.org/r/87wo3p4p35.fsf_-_@x220.int.ebiederm.org
Link: https://lkml.kernel.org/r/20200702164140.4468-8-ebiederm@xmission.com
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Tested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
This commit is contained in:
Eric W. Biederman 2020-06-25 13:12:59 -05:00
parent 1199c6c3da
commit e2dc9bf3f5
3 changed files with 113 additions and 33 deletions

View File

@ -2,6 +2,7 @@
#define __LINUX_USERMODE_DRIVER_H__ #define __LINUX_USERMODE_DRIVER_H__
#include <linux/umh.h> #include <linux/umh.h>
#include <linux/path.h>
#ifdef CONFIG_BPFILTER #ifdef CONFIG_BPFILTER
void __exit_umh(struct task_struct *tsk); void __exit_umh(struct task_struct *tsk);
@ -23,8 +24,11 @@ struct umd_info {
struct file *pipe_from_umh; struct file *pipe_from_umh;
struct list_head list; struct list_head list;
void (*cleanup)(struct umd_info *info); void (*cleanup)(struct umd_info *info);
struct path wd;
pid_t pid; pid_t pid;
}; };
int fork_usermode_blob(void *data, size_t len, struct umd_info *info); 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__ */ #endif /* __LINUX_USERMODE_DRIVER_H__ */

View File

@ -4,11 +4,98 @@
*/ */
#include <linux/shmem_fs.h> #include <linux/shmem_fs.h>
#include <linux/pipe_fs_i.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> #include <linux/usermode_driver.h>
static LIST_HEAD(umh_list); static LIST_HEAD(umh_list);
static DEFINE_MUTEX(umh_list_lock); static DEFINE_MUTEX(umh_list_lock);
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) static int umd_setup(struct subprocess_info *info, struct cred *new)
{ {
struct umd_info *umd_info = info->data; struct umd_info *umd_info = info->data;
@ -43,6 +130,7 @@ static int umd_setup(struct subprocess_info *info, struct cred *new)
return err; return err;
} }
set_fs_pwd(current->fs, &umd_info->wd);
umd_info->pipe_to_umh = to_umh[1]; umd_info->pipe_to_umh = to_umh[1];
umd_info->pipe_from_umh = from_umh[0]; umd_info->pipe_from_umh = from_umh[0];
umd_info->pid = task_pid_nr(current); umd_info->pid = task_pid_nr(current);
@ -62,39 +150,21 @@ static void umd_cleanup(struct subprocess_info *info)
} }
/** /**
* fork_usermode_blob - fork a blob of bytes as a usermode process * fork_usermode_driver - fork a usermode driver
* @data: a blob of bytes that can be do_execv-ed as a file * @info: information about usermode driver (shouldn't be NULL)
* @len: length of the blob
* @info: information about usermode process (shouldn't be NULL)
* *
* Returns either negative error or zero which indicates success * Returns either negative error or zero which indicates success in
* in executing a blob of bytes as a usermode process. In such * executing a usermode driver. In such case 'struct umd_info *info'
* case 'struct umd_info *info' is populated with two pipes * is populated with two pipes and a pid of the process. The caller is
* and a pid of the process. The caller is responsible for health * responsible for health check of the user process, killing it via
* check of the user process, killing it via pid, and closing the * pid, and closing the pipes when user process is no longer needed.
* pipes when user process is no longer needed.
*/ */
int fork_usermode_blob(void *data, size_t len, struct umd_info *info) int fork_usermode_driver(struct umd_info *info)
{ {
struct subprocess_info *sub_info; struct subprocess_info *sub_info;
char **argv = NULL; char **argv = NULL;
struct file *file;
ssize_t written;
loff_t pos = 0;
int err; int err;
file = shmem_kernel_file_setup(info->driver_name, 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; err = -ENOMEM;
argv = argv_split(GFP_KERNEL, info->driver_name, NULL); argv = argv_split(GFP_KERNEL, info->driver_name, NULL);
if (!argv) if (!argv)
@ -106,7 +176,6 @@ int fork_usermode_blob(void *data, size_t len, struct umd_info *info)
if (!sub_info) if (!sub_info)
goto out; goto out;
sub_info->file = file;
err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC); err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
if (!err) { if (!err) {
mutex_lock(&umh_list_lock); mutex_lock(&umh_list_lock);
@ -116,10 +185,9 @@ int fork_usermode_blob(void *data, size_t len, struct umd_info *info)
out: out:
if (argv) if (argv)
argv_free(argv); argv_free(argv);
fput(file);
return err; return err;
} }
EXPORT_SYMBOL_GPL(fork_usermode_blob); EXPORT_SYMBOL_GPL(fork_usermode_driver);
void __exit_umh(struct task_struct *tsk) void __exit_umh(struct task_struct *tsk)
{ {

View File

@ -77,9 +77,7 @@ 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; bpfilter_ops.stop = false;
@ -98,6 +96,12 @@ 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) { if (!bpfilter_ops.stop) {
err = -EFAULT; err = -EFAULT;
@ -110,6 +114,8 @@ static int __init load_umh(void)
} }
out: 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 +128,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);