diff options
author | Tejun Heo <tj@kernel.org> | 2013-05-23 21:50:24 -0400 |
---|---|---|
committer | Tejun Heo <tj@kernel.org> | 2013-05-23 21:50:24 -0400 |
commit | 7805d000db30a3787a4c969bab6ae4d8a5fd8ce6 (patch) | |
tree | 5178e1adaa4f398d2c1367fb38633191e81764e5 /kernel/cgroup.c | |
parent | d6cbf35dac8a3dadb9103379820c96d7c85df3d9 (diff) |
cgroup: fix a subtle bug in descendant pre-order walk
When cgroup_next_descendant_pre() initiates a walk, it checks whether
the subtree root doesn't have any children and if not returns NULL.
Later code assumes that the subtree isn't empty. This is broken
because the subtree may become empty inbetween, which can lead to the
traversal escaping the subtree by walking to the sibling of the
subtree root.
There's no reason to have the early exit path. Remove it along with
the later assumption that the subtree isn't empty. This simplifies
the code a bit and fixes the subtle bug.
While at it, fix the comment of cgroup_for_each_descendant_pre() which
was incorrectly referring to ->css_offline() instead of
->css_online().
Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Michal Hocko <mhocko@suse.cz>
Cc: stable@vger.kernel.org
Diffstat (limited to 'kernel/cgroup.c')
-rw-r--r-- | kernel/cgroup.c | 9 |
1 files changed, 3 insertions, 6 deletions
diff --git a/kernel/cgroup.c b/kernel/cgroup.c index 38b136553044..31e9ef319070 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c | |||
@@ -2954,11 +2954,8 @@ struct cgroup *cgroup_next_descendant_pre(struct cgroup *pos, | |||
2954 | WARN_ON_ONCE(!rcu_read_lock_held()); | 2954 | WARN_ON_ONCE(!rcu_read_lock_held()); |
2955 | 2955 | ||
2956 | /* if first iteration, pretend we just visited @cgroup */ | 2956 | /* if first iteration, pretend we just visited @cgroup */ |
2957 | if (!pos) { | 2957 | if (!pos) |
2958 | if (list_empty(&cgroup->children)) | ||
2959 | return NULL; | ||
2960 | pos = cgroup; | 2958 | pos = cgroup; |
2961 | } | ||
2962 | 2959 | ||
2963 | /* visit the first child if exists */ | 2960 | /* visit the first child if exists */ |
2964 | next = list_first_or_null_rcu(&pos->children, struct cgroup, sibling); | 2961 | next = list_first_or_null_rcu(&pos->children, struct cgroup, sibling); |
@@ -2966,14 +2963,14 @@ struct cgroup *cgroup_next_descendant_pre(struct cgroup *pos, | |||
2966 | return next; | 2963 | return next; |
2967 | 2964 | ||
2968 | /* no child, visit my or the closest ancestor's next sibling */ | 2965 | /* no child, visit my or the closest ancestor's next sibling */ |
2969 | do { | 2966 | while (pos != cgroup) { |
2970 | next = list_entry_rcu(pos->sibling.next, struct cgroup, | 2967 | next = list_entry_rcu(pos->sibling.next, struct cgroup, |
2971 | sibling); | 2968 | sibling); |
2972 | if (&next->sibling != &pos->parent->children) | 2969 | if (&next->sibling != &pos->parent->children) |
2973 | return next; | 2970 | return next; |
2974 | 2971 | ||
2975 | pos = pos->parent; | 2972 | pos = pos->parent; |
2976 | } while (pos != cgroup); | 2973 | } |
2977 | 2974 | ||
2978 | return NULL; | 2975 | return NULL; |
2979 | } | 2976 | } |