cgroup: Fixes for v7.3-rc1

- After cgroup.kill was written to a cgroup, every child cloned into it
   with CLONE_INTO_CGROUP was spuriously killed because the fork path
   snapshotted the kill counter before resolving the target cgroup.
 
 - Releasing an isolated cpuset partition dropped the isolation of CPUs
   isolated on the kernel command line.
 
 - Selftest and documentation fixes.
 -----BEGIN PGP SIGNATURE-----
 
 iIQEABYKACwWIQTfIjM1kS57o3GsC/uxYfJx3gVYGQUCapXm8w4cdGpAa2VybmVs
 Lm9yZwAKCRCxYfJx3gVYGbhtAQCfUc4oanF94uiAGzH2UAA2HIorLT4TDjuDj/oM
 qrqeLQEA1p2XQz56SYXhK8fG+fy+Ep3xxqS2gStmtYL20ie4lQc=
 =i4QU
 -----END PGP SIGNATURE-----

Merge tag 'cgroup-for-7.3-rc1-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup

Pull cgroup fixes from Tejun Heo:

 - After cgroup.kill was written to a cgroup, every child cloned into it
   with CLONE_INTO_CGROUP was spuriously killed because the fork path
   snapshotted the kill counter before resolving the target cgroup

 - Releasing an isolated cpuset partition dropped the isolation of CPUs
   isolated on the kernel command line

 - Selftest and documentation fixes

* tag 'cgroup-for-7.3-rc1-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup:
  selftests/cgroup: test clone3() into a previously killed cgroup
  cgroup: fix spurious SIGKILL of CLONE_INTO_CGROUP children
  selftests/cgroup: Add test for preserving boot-isolated CPUs
  cgroup/cpuset: Preserve boot-isolated CPUs on partition release
  selftests/cgroup: Drop invalid boot isolation comparison
  docs: cgroup-v2: fix misc.events key format description
  selftests/cgroup: Fix cg_run_in_subcgroups ignoring arg parameter
  selftests/cgroup: set the test plan after the setup checks
This commit is contained in:
Linus Torvalds 2026-08-31 14:20:32 -07:00
commit c3b510de42
14 changed files with 159 additions and 32 deletions

View File

@ -3070,7 +3070,7 @@ resources (res_a and res_b) are registered then:
change in this file generates a file modified event. All fields in
this file are hierarchical.
max
<res>.max
The number of times the cgroup's resource usage was
about to go over the max boundary.

View File

