/* SPDX-License-Identifier: GPL-2.0 */ #include <errno.h>#include <linux/limits.h>#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h> #include "kselftest.h"#include "../pidfd/pidfd.h"#include "cgroup_util.h" /* * Kill the given cgroup and wait for the inotify signal. * If there are no events in 10 seconds, treat this as an error. * Then check that the cgroup is in the desired state. */static int cg_kill_wait(const char *cgroup){ int fd, ret = -1; fd = cg_prepare_for_wait(cgroup); if (fd < 0) return fd; ret = cg_write(cgroup, "cgroup.kill", "1"); if (ret) goto out; ret = cg_wait_for(fd); if (ret) goto out; out: close(fd); return ret;} /* * A simple process running in a sleep loop until being * re-parented. */static int child_fn(const char *cgroup, void *arg){ int ppid = getppid(); while (getppid() == ppid) usleep(1000); return getppid() == ppid;} static int test_cgkill_simple(const char *root){ pid_t pids[100]; int ret = KSFT_FAIL; char *cgroup = NULL; int i; cgroup = cg_name(root, "cg_test_simple"); if (!cgroup) goto cleanup; if (cg_create(cgroup)) goto cleanup; for (i = 0; i < 100; i++) pids[i] = cg_run_nowait(cgroup, child_fn, NULL); if (cg_wait_for_proc_count(cgroup, 100)) goto cleanup; if (cg_read_strcmp(cgroup, "cgroup.events", "populated 1\n")) goto cleanup; if (cg_kill_wait(cgroup)) goto cleanup; ret = KSFT_PASS; cleanup: for (i = 0; i < 100; i++) wait_for_pid(pids[i]); if (ret == KSFT_PASS && cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n")) ret = KSFT_FAIL; if (cgroup) cg_destroy(cgroup); free(cgroup); return ret;} /* * The test creates the following hierarchy: * A * / / \ \ * B E I K * /\ | * C D F * | * G * | * H * * with a process in C, H and 3 processes in K. * Then it tries to kill the whole tree. */static int test_cgkill_tree(const char *root){ pid_t pids[5]; char *cgroup[10] = {0}; int ret = KSFT_FAIL; int i; cgroup[0] = cg_name(root, "cg_test_tree_A"); if (!cgroup[0]) goto cleanup; cgroup[1] = cg_name(cgroup[0], "B"); if (!cgroup[1]) goto cleanup; cgroup[2] = cg_name(cgroup[1], "C"); if (!cgroup[2]) goto cleanup; cgroup[3] = cg_name(cgroup[1], "D"); if (!cgroup[3]) goto cleanup; cgroup[4] = cg_name(cgroup[0], "E"); if (!cgroup[4]) goto cleanup; cgroup[5] = cg_name(cgroup[4], "F"); if (!cgroup[5]) goto cleanup; cgroup[6] = cg_name(cgroup[5], "G"); if (!cgroup[6]) goto cleanup; cgroup[7] = cg_name(cgroup[6], "H"); if (!cgroup[7]) goto cleanup; cgroup[8] = cg_name(cgroup[0], "I"); if (!cgroup[8]) goto cleanup; cgroup[9] = cg_name(cgroup[0], "K"); if (!cgroup[9]) goto cleanup; for (i = 0; i < 10; i++) if (cg_create(cgroup[i])) goto cleanup; pids[0] = cg_run_nowait(cgroup[2], child_fn, NULL); pids[1] = cg_run_nowait(cgroup[7], child_fn, NULL); pids[2] = cg_run_nowait(cgroup[9], child_fn, NULL); pids[3] = cg_run_nowait(cgroup[9], child_fn, NULL); pids[4] = cg_run_nowait(cgroup[9], child_fn, NULL); /* * Wait until all child processes will enter * corresponding cgroups. */ if (cg_wait_for_proc_count(cgroup[2], 1) || cg_wait_for_proc_count(cgroup[7], 1) || cg_wait_for_proc_count(cgroup[9], 3)) goto cleanup; /* * Kill A and check that we get an empty notification. */ if (cg_kill_wait(cgroup[0])) goto cleanup; ret = KSFT_PASS; cleanup: for (i = 0; i < 5; i++) wait_for_pid(pids[i]); if (ret == KSFT_PASS && cg_read_strcmp_wait(cgroup[0], "cgroup.events", "populated 0\n")) ret = KSFT_FAIL; for (i = 9; i >= 0 && cgroup[i]; i--) { cg_destroy(cgroup[i]); free(cgroup[i]); } return ret;} static int forkbomb_fn(const char *cgroup, void *arg){ int ppid; fork(); fork(); ppid = getppid(); while (getppid() == ppid) usleep(1000); return getppid() == ppid;} /* * The test runs a fork bomb in a cgroup and tries to kill it. */static int test_cgkill_forkbomb(const char *root){ int ret = KSFT_FAIL; char *cgroup = NULL; pid_t pid = -ESRCH; cgroup = cg_name(root, "cg_forkbomb_test"); if (!cgroup) goto cleanup; if (cg_create(cgroup)) goto cleanup; pid = cg_run_nowait(cgroup, forkbomb_fn, NULL); if (pid < 0) goto cleanup; usleep(100000); if (cg_kill_wait(cgroup)) goto cleanup; if (cg_wait_for_proc_count(cgroup, 0)) goto cleanup; ret = KSFT_PASS; cleanup: if (pid > 0) wait_for_pid(pid); if (ret == KSFT_PASS && cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n")) ret = KSFT_FAIL; if (cgroup) cg_destroy(cgroup); free(cgroup); return ret;} /* * Test that a cgroup that was killed in the past can still be the target * of clone3(CLONE_INTO_CGROUP): writing cgroup.kill must only kill the * tasks in the cgroup at the time of the write, not tasks cloned into * it afterwards. */static int test_cgkill_clone_into_killed(const char *root){ pid_t pid; int cgroup_fd = -EBADF; int ret = KSFT_FAIL; char *cgroup = NULL; cgroup = cg_name(root, "cg_test_clone_into_killed"); if (!cgroup) goto cleanup; if (cg_create(cgroup)) goto cleanup; /* Kill the cgroup while it is still empty. */ if (cg_write(cgroup, "cgroup.kill", "1")) goto cleanup; cgroup_fd = dirfd_open_opath(cgroup); if (cgroup_fd < 0) goto cleanup; pid = clone_into_cgroup(cgroup_fd); if (pid < 0) { if (errno == ENOSYS) ret = KSFT_SKIP; goto cleanup; } if (pid == 0) exit(EXIT_SUCCESS); /* The child must not be SIGKILLed; it has to exit cleanly. */ if (clone_reap(pid, WEXITED) != EXIT_SUCCESS) goto cleanup; ret = KSFT_PASS; cleanup: if (cgroup_fd >= 0) close(cgroup_fd); if (cgroup) cg_destroy(cgroup); free(cgroup); return ret;} #define T(x) { x, #x }struct cgkill_test { int (*fn)(const char *root); const char *name;} tests[] = { T(test_cgkill_simple), T(test_cgkill_tree), T(test_cgkill_forkbomb), T(test_cgkill_clone_into_killed),};#undef T int main(int argc, char *argv[]){ char root[PATH_MAX]; int i; ksft_print_header(); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: ksft_test_result_pass("%s\n", tests[i].name); break; case KSFT_SKIP: ksft_test_result_skip("%s\n", tests[i].name); break; default: ksft_test_result_fail("%s\n", tests[i].name); break; } } ksft_finished();}
/* SPDX-License-Identifier: GPL-2.0 */
#include <errno.h>
#include <linux/limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "kselftest.h"
#include "../pidfd/pidfd.h"
#include "cgroup_util.h"
/*
* Kill the given cgroup and wait for the inotify signal.
* If there are no events in 10 seconds, treat this as an error.
* Then check that the cgroup is in the desired state.
*/
static int cg_kill_wait(const char *cgroup)
{
int fd, ret = -1;
fd = cg_prepare_for_wait(cgroup);
if (fd < 0)
return fd;
ret = cg_write(cgroup, "cgroup.kill", "1");
if (ret)
goto out;
ret = cg_wait_for(fd);
if (ret)
goto out;
out:
close(fd);
return ret;
}
/*
* A simple process running in a sleep loop until being
* re-parented.
*/
static int child_fn(const char *cgroup, void *arg)
{
int ppid = getppid();
while (getppid() == ppid)
usleep(1000);
return getppid() == ppid;
}
static int test_cgkill_simple(const char *root)
{
pid_t pids[100];
int ret = KSFT_FAIL;
char *cgroup = NULL;
int i;
cgroup = cg_name(root, "cg_test_simple");
if (!cgroup)
goto cleanup;
if (cg_create(cgroup))
goto cleanup;
for (i = 0; i < 100; i++)
pids[i] = cg_run_nowait(cgroup, child_fn, NULL);
if (cg_wait_for_proc_count(cgroup, 100))
goto cleanup;
if (cg_read_strcmp(cgroup, "cgroup.events", "populated 1\n"))
goto cleanup;
if (cg_kill_wait(cgroup))
goto cleanup;
ret = KSFT_PASS;
cleanup:
for (i = 0; i < 100; i++)
wait_for_pid(pids[i]);
if (ret == KSFT_PASS &&
cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))
ret = KSFT_FAIL;
if (cgroup)
cg_destroy(cgroup);
free(cgroup);
return ret;
}
/*
* The test creates the following hierarchy:
* A
* / / \ \
* B E I K
* /\ |
* C D F
* |
* G
* |
* H
*
* with a process in C, H and 3 processes in K.
* Then it tries to kill the whole tree.
*/
static int test_cgkill_tree(const char *root)
{
pid_t pids[5];
char *cgroup[10] = {0};
int ret = KSFT_FAIL;
int i;
cgroup[0] = cg_name(root, "cg_test_tree_A");
if (!cgroup[0])
goto cleanup;
cgroup[1] = cg_name(cgroup[0], "B");
if (!cgroup[1])
goto cleanup;
cgroup[2] = cg_name(cgroup[1], "C");
if (!cgroup[2])
goto cleanup;
cgroup[3] = cg_name(cgroup[1], "D");
if (!cgroup[3])
goto cleanup;
cgroup[4] = cg_name(cgroup[0], "E");
if (!cgroup[4])
goto cleanup;
cgroup[5] = cg_name(cgroup[4], "F");
if (!cgroup[5])
goto cleanup;
cgroup[6] = cg_name(cgroup[5], "G");
if (!cgroup[6])
goto cleanup;
cgroup[7] = cg_name(cgroup[6], "H");
if (!cgroup[7])
goto cleanup;
cgroup[8] = cg_name(cgroup[0], "I");
if (!cgroup[8])
goto cleanup;
cgroup[9] = cg_name(cgroup[0], "K");
if (!cgroup[9])
goto cleanup;
for (i = 0; i < 10; i++)
if (cg_create(cgroup[i]))
goto cleanup;
pids[0] = cg_run_nowait(cgroup[2], child_fn, NULL);
pids[1] = cg_run_nowait(cgroup[7], child_fn, NULL);
pids[2] = cg_run_nowait(cgroup[9], child_fn, NULL);
pids[3] = cg_run_nowait(cgroup[9], child_fn, NULL);
pids[4] = cg_run_nowait(cgroup[9], child_fn, NULL);
/*
* Wait until all child processes will enter
* corresponding cgroups.
*/
if (cg_wait_for_proc_count(cgroup[2], 1) ||
cg_wait_for_proc_count(cgroup[7], 1) ||
cg_wait_for_proc_count(cgroup[9], 3))
goto cleanup;
/*
* Kill A and check that we get an empty notification.
*/
if (cg_kill_wait(cgroup[0]))
goto cleanup;
ret = KSFT_PASS;
cleanup:
for (i = 0; i < 5; i++)
wait_for_pid(pids[i]);
if (ret == KSFT_PASS &&
cg_read_strcmp_wait(cgroup[0], "cgroup.events",
"populated 0\n"))
ret = KSFT_FAIL;
for (i = 9; i >= 0 && cgroup[i]; i--) {
cg_destroy(cgroup[i]);
free(cgroup[i]);
}
return ret;
}
static int forkbomb_fn(const char *cgroup, void *arg)
{
int ppid;
fork();
fork();
ppid = getppid();
while (getppid() == ppid)
usleep(1000);
return getppid() == ppid;
}
/*
* The test runs a fork bomb in a cgroup and tries to kill it.
*/
static int test_cgkill_forkbomb(const char *root)
{
int ret = KSFT_FAIL;
char *cgroup = NULL;
pid_t pid = -ESRCH;
cgroup = cg_name(root, "cg_forkbomb_test");
if (!cgroup)
goto cleanup;
if (cg_create(cgroup))
goto cleanup;
pid = cg_run_nowait(cgroup, forkbomb_fn, NULL);
if (pid < 0)
goto cleanup;
usleep(100000);
if (cg_kill_wait(cgroup))
goto cleanup;
if (cg_wait_for_proc_count(cgroup, 0))
goto cleanup;
ret = KSFT_PASS;
cleanup:
if (pid > 0)
wait_for_pid(pid);
if (ret == KSFT_PASS &&
cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))
ret = KSFT_FAIL;
if (cgroup)
cg_destroy(cgroup);
free(cgroup);
return ret;
}
/*
* Test that a cgroup that was killed in the past can still be the target
* of clone3(CLONE_INTO_CGROUP): writing cgroup.kill must only kill the
* tasks in the cgroup at the time of the write, not tasks cloned into
* it afterwards.
*/
static int test_cgkill_clone_into_killed(const char *root)
{
pid_t pid;
int cgroup_fd = -EBADF;
int ret = KSFT_FAIL;
char *cgroup = NULL;
cgroup = cg_name(root, "cg_test_clone_into_killed");
if (!cgroup)
goto cleanup;
if (cg_create(cgroup))
goto cleanup;
/* Kill the cgroup while it is still empty. */
if (cg_write(cgroup, "cgroup.kill", "1"))
goto cleanup;
cgroup_fd = dirfd_open_opath(cgroup);
if (cgroup_fd < 0)
goto cleanup;
pid = clone_into_cgroup(cgroup_fd);
if (pid < 0) {
if (errno == ENOSYS)
ret = KSFT_SKIP;
goto cleanup;
}
if (pid == 0)
exit(EXIT_SUCCESS);
/* The child must not be SIGKILLed; it has to exit cleanly. */
if (clone_reap(pid, WEXITED) != EXIT_SUCCESS)
goto cleanup;
ret = KSFT_PASS;
cleanup:
if (cgroup_fd >= 0)
close(cgroup_fd);
if (cgroup)
cg_destroy(cgroup);
free(cgroup);
return ret;
}
#define T(x) { x, #x }
struct cgkill_test {
int (*fn)(const char *root);
const char *name;
} tests[] = {
T(test_cgkill_simple),
T(test_cgkill_tree),
T(test_cgkill_forkbomb),
T(test_cgkill_clone_into_killed),
};
#undef T
int main(int argc, char *argv[])
{
char root[PATH_MAX];
int i;
ksft_print_header();
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:
ksft_test_result_pass("%s\n", tests[i].name);
break;
case KSFT_SKIP:
ksft_test_result_skip("%s\n", tests[i].name);
break;
default:
ksft_test_result_fail("%s\n", tests[i].name);
break;
}
}
ksft_finished();
}