summaryrefslogtreecommitdiffstats
path: root/kernel/usermode_driver.c
blob: e73550e946d6aa194479fc51b868fb2f61fc55b1 (plain)
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
// 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/usermode_driver.h>

static LIST_HEAD(umh_list);
static DEFINE_MUTEX(umh_list_lock);

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;
	}

	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_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 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_blob(void *data, size_t len, struct umd_info *info)
{
	const char *cmdline = (info->cmdline) ? info->cmdline : "usermodehelper";
	struct subprocess_info *sub_info;
	char **argv = NULL;
	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;
	argv = argv_split(GFP_KERNEL, cmdline, NULL);
	if (!argv)
		goto out;

	sub_info = call_usermodehelper_setup("none", argv, NULL, GFP_KERNEL,
					     umd_setup, umd_cleanup, info);
	if (!sub_info)
		goto out;

	sub_info->file = file;
	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);
	fput(file);
	return err;
}
EXPORT_SYMBOL_GPL(fork_usermode_blob);

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);
}