1 // SPDX-License-Identifier: GPL-2.0
3 #include <linux/limits.h>
6 #include "../kselftest.h"
7 #include "cgroup_util.h"
9 static int idle_process_fn(const char *cgroup
, void *arg
)
15 static int do_migration_fn(const char *cgroup
, void *arg
)
17 int object_pid
= (int)(size_t)arg
;
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))
30 static int do_controller_fn(const char *cgroup
, void *arg
)
32 const char *child
= cgroup
;
33 const char *parent
= arg
;
38 if (!cg_read_strstr(child
, "cgroup.controllers", "cpuset"))
41 if (cg_write(parent
, "cgroup.subtree_control", "+cpuset"))
44 if (cg_read_strstr(child
, "cgroup.controllers", "cpuset"))
47 if (cg_write(parent
, "cgroup.subtree_control", "-cpuset"))
50 if (!cg_read_strstr(child
, "cgroup.controllers", "cpuset"))
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).
62 * Deliberately don't set cpuset.cpus in children to avoid definining migration
63 * permissions between two different cpusets.
65 static int test_cpuset_perms_object(const char *root
, bool allow
)
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
;
73 parent
= cg_name(root
, "cpuset_test_0");
76 parent_procs
= cg_name(parent
, "cgroup.procs");
79 if (cg_create(parent
))
82 child_src
= cg_name(parent
, "cpuset_test_1");
85 child_src_procs
= cg_name(child_src
, "cgroup.procs");
88 if (cg_create(child_src
))
91 child_dst
= cg_name(parent
, "cpuset_test_2");
94 child_dst_procs
= cg_name(child_dst
, "cgroup.procs");
97 if (cg_create(child_dst
))
100 if (cg_write(parent
, "cgroup.subtree_control", "+cpuset"))
103 if (cg_read_strstr(child_src
, "cgroup.controllers", "cpuset") ||
104 cg_read_strstr(child_dst
, "cgroup.controllers", "cpuset"))
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))
112 if (allow
&& chown(parent_procs
, test_euid
, -1))
115 /* Fork a privileged child as a test object */
116 object_pid
= cg_run_nowait(child_src
, idle_process_fn
, NULL
);
120 /* Carry out migration in a child process that can drop all privileges
121 * (including capabilities), the main process must remain privileged for
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.
126 if (allow
^ (cg_run(child_dst
, do_migration_fn
, (void *)(size_t)object_pid
) == EXIT_SUCCESS
))
132 if (object_pid
> 0) {
133 (void)kill(object_pid
, SIGTERM
);
134 (void)clone_reap(object_pid
, WEXITED
);
137 cg_destroy(child_dst
);
138 free(child_dst_procs
);
141 cg_destroy(child_src
);
142 free(child_src_procs
);
152 static int test_cpuset_perms_object_allow(const char *root
)
154 return test_cpuset_perms_object(root
, true);
157 static int test_cpuset_perms_object_deny(const char *root
)
159 return test_cpuset_perms_object(root
, false);
163 * Migrate a process between parent and child implicitely
164 * Implicit migration happens when a controller is enabled/disabled.
167 static int test_cpuset_perms_subtree(const char *root
)
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
;
175 parent
= cg_name(root
, "cpuset_test_0");
178 parent_procs
= cg_name(parent
, "cgroup.procs");
181 parent_subctl
= cg_name(parent
, "cgroup.subtree_control");
184 if (cg_create(parent
))
187 child
= cg_name(parent
, "cpuset_test_1");
190 child_procs
= cg_name(child
, "cgroup.procs");
193 if (cg_create(child
))
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))
202 /* Put a privileged child in the subtree and modify controller state
203 * from an unprivileged process, the main process remains privileged
205 * The unprivileged child runs in subtree too to avoid parent and
206 * internal-node constraing violation.
208 object_pid
= cg_run_nowait(child
, idle_process_fn
, NULL
);
212 if (cg_run(child
, do_controller_fn
, parent
) != EXIT_SUCCESS
)
218 if (object_pid
> 0) {
219 (void)kill(object_pid
, SIGTERM
);
220 (void)clone_reap(object_pid
, WEXITED
);
236 #define T(x) { x, #x }
238 int (*fn
)(const char *root
);
241 T(test_cpuset_perms_object_allow
),
242 T(test_cpuset_perms_object_deny
),
243 T(test_cpuset_perms_subtree
),
247 int main(int argc
, char *argv
[])
250 int i
, ret
= EXIT_SUCCESS
;
252 if (cg_find_unified_root(root
, sizeof(root
), NULL
))
253 ksft_exit_skip("cgroup v2 isn't mounted\n");
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");
259 for (i
= 0; i
< ARRAY_SIZE(tests
); i
++) {
260 switch (tests
[i
].fn(root
)) {
262 ksft_test_result_pass("%s\n", tests
[i
].name
);
265 ksft_test_result_skip("%s\n", tests
[i
].name
);
269 ksft_test_result_fail("%s\n", tests
[i
].name
);