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