selftests/cgroup: test clone3() into a previously killed cgroup

Once cgroup.kill had been written to a cgroup, a stale kill_seq
snapshot (taken in cgroup_css_set_fork() before the target cgroup was
resolved) caused every child subsequently cloned into that cgroup with
clone3(CLONE_INTO_CGROUP) to be SIGKILLed on the spot.

Add a regression test: create a cgroup, kill it while it is empty,
then clone a child into it and check that the child runs and exits
cleanly. On a kernel without the fix, the test fails:

  not ok 4 test_cgkill_clone_into_killed

The test is skipped on kernels without clone3() or without
CLONE_INTO_CGROUP.

Cc: Shakeel Butt <shakeel.butt@linux.dev>
Assisted-by: LLM
Signed-off-by: Etienne Perot <eperot@google.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
Etienne Perot 2026-08-28 21:52:52 +00:00 committed by Tejun Heo
parent 8e35992021
commit 3f4b7d1a49

View File

@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "kselftest.h"
@ -261,6 +262,59 @@ static int test_cgkill_forkbomb(const char *root)
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);
@ -269,6 +323,7 @@ struct cgkill_test {
T(test_cgkill_simple),
T(test_cgkill_tree),
T(test_cgkill_forkbomb),
T(test_cgkill_clone_into_killed),
};
#undef T