xref: /linux/tools/testing/selftests/cgroup/test_cpuset.c (revision 90d32e92011eaae8e70a9169b4e7acf4ca8f9d3a)
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 
9 static int idle_process_fn(const char *cgroup, void *arg)
10 {
11 	(void)pause();
12 	return 0;
13 }
14 
15 static 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 
30 static 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  */
65 static 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 
131 cleanup:
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 
152 static int test_cpuset_perms_object_allow(const char *root)
153 {
154 	return test_cpuset_perms_object(root, true);
155 }
156 
157 static 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  */
167 static 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 
217 cleanup:
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 }
237 struct 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 
247 int 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), NULL))
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