Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | // 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) umd_cleanup_helper(umd_info); } /** * umd_cleanup_helper - release the resources which were allocated in umd_setup * @info: information about usermode driver */ void umd_cleanup_helper(struct umd_info *info) { fput(info->pipe_to_umh); fput(info->pipe_from_umh); put_pid(info->tgid); info->tgid = NULL; } EXPORT_SYMBOL_GPL(umd_cleanup_helper); /** * 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); |