@ -527,7 +527,10 @@ struct cgroup {
int nr_threaded_children; /* # of live threaded child cgroups */
/* sequence number for cgroup.kill, serialized by css_set_lock. */
/*
* Sequence number for cgroup.kill. Incremented with both cgroup_mutex
* and css_set_lock held. Readers hold either one.
*/
unsigned int kill_seq;
struct kernfs_node *kn; /* cgroup kernfs entry */

View File

@ -6873,10 +6873,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
get_css_set(cset);
if (kargs->cgrp)
kargs->kill_seq = kargs->cgrp->kill_seq;
else
kargs->kill_seq = cset->dfl_cgrp->kill_seq;
kargs->kill_seq = cset->dfl_cgrp->kill_seq;
spin_unlock_irq(&css_set_lock);
if (!(kargs->flags & CLONE_INTO_CGROUP)) {
@ -6940,6 +6937,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
put_css_set(cset);
kargs->cgrp = dst_cgrp;
kargs->kill_seq = dst_cgrp->kill_seq;
return ret;
err:

View File

@ -1259,6 +1259,28 @@ static void reset_partition_data(struct cpuset *cs)
cpumask_copy(cs->effective_cpus, parent->effective_cpus);
}
/* Return true if isolated_cpus changes. */
static bool isolated_cpu_update(int new_prs, int cpu)
{
lockdep_assert_held(&callback_lock);
lockdep_assert_held(&cpuset_mutex);
if (new_prs == PRS_ISOLATED) {
if (cpumask_test_cpu(cpu, isolated_cpus))
return false;
cpumask_set_cpu(cpu, isolated_cpus);
return true;
}
/* CPUs isolated at boot must remain isolated. */
if (!cpumask_test_cpu(cpu,
housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)) ||
!cpumask_test_cpu(cpu, isolated_cpus))
return false;
cpumask_clear_cpu(cpu, isolated_cpus);
return true;
}
/*
* isolated_cpus_update - Update the isolated_cpus mask
* @old_prs: old partition_root_state
@ -1267,19 +1289,16 @@ static void reset_partition_data(struct cpuset *cs)
*/
static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus)
{
bool updated = false;
int cpu;
WARN_ON_ONCE(old_prs == new_prs);
lockdep_assert_held(&callback_lock);
lockdep_assert_held(&cpuset_mutex);
if (new_prs == PRS_ISOLATED) {
if (cpumask_subset(xcpus, isolated_cpus))
return;
cpumask_or(isolated_cpus, isolated_cpus, xcpus);
} else {
if (!cpumask_intersects(xcpus, isolated_cpus))
return;
cpumask_andnot(isolated_cpus, isolated_cpus, xcpus);
}
update_housekeeping = true;
for_each_cpu(cpu, xcpus)
updated |= isolated_cpu_update(new_prs, cpu);
if (updated)
update_housekeeping = true;
}
/*

View File

@ -919,7 +919,6 @@ int main(int argc, char *argv[])
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) {
if (setup_named_v1_root(root, sizeof(root), CG_NAMED_NAME))
ksft_exit_skip("cgroup v2 isn't mounted and could not setup named v1 hierarchy\n");
@ -932,6 +931,7 @@ int main(int argc, char *argv[])
ksft_exit_skip("Failed to set memory controller\n");
post_v2_setup:
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -832,7 +832,6 @@ int main(int argc, char *argv[])
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -840,6 +839,7 @@ int main(int argc, char *argv[])
if (cg_write(root, "cgroup.subtree_control", "+cpu"))
ksft_exit_skip("Failed to set cpu controller\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -497,7 +497,6 @@ int main(int argc, char *argv[])
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -505,6 +504,7 @@ int main(int argc, char *argv[])
if (cg_write(root, "cgroup.subtree_control", "+cpuset"))
ksft_exit_skip("Failed to set cpuset controller\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -797,7 +797,6 @@ check_isolcpus()
EXPECTED_ISOLCPUS=$1
ISCPUS=${CGROUP2}/cpuset.cpus.isolated
ISOLCPUS=$(cat $ISCPUS)
HKICPUS=$(cat /sys/devices/system/cpu/isolated)
LASTISOLCPU=
SCHED_DOMAINS=/sys/kernel/debug/sched/domains
if [[ $EXPECTED_ISOLCPUS = . ]]
@ -835,11 +834,6 @@ check_isolcpus()
ISOLCPUS=
EXPECTED_ISOLCPUS=$EXPECTED_SDOMAIN
#
# The inverse of HK_TYPE_DOMAIN cpumask in $HKICPUS should match $ISOLCPUS
#
[[ "$ISOLCPUS" != "$HKICPUS" ]] && return 1
#
# Use the sched domain in debugfs to check isolated CPUs, if available
#
@ -1161,6 +1155,63 @@ test_isolated()
pause 0.05
}
#
# Select an online CPU isolated from scheduler domains at boot.
# $1: test name used in the skip message
#
get_boot_isolated_cpu()
{
TEST_NAME=$1
BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated
[[ -r $BOOT_ISOLATED_FILE ]] || {
echo "$TEST_NAME test SKIPPED: boot isolation state unavailable"
return 1
}
BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE)
[[ -n "$BOOT_CPUS" ]] || {
echo "$TEST_NAME test SKIPPED: no boot-isolated CPU"
return 1
}
BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//')
CPU_ONLINE=/sys/devices/system/cpu/cpu${BOOT_CPU}/online
[[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || {
echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is offline"
return 1
}
}
#
# A CPU isolated at boot must stay isolated after it is released by a dynamic
# isolated partition.
#
test_boot_isolated()
{
TEST_NAME="Boot-isolated CPU partition release"
get_boot_isolated_cpu "$TEST_NAME" || return 0
echo "Running $TEST_NAME test ..."
cd $CGROUP2/test
echo member > cpuset.cpus.partition
echo $BOOT_CPU > cpuset.cpus
[[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
echo "" > cpuset.cpus
cd $CGROUP2
return 0
}
test_partition isolated
test_partition member
check_isolcpus "." || {
echo "Boot-isolated CPU $BOOT_CPU was lost after partition release"
exit 1
}
echo "" > cpuset.cpus
cd $CGROUP2
echo "$TEST_NAME test PASSED."
}
#
# Wait for inotify event for the given file and read it
# $1: cgroup file to wait for
@ -1232,5 +1283,6 @@ trap cleanup 0 2 3 6
run_state_test TEST_MATRIX
run_remote_state_test REMOTE_TEST_MATRIX
test_isolated
test_boot_isolated
test_inotify
echo "All tests PASSED."

View File

@ -1491,9 +1491,9 @@ int main(int argc, char *argv[])
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
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:

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
@ -278,9 +333,9 @@ int main(int argc, char *argv[])
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
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:

View File

@ -145,7 +145,7 @@ static int cg_run_in_subcgroups(const char *parent,
return -1;
}
if (cg_run(child, fn, NULL)) {
if (cg_run(child, fn, arg)) {
cg_destroy(child);
free(child);
return -1;
@ -426,7 +426,6 @@ int main(int argc, char **argv)
int i;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -441,6 +440,7 @@ int main(int argc, char **argv)
if (cg_write(root, "cgroup.subtree_control", "+memory"))
ksft_exit_skip("Failed to set memory controller\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -1798,7 +1798,6 @@ int main(int argc, char **argv)
page_size = BUF_SIZE;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -1823,6 +1822,7 @@ int main(int argc, char **argv)
ksft_exit_skip("Failed to query cgroup mount option\n");
has_localevents = proc_status;
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -148,7 +148,6 @@ int main(int argc, char **argv)
char root[PATH_MAX];
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -163,6 +162,7 @@ int main(int argc, char **argv)
if (cg_write(root, "cgroup.subtree_control", "+pids"))
ksft_exit_skip("Failed to set pids controller\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (int i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS:

View File

@ -819,7 +819,6 @@ int main(int argc, char **argv)
page_size = BUF_SIZE;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests));
if (cg_find_unified_root(root, sizeof(root), NULL))
ksft_exit_skip("cgroup v2 isn't mounted\n");
@ -836,6 +835,7 @@ int main(int argc, char **argv)
if (cg_write(root, "cgroup.subtree_control", "+memory"))
ksft_exit_skip("Failed to set memory controller\n");
ksft_set_plan(ARRAY_SIZE(tests));
for (i = 0; i < ARRAY_SIZE(tests); i++) {
switch (tests[i].fn(root)) {
case KSFT_PASS: