Path: blob/master/tools/testing/selftests/cgroup/test_cpuset.c
26285 views
// SPDX-License-Identifier: GPL-2.012#include <linux/limits.h>3#include <signal.h>45#include "../kselftest.h"6#include "cgroup_util.h"78static int idle_process_fn(const char *cgroup, void *arg)9{10(void)pause();11return 0;12}1314static int do_migration_fn(const char *cgroup, void *arg)15{16int object_pid = (int)(size_t)arg;1718if (setuid(TEST_UID))19return EXIT_FAILURE;2021// XXX checking /proc/$pid/cgroup would be quicker than wait22if (cg_enter(cgroup, object_pid) ||23cg_wait_for_proc_count(cgroup, 1))24return EXIT_FAILURE;2526return EXIT_SUCCESS;27}2829static int do_controller_fn(const char *cgroup, void *arg)30{31const char *child = cgroup;32const char *parent = arg;3334if (setuid(TEST_UID))35return EXIT_FAILURE;3637if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))38return EXIT_FAILURE;3940if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))41return EXIT_FAILURE;4243if (cg_read_strstr(child, "cgroup.controllers", "cpuset"))44return EXIT_FAILURE;4546if (cg_write(parent, "cgroup.subtree_control", "-cpuset"))47return EXIT_FAILURE;4849if (!cg_read_strstr(child, "cgroup.controllers", "cpuset"))50return EXIT_FAILURE;5152return EXIT_SUCCESS;53}5455/*56* Migrate a process between two sibling cgroups.57* The success should only depend on the parent cgroup permissions and not the58* migrated process itself (cpuset controller is in place because it uses59* security_task_setscheduler() in cgroup v1).60*61* Deliberately don't set cpuset.cpus in children to avoid definining migration62* permissions between two different cpusets.63*/64static int test_cpuset_perms_object(const char *root, bool allow)65{66char *parent = NULL, *child_src = NULL, *child_dst = NULL;67char *parent_procs = NULL, *child_src_procs = NULL, *child_dst_procs = NULL;68const uid_t test_euid = TEST_UID;69int object_pid = 0;70int ret = KSFT_FAIL;7172parent = cg_name(root, "cpuset_test_0");73if (!parent)74goto cleanup;75parent_procs = cg_name(parent, "cgroup.procs");76if (!parent_procs)77goto cleanup;78if (cg_create(parent))79goto cleanup;8081child_src = cg_name(parent, "cpuset_test_1");82if (!child_src)83goto cleanup;84child_src_procs = cg_name(child_src, "cgroup.procs");85if (!child_src_procs)86goto cleanup;87if (cg_create(child_src))88goto cleanup;8990child_dst = cg_name(parent, "cpuset_test_2");91if (!child_dst)92goto cleanup;93child_dst_procs = cg_name(child_dst, "cgroup.procs");94if (!child_dst_procs)95goto cleanup;96if (cg_create(child_dst))97goto cleanup;9899if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))100goto cleanup;101102if (cg_read_strstr(child_src, "cgroup.controllers", "cpuset") ||103cg_read_strstr(child_dst, "cgroup.controllers", "cpuset"))104goto cleanup;105106/* Enable permissions along src->dst tree path */107if (chown(child_src_procs, test_euid, -1) ||108chown(child_dst_procs, test_euid, -1))109goto cleanup;110111if (allow && chown(parent_procs, test_euid, -1))112goto cleanup;113114/* Fork a privileged child as a test object */115object_pid = cg_run_nowait(child_src, idle_process_fn, NULL);116if (object_pid < 0)117goto cleanup;118119/* Carry out migration in a child process that can drop all privileges120* (including capabilities), the main process must remain privileged for121* cleanup.122* Child process's cgroup is irrelevant but we place it into child_dst123* as hacky way to pass information about migration target to the child.124*/125if (allow ^ (cg_run(child_dst, do_migration_fn, (void *)(size_t)object_pid) == EXIT_SUCCESS))126goto cleanup;127128ret = KSFT_PASS;129130cleanup:131if (object_pid > 0) {132(void)kill(object_pid, SIGTERM);133(void)clone_reap(object_pid, WEXITED);134}135136cg_destroy(child_dst);137free(child_dst_procs);138free(child_dst);139140cg_destroy(child_src);141free(child_src_procs);142free(child_src);143144cg_destroy(parent);145free(parent_procs);146free(parent);147148return ret;149}150151static int test_cpuset_perms_object_allow(const char *root)152{153return test_cpuset_perms_object(root, true);154}155156static int test_cpuset_perms_object_deny(const char *root)157{158return test_cpuset_perms_object(root, false);159}160161/*162* Migrate a process between parent and child implicitely163* Implicit migration happens when a controller is enabled/disabled.164*165*/166static int test_cpuset_perms_subtree(const char *root)167{168char *parent = NULL, *child = NULL;169char *parent_procs = NULL, *parent_subctl = NULL, *child_procs = NULL;170const uid_t test_euid = TEST_UID;171int object_pid = 0;172int ret = KSFT_FAIL;173174parent = cg_name(root, "cpuset_test_0");175if (!parent)176goto cleanup;177parent_procs = cg_name(parent, "cgroup.procs");178if (!parent_procs)179goto cleanup;180parent_subctl = cg_name(parent, "cgroup.subtree_control");181if (!parent_subctl)182goto cleanup;183if (cg_create(parent))184goto cleanup;185186child = cg_name(parent, "cpuset_test_1");187if (!child)188goto cleanup;189child_procs = cg_name(child, "cgroup.procs");190if (!child_procs)191goto cleanup;192if (cg_create(child))193goto cleanup;194195/* Enable permissions as in a delegated subtree */196if (chown(parent_procs, test_euid, -1) ||197chown(parent_subctl, test_euid, -1) ||198chown(child_procs, test_euid, -1))199goto cleanup;200201/* Put a privileged child in the subtree and modify controller state202* from an unprivileged process, the main process remains privileged203* for cleanup.204* The unprivileged child runs in subtree too to avoid parent and205* internal-node constraing violation.206*/207object_pid = cg_run_nowait(child, idle_process_fn, NULL);208if (object_pid < 0)209goto cleanup;210211if (cg_run(child, do_controller_fn, parent) != EXIT_SUCCESS)212goto cleanup;213214ret = KSFT_PASS;215216cleanup:217if (object_pid > 0) {218(void)kill(object_pid, SIGTERM);219(void)clone_reap(object_pid, WEXITED);220}221222cg_destroy(child);223free(child_procs);224free(child);225226cg_destroy(parent);227free(parent_subctl);228free(parent_procs);229free(parent);230231return ret;232}233234235#define T(x) { x, #x }236struct cpuset_test {237int (*fn)(const char *root);238const char *name;239} tests[] = {240T(test_cpuset_perms_object_allow),241T(test_cpuset_perms_object_deny),242T(test_cpuset_perms_subtree),243};244#undef T245246int main(int argc, char *argv[])247{248char root[PATH_MAX];249int i, ret = EXIT_SUCCESS;250251if (cg_find_unified_root(root, sizeof(root), NULL))252ksft_exit_skip("cgroup v2 isn't mounted\n");253254if (cg_read_strstr(root, "cgroup.subtree_control", "cpuset"))255if (cg_write(root, "cgroup.subtree_control", "+cpuset"))256ksft_exit_skip("Failed to set cpuset controller\n");257258for (i = 0; i < ARRAY_SIZE(tests); i++) {259switch (tests[i].fn(root)) {260case KSFT_PASS:261ksft_test_result_pass("%s\n", tests[i].name);262break;263case KSFT_SKIP:264ksft_test_result_skip("%s\n", tests[i].name);265break;266default:267ret = EXIT_FAILURE;268ksft_test_result_fail("%s\n", tests[i].name);269break;270}271}272273return ret;274}275276277