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