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 <[email protected]>
Assisted-by: LLM
Signed-off-by: Etienne Perot <[email protected]>
---
 tools/testing/selftests/cgroup/test_kill.c | 55 ++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/tools/testing/selftests/cgroup/test_kill.c 
b/tools/testing/selftests/cgroup/test_kill.c
index f6cd23a8ecc7..5ba8e285b2f3 100644
--- a/tools/testing/selftests/cgroup/test_kill.c
+++ b/tools/testing/selftests/cgroup/test_kill.c
@@ -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
 
-- 
2.55.0.897.gb25b4bd76c-goog


Reply via email to