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