{"cve":"CVE-2026-46223","nvd":{"published":"2026-05-28T10:16:37.913","modified":"2026-06-17T10:53:21.750","status":"Analyzed","description":"In the Linux kernel, the following vulnerability has been resolved:\n\ncgroup: Defer css percpu_ref kill on rmdir until cgroup is depopulated\n\nA chain of commits going back to v7.0 reworked rmdir to satisfy the\ncontroller invariant that a subsystem's ->css_offline() must not run while\ntasks are still doing kernel-side work in the cgroup.\n\n[1] d245698d727a (\"cgroup: Defer task cgroup unlink until after the task is done switching out\")\n[2] a72f73c4dd9b (\"cgroup: Don't expose dead tasks in cgroup\")\n[3] 1b164b876c36 (\"cgroup: Wait for dying tasks to leave on rmdir\")\n[4] 4c56a8ac6869 (\"cgroup: Fix cgroup_drain_dying() testing the wrong condition\")\n[5] 13e786b64bd3 (\"cgroup: Increment nr_dying_subsys_* from rmdir context\")\n\n[1] moved task cset unlink from do_exit() to finish_task_switch() so a\ntask's cset link drops only after the task has fully stopped scheduling.\nThat made tasks past exit_signals() linger on cset->tasks until their final\ncontext switch, which led to a series of problems as what userspace expected\nto see after rmdir diverged from what the kernel needs to wait for. [2]-[5]\ntried to bridge that divergence: [2] filtered the exiting tasks from\ncgroup.procs; [3] had rmdir(2) sleep in TASK_UNINTERRUPTIBLE for them; [4]\nfixed the wait's condition; [5] made nr_dying_subsys_* visible\nsynchronously.\n\nThe cgroup_drain_dying() wait in [3] turned out to be a dead end. When the\nrmdir caller is also the reaper of a zombie that pins a pidns teardown (e.g.\nhost PID 1 systemd reaping orphan pids that were re-parented to it during\nthe same teardown), rmdir blocks in TASK_UNINTERRUPTIBLE waiting for those\npids to free, the pids can't free because PID 1 is the reaper and it's stuck\nin rmdir, and the system A-A deadlocks. No internal lock ordering breaks\nthis; the wait itself is the bug.\n\nThe css killing side that drove the original reorder, however, can be made\ncleanly asynchronous: ->css_offline() is already async, run from\ncss_killed_work_fn() driven by percpu_ref_kill_and_confirm(). The fix is to\nmake that chain start only after all tasks have left the cgroup. rmdir's\nuser-visible side then returns as soon as cgroup.procs and friends are\nempty, while ->css_offline() still runs only after the cgroup is fully\ndrained.\n\nVerified by the original reproducer (pidns teardown + zombie reaper, runs\nunder vng) which hangs vanilla and succeeds here, and by per-commit\ndeterministic repros for [2], [3], [4], [5] with a boot parameter that\nwidens the post-exit_signals() window so each state is reliably reachable.\nSome stress tests on top of that.\n\ncgroup_apply_control_disable() has the same shape of pre-existing race:\nwhen a controller is disabled via subtree_control, kill_css() ran\nsynchronously while tasks past exit_signals() could still be linked to\nthe cgroup's csets, and ->css_offline() could fire before they drained.\nThis patch preserves the existing synchronous behavior at that call site\n(kill_css_sync() + kill_css_finish() back-to-back) and a follow-up patch\nwill defer kill_css_finish() there using a per-css trigger.\n\nThis seems like the right approach and I don't see problems with it. The\nchanges are somewhat invasive but not excessively so, so backporting to\n-stable should be okay. If something does turn out to be wrong, the fallback\nis to revert the entire chain ([1]-[5]) and rework in the development branch\ninstead.\n\nv2: Pin cgrp across the deferred destroy work with explicit\n    cgroup_get()/cgroup_put() around queue_work() and the work_fn. v1\n    wasn't actually broken (ordered cgroup_offline_wq + queue_work order\n    in cgroup_task_dead() saved it) but the explicit ref removes the\n    dependency on those non-obvious invariants. Also note the\n    pre-existing cgroup_apply_control_disable() race in the description;\n    a follow-up will defer kill_css_finish() there.","cvss_v4":null,"cvss_v3":{"version":"3.1","vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","score":5.5},"cwes":["CWE-667"],"references":["https://git.kernel.org/stable/c/33fa2e6b1507a0a377a151a8826438bedad1d0b0","https://git.kernel.org/stable/c/93618edf753838a727dbff63c7c291dee22d656b"],"products":["linux:linux_kernel"],"source":"nvd","synced_at":"2026-10-19T22:36:28.938604137+00:00"},"epss":{"cve":"CVE-2026-46223","score":0.00083,"percentile":0.00236,"date":"2026-10-31"},"kev":null,"osv":[],"attribution":["This product uses the NVD API but is not endorsed or certified by the NVD.","EPSS scores: FIRST Exploit Prediction Scoring System, https://www.first.org/epss","Known exploited vulnerabilities: CISA Known Exploited Vulnerabilities Catalog, https://www.cisa.gov/known-exploited-vulnerabilities-catalog","Package advisories: OSV (https://osv.dev), from the GitHub Advisory Database, PyPA, Go, RustSec and other ecosystem databases, under CC-BY 4.0 or CC0","CVE records: the CVE Program's CVE List (https://www.cve.org), under its terms of use (https://www.cve.org/Legal/TermsOfUse). CVE is a registered trademark of The MITRE Corporation; this product is not affiliated with the CVE Program."]}