brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · 4034d14 Raw
276 lines · c
1// SPDX-License-Identifier: GPL-2.02 3#include <linux/limits.h>4#include <signal.h>5 6#include "../kselftest.h"7#include "cgroup_util.h"8 9static int idle_process_fn(const char *cgroup, void *arg)10{11	(void)pause();12	return 0;13}14 15static 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 wait23	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 30static 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 the59 * migrated process itself (cpuset controller is in place because it uses60 * security_task_setscheduler() in cgroup v1).61 *62 * Deliberately don't set cpuset.cpus in children to avoid definining migration63 * permissions between two different cpusets.64 */65static 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 privileges121	 * (including capabilities), the main process must remain privileged for122	 * cleanup.123	 * Child process's cgroup is irrelevant but we place it into child_dst124	 * 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 131cleanup: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 152static int test_cpuset_perms_object_allow(const char *root)153{154	return test_cpuset_perms_object(root, true);155}156 157static 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 implicitely164 * Implicit migration happens when a controller is enabled/disabled.165 *166 */167static 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 state203	 * from an unprivileged process, the main process remains privileged204	 * for cleanup.205	 * The unprivileged child runs in subtree too to avoid parent and206	 * 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 217cleanup: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 }237struct 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 T246 247int 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