cgroup: fix cgroup_path() vs rename() race
Subsystems (2)
PATCH v5 1/2 cgroup: fix cgroup_path() vs rename() race
rename() will change dentry->d_name. The result of this race can be worse than seeing partially rewritten name, but we might access a stale pointer because rename() will re-allocate memory to hold a longer name.
As accessing dentry->name must be protected by dentry->d_lock or parent inode's i_mutex, while on the other hand cgroup-path() can be called with some irq-safe spinlocks held, we can't generate cgroup path using dentry->d_name.
Alternatively we make a copy of dentry->d_name and save it in cgrp->name when a cgroup is created, and update cgrp->name at rename().
v5: use flexible array instead of zero-size array.
v4: - allocate root_cgroup_name and all root_cgroup->name points to it.
- add cgroup_name() wrapper.
v3: use kfree_rcu() instead of synchronize_rcu() in user-visible path.
v2: make cgrp->name RCU safe.
--- block/blk-cgroup.h | 2 - include/linux/cgroup.h | 24 +++++++++++ kernel/cgroup.c | 106 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 32 deletions(-)
block/blk-cgroup.h +0 -2
@@ -216,9 +216,7 @@ static inline int blkg_path(struct blkcg_gq *blkg, char *buf, int buflen) {int ret; -rcu_read_lock (); ret =cgroup_path (blkg->blkcg->css.cgroup, buf, buflen); -rcu_read_unlock ();if (ret)strncpy (buf,"<unavailable>" , buflen);return ret;
include/linux/cgroup.h +24 -0
@@ -150,6 +150,11 @@ enum { CGRP_CPUSET_CLONE_CHILDREN, }; +struct cgroup_name { +struct rcu_head rcu_head; +char name[]; +}; +struct cgroup {unsigned long flags;/* "unsigned long" so bitops work */
@@ -172,6 +177,19 @@ struct cgroup {struct cgroup *parent;/* my parent */ struct dentry *dentry;/* cgroup fs entry, RCU protected */ +/* +* This is a copy of dentry->d_name, and it's needed because +* we can't use dentry->d_name in cgroup_path(). +* +* You must acquire rcu_read_lock() to access cgrp->name, and +* the only place that can change it is rename(), which is +* protected by parent dir's i_mutex. +* +* Normally you should use cgroup_name() wrapper rather than +* access it directly. +*/ +struct cgroup_name __rcu *name; +/* Private pointers for each registered subsystem */ struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT ];
@@ -404,6 +422,12 @@ struct cgroup_scanner {void *data; }; +/* Caller should hold rcu_read_lock() */ +static inline const char *cgroup_name (const struct cgroup *cgrp) +{ +return rcu_dereference (cgrp->name)->name; +} +int cgroup_add_cftypes (struct cgroup_subsys *ss,struct cftype *cfts);int cgroup_rm_cftypes (struct cgroup_subsys *ss,struct cftype *cfts);
kernel/cgroup.c +76 -30
183 lines not on this page · raw message
Signature · 1 line
1.8.0.2
PATCH 2/2 cpuset: use cgroup_name() in cpuset_print_task_mems_allowed()
Use cgroup_name() instead of cgrp->dentry->name. This makes the code a bit simpler.
While at it, remove cpuset_name and make cpuset_nodelist a local variable to cpuset_print_task_mems_allowed().
--- kernel/cpuset.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-)
kernel/cpuset.c +9 -23
@@ -265,17 +265,6 @@ static DEFINE_MUTEX(cpuset_mutex);static DEFINE_MUTEX (callback_mutex);/* -* cpuset_buffer_lock protects both the cpuset_name and cpuset_nodelist -* buffers. They are statically allocated to prevent using excess stack -* when calling cpuset_print_task_mems_allowed(). -*/ -#define CPUSET_NAME_LEN (128) -#define CPUSET_NODELIST_LEN (256) -static char cpuset_name[CPUSET_NAME_LEN ]; -static char cpuset_nodelist[CPUSET_NODELIST_LEN ]; -static DEFINE_SPINLOCK (cpuset_buffer_lock); - -/* * CPU / memory hotplug is handled asynchronously. */ static struct workqueue_struct *cpuset_propagate_hotplug_wq;
@@ -2592,6 +2581,8 @@ int cpuset_mems_allowed_intersects(const struct task_struct *tsk1,return nodes_intersects (tsk1->mems_allowed, tsk2->mems_allowed); } +#define CPUSET_NODELIST_LEN (256) +/** * cpuset_print_task_mems_allowed - prints task's cpuset and mems_allowed * @task: pointer to task_struct of some task.
@@ -2602,24 +2593,19 @@ int cpuset_mems_allowed_intersects(const struct task_struct *tsk1,*/ void cpuset_print_task_mems_allowed (struct task_struct *tsk) { -struct dentry *dentry; +/* Statically allocated to prevent using excess stack. */ +static char cpuset_nodelist[CPUSET_NODELIST_LEN ]; +static DEFINE_SPINLOCK (cpuset_buffer_lock); - dentry =task_cs (tsk)->css.cgroup->dentry; -spin_lock (&cpuset_buffer_lock); +struct cgroup *cgrp =task_cs (tsk)->css.cgroup; -if (!dentry) { -strcpy (cpuset_name,"/" ); - }else { -spin_lock (&dentry->d_lock); -strlcpy (cpuset_name, (const char *)dentry->d_name.name, -CPUSET_NAME_LEN ); -spin_unlock (&dentry->d_lock); - } +spin_lock (&cpuset_buffer_lock);nodelist_scnprintf (cpuset_nodelist,CPUSET_NODELIST_LEN , tsk->mems_allowed);printk (KERN_INFO "%s cpuset=%s mems_allowed=%s\n" , - tsk->comm, cpuset_name, cpuset_nodelist); + tsk->comm,cgroup_name (cgrp), cpuset_nodelist); +spin_unlock (&cpuset_buffer_lock); }
Signature · 1 line
1.8.0.2
Tejun Heo
maintainerQuoted 14 lines · Li Zefan
rename()will changedentry->d_name. The result of this race can be worse than seeing partially rewritten name, but we might access a stale pointer becauserename()will re-allocate memory to hold a longer name.As accessing
dentry->namemust be protected bydentry->d_lockor parent inode'si_mutex, while on the other hand cgroup-path() can be called with some irq-safe spinlocks held, we can't generate cgroup path usingdentry->d_name.Alternatively we make a copy of
dentry->d_nameand save it incgrp->namewhen a cgroup is created, and updatecgrp->nameatrename().
Quoted 7 lines
v5: use flexible array instead of zero-size array.
v4: - allocateroot_cgroup_nameand allroot_cgroup->namepoints to it.
- addcgroup_name()wrapper.
v3: usekfree_rcu()instead ofsynchronize_rcu()in user-visible path.
v2: makecgrp->nameRCU safe.Signed-off-by Li Zefan <[email protected]>
Applied 1-2 to cgroup/for-3.10.
Thanks.
Signature · 1 line
tejun