| 1 | |
| 2 | |
| 3 | * umd - User mode driver support
|
| 4 |
|
| 5 | #include <linux/shmem_fs.h>
|
| 6 | #include <linux/pipe_fs_i.h>
|
| 7 | #include <linux/mount.h>
|
| 8 | #include <linux/fs_struct.h>
|
| 9 | #include <linux/task_work.h>
|
| 10 | #include <linux/usermode_driver.h>
|
| 11 |
|
| 12 | static struct vfsmount *blob_to_mnt(const void *data, size_t len, const char *name)
|
| 13 | {
|
| 14 | struct file_system_type *type;
|
| 15 | struct vfsmount *mnt;
|
| 16 | struct file *file;
|
| 17 | ssize_t written;
|
| 18 | loff_t pos = 0;
|
| 19 |
|
| 20 | type = get_fs_type("tmpfs");
|
| 21 | if (!type)
|
| 22 | return ERR_PTR(-ENODEV);
|
| 23 |
|
| 24 | mnt = kern_mount(type);
|
| 25 | put_filesystem(type);
|
| 26 | if (IS_ERR(mnt))
|
| 27 | return mnt;
|
| 28 |
|
| 29 | file = file_open_root(mnt->mnt_root, mnt, name, O_CREAT | O_WRONLY, 0700);
|
| 30 | if (IS_ERR(file)) {
|
| 31 | mntput(mnt);
|
| 32 | return ERR_CAST(file);
|
| 33 | }
|
| 34 |
|
| 35 | written = kernel_write(file, data, len, &pos);
|
| 36 | if (written != len) {
|
| 37 | int err = written;
|
| 38 | if (err >= 0)
|
| 39 | err = -ENOMEM;
|
| 40 | filp_close(file, NULL);
|
| 41 | mntput(mnt);
|
| 42 | return ERR_PTR(err);
|
| 43 | }
|
| 44 |
|
| 45 | fput(file);
|
| 46 |
|
| 47 |
|
| 48 | flush_delayed_fput();
|
| 49 | task_work_run();
|
| 50 | return mnt;
|
| 51 | }
|
| 52 |
|
| 53 | |
| 54 | * umd_load_blob - Remember a blob of bytes for fork_usermode_driver
|
| 55 | * @info: information about usermode driver
|
| 56 | * @data: a blob of bytes that can be executed as a file
|
| 57 | * @len: The lentgh of the blob
|
| 58 | *
|
| 59 |
|
| 60 | int umd_load_blob(struct umd_info *info, const void *data, size_t len)
|
| 61 | {
|
| 62 | struct vfsmount *mnt;
|
| 63 |
|
| 64 | if (WARN_ON_ONCE(info->wd.dentry || info->wd.mnt))
|
| 65 | return -EBUSY;
|
| 66 |
|
| 67 | mnt = blob_to_mnt(data, len, info->driver_name);
|
| 68 | if (IS_ERR(mnt))
|
| 69 | return PTR_ERR(mnt);
|
| 70 |
|
| 71 | info->wd.mnt = mnt;
|
| 72 | info->wd.dentry = mnt->mnt_root;
|
| 73 | return 0;
|
| 74 | }
|
| 75 | EXPORT_SYMBOL_GPL(umd_load_blob);
|
| 76 |
|
| 77 | |
| 78 | * umd_unload_blob - Disassociate @info from a previously loaded blob
|
| 79 | * @info: information about usermode driver
|
| 80 | *
|
| 81 |
|
| 82 | int umd_unload_blob(struct umd_info *info)
|
| 83 | {
|
| 84 | if (WARN_ON_ONCE(!info->wd.mnt ||
|
| 85 | !info->wd.dentry ||
|
| 86 | info->wd.mnt->mnt_root != info->wd.dentry))
|
| 87 | return -EINVAL;
|
| 88 |
|
| 89 | kern_unmount(info->wd.mnt);
|
| 90 | info->wd.mnt = NULL;
|
| 91 | info->wd.dentry = NULL;
|
| 92 | return 0;
|
| 93 | }
|
| 94 | EXPORT_SYMBOL_GPL(umd_unload_blob);
|
| 95 |
|
| 96 | static int umd_setup(struct subprocess_info *info, struct cred *new)
|
| 97 | {
|
| 98 | struct umd_info *umd_info = info->data;
|
| 99 | struct file *from_umh[2];
|
| 100 | struct file *to_umh[2];
|
| 101 | int err;
|
| 102 |
|
| 103 |
|
| 104 | err = create_pipe_files(to_umh, 0);
|
| 105 | if (err)
|
| 106 | return err;
|
| 107 | err = replace_fd(0, to_umh[0], 0);
|
| 108 | fput(to_umh[0]);
|
| 109 | if (err < 0) {
|
| 110 | fput(to_umh[1]);
|
| 111 | return err;
|
| 112 | }
|
| 113 |
|
| 114 |
|
| 115 | err = create_pipe_files(from_umh, 0);
|
| 116 | if (err) {
|
| 117 | fput(to_umh[1]);
|
| 118 | replace_fd(0, NULL, 0);
|
| 119 | return err;
|
| 120 | }
|
| 121 | err = replace_fd(1, from_umh[1], 0);
|
| 122 | fput(from_umh[1]);
|
| 123 | if (err < 0) {
|
| 124 | fput(to_umh[1]);
|
| 125 | replace_fd(0, NULL, 0);
|
| 126 | fput(from_umh[0]);
|
| 127 | return err;
|
| 128 | }
|
| 129 |
|
| 130 | set_fs_pwd(current->fs, &umd_info->wd);
|
| 131 | umd_info->pipe_to_umh = to_umh[1];
|
| 132 | umd_info->pipe_from_umh = from_umh[0];
|
| 133 | umd_info->tgid = get_pid(task_tgid(current));
|
| 134 | return 0;
|
| 135 | }
|
| 136 |
|
| 137 | static void umd_cleanup(struct subprocess_info *info)
|
| 138 | {
|
| 139 | struct umd_info *umd_info = info->data;
|
| 140 |
|
| 141 |
|
| 142 | if (info->retval)
|
| 143 | umd_cleanup_helper(umd_info);
|
| 144 | }
|
| 145 |
|
| 146 | |
| 147 | * umd_cleanup_helper - release the resources which were allocated in umd_setup
|
| 148 | * @info: information about usermode driver
|
| 149 |
|
| 150 | void umd_cleanup_helper(struct umd_info *info)
|
| 151 | {
|
| 152 | fput(info->pipe_to_umh);
|
| 153 | fput(info->pipe_from_umh);
|
| 154 | put_pid(info->tgid);
|
| 155 | info->tgid = NULL;
|
| 156 | }
|
| 157 | EXPORT_SYMBOL_GPL(umd_cleanup_helper);
|
| 158 |
|
| 159 | |
| 160 | * fork_usermode_driver - fork a usermode driver
|
| 161 | * @info: information about usermode driver (shouldn't be NULL)
|
| 162 | *
|
| 163 | * Returns either negative error or zero which indicates success in
|
| 164 | * executing a usermode driver. In such case 'struct umd_info *info'
|
| 165 | * is populated with two pipes and a tgid of the process. The caller is
|
| 166 | * responsible for health check of the user process, killing it via
|
| 167 | * tgid, and closing the pipes when user process is no longer needed.
|
| 168 |
|
| 169 | int fork_usermode_driver(struct umd_info *info)
|
| 170 | {
|
| 171 | struct subprocess_info *sub_info;
|
| 172 | const char *argv[] = { info->driver_name, NULL };
|
| 173 | int err;
|
| 174 |
|
| 175 | if (WARN_ON_ONCE(info->tgid))
|
| 176 | return -EBUSY;
|
| 177 |
|
| 178 | err = -ENOMEM;
|
| 179 | sub_info = call_usermodehelper_setup(info->driver_name,
|
| 180 | (char **)argv, NULL, GFP_KERNEL,
|
| 181 | umd_setup, umd_cleanup, info);
|
| 182 | if (!sub_info)
|
| 183 | goto out;
|
| 184 |
|
| 185 | err = call_usermodehelper_exec(sub_info, UMH_WAIT_EXEC);
|
| 186 | out:
|
| 187 | return err;
|
| 188 | }
|
| 189 | EXPORT_SYMBOL_GPL(fork_usermode_driver);
|
| 190 |
|
| 191 |
|