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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.