mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 13:55:32 +00:00
![Eric W. Biederman](/assets/img/avatar_default.png)
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>
211 lines
4.6 KiB
C
211 lines
4.6 KiB
C
// 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 LIST_HEAD(umh_list);
|
|
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)
|
|
{
|
|
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->pid = task_pid_nr(current);
|
|
current->flags |= PF_UMH;
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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_driver(struct umd_info *info)
|
|
{
|
|
struct subprocess_info *sub_info;
|
|
char **argv = NULL;
|
|
int err;
|
|
|
|
err = -ENOMEM;
|
|
argv = argv_split(GFP_KERNEL, info->driver_name, NULL);
|
|
if (!argv)
|
|
goto out;
|
|
|
|
sub_info = call_usermodehelper_setup(info->driver_name, argv, NULL,
|
|
GFP_KERNEL,
|
|
umd_setup, umd_cleanup, 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:
|
|
if (argv)
|
|
argv_free(argv);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fork_usermode_driver);
|
|
|
|
void __exit_umh(struct task_struct *tsk)
|
|
{
|
|
struct umd_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);
|
|
}
|
|
|