Re: [PATCH] cgroup: prevent use-after-free during namespace root replacement
From: Bradley Morgan
Date: Wed Sep 23 2026 - 17:05:48 EST
On 23 September 2026 21:49:36 BST, "Jérémy Jean"
<Jeremy.Jean@xxxxxxxxxxxxxxxxx> wrote:
>copy_cgroup_ns() pins the creator's css_set, but cgroup_post_fork()
>replaces root_cset and releases the namespace's reference to that css_set
>after the child is visible to pid and pidfd lookups. A task joining the
>new namespace can race with this replacement and access the original
>css_set or its cgroup after they are freed.
>
>KASAN reports:
>
> BUG: KASAN: slab-use-after-free in kernfs_get.part.0+0x47/0x60
> Write of size 4 at addr ff1100000379f320 by task ns-path-probe/69
> kernfs_walk_and_get_ns+0x1cc/0x280
> cgroup_get_from_path+0xfb/0x340
> nft_socket_cgroup_subtree_level+0x14/0x1a0
>
>Keep the initial root pinned until namespace destruction, taking a
>separate reference to the final root only when the roots differ.
Why would this be bad?
>
>Fixes: ef2c41cf38a7 ("clone3: allow spawning processes into cgroups")
>Assisted-by: LLM
>Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
>---
> include/linux/cgroup_namespace.h | 2 ++
> kernel/cgroup/cgroup.c | 9 ++++++---
> kernel/cgroup/namespace.c | 3 +++
> 3 files changed, 11 insertions(+), 3 deletions(-)
>
>diff --git a/include/linux/cgroup_namespace.h b/include/linux/cgroup_namespace.h
>index 78a8418..f36ec1a 100644
>--- a/include/linux/cgroup_namespace.h
>+++ b/include/linux/cgroup_namespace.h
>@@ -9,6 +9,8 @@ struct cgroup_namespace {
> struct user_namespace *user_ns;
> struct ucounts *ucounts;
> struct css_set *root_cset;
>+ /* Preserve the root observed before cgroup_post_fork() updates it. */
>+ struct css_set *initial_root_cset;
> };
>
> extern struct cgroup_namespace init_cgroup_ns;
>diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
>index 227d097..c3cb258 100644
>--- a/kernel/cgroup/cgroup.c
>+++ b/kernel/cgroup/cgroup.c
>@@ -247,6 +247,7 @@ struct cgroup_namespace init_cgroup_ns = {
> .ns = NS_COMMON_INIT(init_cgroup_ns),
> .user_ns = &init_user_ns,
> .root_cset = &init_css_set,
>+ .initial_root_cset = &init_css_set,
> };
>
> static struct file_system_type cgroup2_fs_type;
>@@ -7131,9 +7132,11 @@ void cgroup_post_fork(struct task_struct *child,
> if (kargs->flags & CLONE_NEWCGROUP) {
> struct css_set *rcset = child->nsproxy->cgroup_ns->root_cset;
>
>- get_css_set(cset);
>- child->nsproxy->cgroup_ns->root_cset = cset;
>- put_css_set(rcset);
>+ /* Both possible roots must remain pinned for namespace readers. */
>+ if (rcset != cset) {
>+ get_css_set(cset);
>+ WRITE_ONCE(child->nsproxy->cgroup_ns->root_cset, cset);
>+ }
> }
>
> /* Cgroup has to be killed so take down child immediately. */
>diff --git a/kernel/cgroup/namespace.c b/kernel/cgroup/namespace.c
>index ea4ee13..1153ea4 100644
>--- a/kernel/cgroup/namespace.c
>+++ b/kernel/cgroup/namespace.c
>@@ -37,6 +37,8 @@ void free_cgroup_ns(struct cgroup_namespace *ns)
> {
> ns_tree_remove(ns);
> put_css_set(ns->root_cset);
>+ if (ns->initial_root_cset != ns->root_cset)
>+ put_css_set(ns->initial_root_cset);
> dec_cgroup_namespaces(ns->ucounts);
> put_user_ns(ns->user_ns);
> ns_common_free(ns);
>@@ -84,6 +86,7 @@ struct cgroup_namespace *copy_cgroup_ns(u64 flags,
> new_ns->user_ns = get_user_ns(user_ns);
> new_ns->ucounts = ucounts;
> new_ns->root_cset = cset;
>+ new_ns->initial_root_cset = cset;
>
> ns_tree_add(new_ns);
> return new_ns;
>
--- Thanks!
"I'm not a very positive person" - Linus torvalds