[PATCH] cgroup: prevent use-after-free during namespace root replacement
From: Jérémy Jean
Date: Wed Sep 23 2026 - 16:57:29 EST
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.
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;
--
2.47.3