1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/limits.h>
4#include <signal.h>
5
6#include "../kselftest.h"
7#include "cgroup_util.h"
8
9static int idle_process_fn(const char *cgroup, void *arg)
10{
11	(void)pause();
12	return 0;
13}
14
15static int do_migration_fn(const char *cgroup, void *arg)
16{
17	int object_pid = (int)(size_t)arg;
18
19	if (setuid(TEST_UID))
20		return EXIT_FAILURE;
21
22	// XXX checking /proc/$pid/cgroup would be quicker than wait
23	if (cg_enter(cgroup, object_pid) ||
24	    cg_wait_for_proc_count(cgroup, 1))
25		return EXIT_FAILURE;
26
27	return EXIT_SUCCESS;
28}
29
30static int do_controller_fn(const char *cgroup, void *arg)
31{
32	const char *child = cgroup;
33	const char *parent = arg;
34
35	if (setuid(TEST_UID))
36		return EXIT_FAILURE;
37
38	if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))
39		return EXIT_FAILURE;
40
41	if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))
42		return EXIT_FAILURE;
43
44	if (cg_read_strstr(child, "cgroup.controllers", "cpuset"))
45		return EXIT_FAILURE;
46
47	if (cg_write(parent, "cgroup.subtree_control", "-cpuset"))
48		return EXIT_FAILURE;
49
50	if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))
51		return EXIT_FAILURE;
52
53	return EXIT_SUCCESS;
54}
55
56/*
57 * Migrate a process between two sibling cgroups.
58 * The success should only depend on the parent cgroup permissions and not the
59 * migrated process itself (cpuset controller is in place because it uses
60 * security_task_setscheduler() in cgroup v1).
61 *
62 * Deliberately don't set cpuset.cpus in children to avoid definining migration
63 * permissions between two different cpusets.
64 */
65static int test_cpuset_perms_object(const char *root, bool allow)
66{
67	char *parent = NULL, *child_src = NULL, *child_dst = NULL;
68	char *parent_procs = NULL, *child_src_procs = NULL, *child_dst_procs = NULL;
69	const uid_t test_euid = TEST_UID;
70	int object_pid = 0;
71	int ret = KSFT_FAIL;
72
73	parent = cg_name(root, "cpuset_test_0");
74	if (!parent)
75		goto cleanup;
76	parent_procs = cg_name(parent, "cgroup.procs");
77	if (!parent_procs)
78		goto cleanup;
79	if (cg_create(parent))
80		goto cleanup;
81
82	child_src = cg_name(parent, "cpuset_test_1");
83	if (!child_src)
84		goto cleanup;
85	child_src_procs = cg_name(child_src, "cgroup.procs");
86	if (!child_src_procs)
87		goto cleanup;
88	if (cg_create(child_src))
89		goto cleanup;
90
91	child_dst = cg_name(parent, "cpuset_test_2");
92	if (!child_dst)
93		goto cleanup;
94	child_dst_procs = cg_name(child_dst, "cgroup.procs");
95	if (!child_dst_procs)
96		goto cleanup;
97	if (cg_create(child_dst))
98		goto cleanup;
99
100	if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))
101		goto cleanup;
102
103	if (cg_read_strstr(child_src, "cgroup.controllers", "cpuset") ||
104	    cg_read_strstr(child_dst, "cgroup.controllers", "cpuset"))
105		goto cleanup;
106
107	/* Enable permissions along src->dst tree path */
108	if (chown(child_src_procs, test_euid, -1) ||
109	    chown(child_dst_procs, test_euid, -1))
110		goto cleanup;
111
112	if (allow && chown(parent_procs, test_euid, -1))
113		goto cleanup;
114
115	/* Fork a privileged child as a test object */
116	object_pid = cg_run_nowait(child_src, idle_process_fn, NULL);
117	if (object_pid < 0)
118		goto cleanup;
119
120	/* Carry out migration in a child process that can drop all privileges
121	 * (including capabilities), the main process must remain privileged for
122	 * cleanup.
123	 * Child process's cgroup is irrelevant but we place it into child_dst
124	 * as hacky way to pass information about migration target to the child.
125	 */
126	if (allow ^ (cg_run(child_dst, do_migration_fn, (void *)(size_t)object_pid) == EXIT_SUCCESS))
127		goto cleanup;
128
129	ret = KSFT_PASS;
130
131cleanup:
132	if (object_pid > 0) {
133		(void)kill(object_pid, SIGTERM);
134		(void)clone_reap(object_pid, WEXITED);
135	}
136
137	cg_destroy(child_dst);
138	free(child_dst_procs);
139	free(child_dst);
140
141	cg_destroy(child_src);
142	free(child_src_procs);
143	free(child_src);
144
145	cg_destroy(parent);
146	free(parent_procs);
147	free(parent);
148
149	return ret;
150}
151
152static int test_cpuset_perms_object_allow(const char *root)
153{
154	return test_cpuset_perms_object(root, true);
155}
156
157static int test_cpuset_perms_object_deny(const char *root)
158{
159	return test_cpuset_perms_object(root, false);
160}
161
162/*
163 * Migrate a process between parent and child implicitely
164 * Implicit migration happens when a controller is enabled/disabled.
165 *
166 */
167static int test_cpuset_perms_subtree(const char *root)
168{
169	char *parent = NULL, *child = NULL;
170	char *parent_procs = NULL, *parent_subctl = NULL, *child_procs = NULL;
171	const uid_t test_euid = TEST_UID;
172	int object_pid = 0;
173	int ret = KSFT_FAIL;
174
175	parent = cg_name(root, "cpuset_test_0");
176	if (!parent)
177		goto cleanup;
178	parent_procs = cg_name(parent, "cgroup.procs");
179	if (!parent_procs)
180		goto cleanup;
181	parent_subctl = cg_name(parent, "cgroup.subtree_control");
182	if (!parent_subctl)
183		goto cleanup;
184	if (cg_create(parent))
185		goto cleanup;
186
187	child = cg_name(parent, "cpuset_test_1");
188	if (!child)
189		goto cleanup;
190	child_procs = cg_name(child, "cgroup.procs");
191	if (!child_procs)
192		goto cleanup;
193	if (cg_create(child))
194		goto cleanup;
195
196	/* Enable permissions as in a delegated subtree */
197	if (chown(parent_procs, test_euid, -1) ||
198	    chown(parent_subctl, test_euid, -1) ||
199	    chown(child_procs, test_euid, -1))
200		goto cleanup;
201
202	/* Put a privileged child in the subtree and modify controller state
203	 * from an unprivileged process, the main process remains privileged
204	 * for cleanup.
205	 * The unprivileged child runs in subtree too to avoid parent and
206	 * internal-node constraing violation.
207	 */
208	object_pid = cg_run_nowait(child, idle_process_fn, NULL);
209	if (object_pid < 0)
210		goto cleanup;
211
212	if (cg_run(child, do_controller_fn, parent) != EXIT_SUCCESS)
213		goto cleanup;
214
215	ret = KSFT_PASS;
216
217cleanup:
218	if (object_pid > 0) {
219		(void)kill(object_pid, SIGTERM);
220		(void)clone_reap(object_pid, WEXITED);
221	}
222
223	cg_destroy(child);
224	free(child_procs);
225	free(child);
226
227	cg_destroy(parent);
228	free(parent_subctl);
229	free(parent_procs);
230	free(parent);
231
232	return ret;
233}
234
235
236#define T(x) { x, #x }
237struct cpuset_test {
238	int (*fn)(const char *root);
239	const char *name;
240} tests[] = {
241	T(test_cpuset_perms_object_allow),
242	T(test_cpuset_perms_object_deny),
243	T(test_cpuset_perms_subtree),
244};
245#undef T
246
247int main(int argc, char *argv[])
248{
249	char root[PATH_MAX];
250	int i, ret = EXIT_SUCCESS;
251
252	if (cg_find_unified_root(root, sizeof(root)))
253		ksft_exit_skip("cgroup v2 isn't mounted\n");
254
255	if (cg_read_strstr(root, "cgroup.subtree_control", "cpuset"))
256		if (cg_write(root, "cgroup.subtree_control", "+cpuset"))
257			ksft_exit_skip("Failed to set cpuset controller\n");
258
259	for (i = 0; i < ARRAY_SIZE(tests); i++) {
260		switch (tests[i].fn(root)) {
261		case KSFT_PASS:
262			ksft_test_result_pass("%s\n", tests[i].name);
263			break;
264		case KSFT_SKIP:
265			ksft_test_result_skip("%s\n", tests[i].name);
266			break;
267		default:
268			ret = EXIT_FAILURE;
269			ksft_test_result_fail("%s\n", tests[i].name);
270			break;
271		}
272	}
273
274	return ret;
275}
276