diff options
author | Li Zefan <lizefan@huawei.com> | 2014-02-11 03:05:46 -0500 |
---|---|---|
committer | Tejun Heo <tj@kernel.org> | 2014-02-11 10:38:30 -0500 |
commit | 0ab02ca8f887908152d1a96db5130fc661d36a1e (patch) | |
tree | 81f7be1f4da646a88ab09bd7c01927a4f2a180bf | |
parent | 48573a893303986e3b0b2974d6fb11f3d1bb7064 (diff) |
cgroup: protect modifications to cgroup_idr with cgroup_mutex
Setup cgroupfs like this:
# mount -t cgroup -o cpuacct xxx /cgroup
# mkdir /cgroup/sub1
# mkdir /cgroup/sub2
Then run these two commands:
# for ((; ;)) { mkdir /cgroup/sub1/tmp && rmdir /mnt/sub1/tmp; } &
# for ((; ;)) { mkdir /cgroup/sub2/tmp && rmdir /mnt/sub2/tmp; } &
After seconds you may see this warning:
------------[ cut here ]------------
WARNING: CPU: 1 PID: 25243 at lib/idr.c:527 sub_remove+0x87/0x1b0()
idr_remove called for id=6 which is not allocated.
...
Call Trace:
[<ffffffff8156063c>] dump_stack+0x7a/0x96
[<ffffffff810591ac>] warn_slowpath_common+0x8c/0xc0
[<ffffffff81059296>] warn_slowpath_fmt+0x46/0x50
[<ffffffff81300aa7>] sub_remove+0x87/0x1b0
[<ffffffff810f3f02>] ? css_killed_work_fn+0x32/0x1b0
[<ffffffff81300bf5>] idr_remove+0x25/0xd0
[<ffffffff810f2bab>] cgroup_destroy_css_killed+0x5b/0xc0
[<ffffffff810f4000>] css_killed_work_fn+0x130/0x1b0
[<ffffffff8107cdbc>] process_one_work+0x26c/0x550
[<ffffffff8107eefe>] worker_thread+0x12e/0x3b0
[<ffffffff81085f96>] kthread+0xe6/0xf0
[<ffffffff81570bac>] ret_from_fork+0x7c/0xb0
---[ end trace 2d1577ec10cf80d0 ]---
It's because allocating/removing cgroup ID is not properly synchronized.
The bug was introduced when we converted cgroup_ida to cgroup_idr.
While synchronization is already done inside ida_simple_{get,remove}(),
users are responsible for concurrent calls to idr_{alloc,remove}().
tj: Refreshed on top of b58c89986a77 ("cgroup: fix error return from
cgroup_create()").
Fixes: 4e96ee8e981b ("cgroup: convert cgroup_ida to cgroup_idr")
Cc: <stable@vger.kernel.org> #3.12+
Reported-by: Michal Hocko <mhocko@suse.cz>
Signed-off-by: Li Zefan <lizefan@huawei.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
-rw-r--r-- | include/linux/cgroup.h | 2 | ||||
-rw-r--r-- | kernel/cgroup.c | 34 |
2 files changed, 20 insertions, 16 deletions
diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index 5c097596104b..9450f025fe0c 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h | |||
@@ -166,6 +166,8 @@ struct cgroup { | |||
166 | * | 166 | * |
167 | * The ID of the root cgroup is always 0, and a new cgroup | 167 | * The ID of the root cgroup is always 0, and a new cgroup |
168 | * will be assigned with a smallest available ID. | 168 | * will be assigned with a smallest available ID. |
169 | * | ||
170 | * Allocating/Removing ID must be protected by cgroup_mutex. | ||
169 | */ | 171 | */ |
170 | int id; | 172 | int id; |
171 | 173 | ||
diff --git a/kernel/cgroup.c b/kernel/cgroup.c index 3edf7163b84f..52719ce55dd3 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c | |||
@@ -886,7 +886,9 @@ static void cgroup_diput(struct dentry *dentry, struct inode *inode) | |||
886 | * per-subsystem and moved to css->id so that lookups are | 886 | * per-subsystem and moved to css->id so that lookups are |
887 | * successful until the target css is released. | 887 | * successful until the target css is released. |
888 | */ | 888 | */ |
889 | mutex_lock(&cgroup_mutex); | ||
889 | idr_remove(&cgrp->root->cgroup_idr, cgrp->id); | 890 | idr_remove(&cgrp->root->cgroup_idr, cgrp->id); |
891 | mutex_unlock(&cgroup_mutex); | ||
890 | cgrp->id = -1; | 892 | cgrp->id = -1; |
891 | 893 | ||
892 | call_rcu(&cgrp->rcu_head, cgroup_free_rcu); | 894 | call_rcu(&cgrp->rcu_head, cgroup_free_rcu); |
@@ -4168,16 +4170,6 @@ static long cgroup_create(struct cgroup *parent, struct dentry *dentry, | |||
4168 | rcu_assign_pointer(cgrp->name, name); | 4170 | rcu_assign_pointer(cgrp->name, name); |
4169 | 4171 | ||
4170 | /* | 4172 | /* |
4171 | * Temporarily set the pointer to NULL, so idr_find() won't return | ||
4172 | * a half-baked cgroup. | ||
4173 | */ | ||
4174 | cgrp->id = idr_alloc(&root->cgroup_idr, NULL, 1, 0, GFP_KERNEL); | ||
4175 | if (cgrp->id < 0) { | ||
4176 | err = -ENOMEM; | ||
4177 | goto err_free_name; | ||
4178 | } | ||
4179 | |||
4180 | /* | ||
4181 | * Only live parents can have children. Note that the liveliness | 4173 | * Only live parents can have children. Note that the liveliness |
4182 | * check isn't strictly necessary because cgroup_mkdir() and | 4174 | * check isn't strictly necessary because cgroup_mkdir() and |
4183 | * cgroup_rmdir() are fully synchronized by i_mutex; however, do it | 4175 | * cgroup_rmdir() are fully synchronized by i_mutex; however, do it |
@@ -4186,7 +4178,17 @@ static long cgroup_create(struct cgroup *parent, struct dentry *dentry, | |||
4186 | */ | 4178 | */ |
4187 | if (!cgroup_lock_live_group(parent)) { | 4179 | if (!cgroup_lock_live_group(parent)) { |
4188 | err = -ENODEV; | 4180 | err = -ENODEV; |
4189 | goto err_free_id; | 4181 | goto err_free_name; |
4182 | } | ||
4183 | |||
4184 | /* | ||
4185 | * Temporarily set the pointer to NULL, so idr_find() won't return | ||
4186 | * a half-baked cgroup. | ||
4187 | */ | ||
4188 | cgrp->id = idr_alloc(&root->cgroup_idr, NULL, 1, 0, GFP_KERNEL); | ||
4189 | if (cgrp->id < 0) { | ||
4190 | err = -ENOMEM; | ||
4191 | goto err_unlock; | ||
4190 | } | 4192 | } |
4191 | 4193 | ||
4192 | /* Grab a reference on the superblock so the hierarchy doesn't | 4194 | /* Grab a reference on the superblock so the hierarchy doesn't |
@@ -4218,7 +4220,7 @@ static long cgroup_create(struct cgroup *parent, struct dentry *dentry, | |||
4218 | */ | 4220 | */ |
4219 | err = cgroup_create_file(dentry, S_IFDIR | mode, sb); | 4221 | err = cgroup_create_file(dentry, S_IFDIR | mode, sb); |
4220 | if (err < 0) | 4222 | if (err < 0) |
4221 | goto err_unlock; | 4223 | goto err_free_id; |
4222 | lockdep_assert_held(&dentry->d_inode->i_mutex); | 4224 | lockdep_assert_held(&dentry->d_inode->i_mutex); |
4223 | 4225 | ||
4224 | cgrp->serial_nr = cgroup_serial_nr_next++; | 4226 | cgrp->serial_nr = cgroup_serial_nr_next++; |
@@ -4254,12 +4256,12 @@ static long cgroup_create(struct cgroup *parent, struct dentry *dentry, | |||
4254 | 4256 | ||
4255 | return 0; | 4257 | return 0; |
4256 | 4258 | ||
4257 | err_unlock: | ||
4258 | mutex_unlock(&cgroup_mutex); | ||
4259 | /* Release the reference count that we took on the superblock */ | ||
4260 | deactivate_super(sb); | ||
4261 | err_free_id: | 4259 | err_free_id: |
4262 | idr_remove(&root->cgroup_idr, cgrp->id); | 4260 | idr_remove(&root->cgroup_idr, cgrp->id); |
4261 | /* Release the reference count that we took on the superblock */ | ||
4262 | deactivate_super(sb); | ||
4263 | err_unlock: | ||
4264 | mutex_unlock(&cgroup_mutex); | ||
4263 | err_free_name: | 4265 | err_free_name: |
4264 | kfree(rcu_dereference_raw(cgrp->name)); | 4266 | kfree(rcu_dereference_raw(cgrp->name)); |
4265 | err_free_cgrp: | 4267 | err_free_cgrp: |