perf: Fix locking for children siblings group read
Jiri Olsa
reviewerPATCH 1/1 perf: Fix locking for children siblings group read
We're missing ctx lock when iterating children siblings within the perf_read path for group reading. Following race and crash can happen:
User space doing read syscall on event group leader:
T1:
perf_read
lock event->ctx->mutex
perf_read_group
lock leader->child_mutex
__perf_read_group_add(child)
list_for_each_entry(sub, &leader->sibling_list, group_entry)----> sub might be invalid at this point, because it could
get removed via perf_event_exit_task_context in T2
Child exiting and cleaning up its events:
T2:
perf_event_exit_task_context
lock ctx->mutex
list_for_each_entry_safe(child_event, next, &child_ctx->event_list,...
perf_event_exit_event(child)
lock ctx->lock
perf_group_detach(child)
unlock ctx->lock----> child is removed from sibling_list without any sync
with T1 path above
...
free_event(child)
Before the child is removed from the leader's child_list, (and thus is omitted from perf_read_group processing), we need to ensure that perf_read_group touches child's siblings under its ctx->lock.
--- kernel/events/core.c | 5 +++++ 1 file changed, 5 insertions(+)
kernel/events/core.c +5 -0
@@ -4365,7 +4365,9 @@ EXPORT_SYMBOL_GPL(perf_event_read_value);static int __perf_read_group_add (struct perf_event *leader,u64 read_format,u64 *values) { +struct perf_event_context *ctx = leader->ctx;struct perf_event *sub; +unsigned long flags;int n =1 ;/* skip @nr */ int ret;
@@ -4395,12 +4397,15 @@ static int __perf_read_group_add(struct perf_event *leader,if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (leader); +raw_spin_lock_irqsave (&ctx->lock, flags); +list_for_each_entry (sub, &leader->sibling_list, group_entry) { values[n++] +=perf_event_count (sub);if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (sub); } +raw_spin_unlock_irqrestore (&ctx->lock, flags);return 0 ; }
Signature · 1 line
2.9.4
Quoted 31 lines · Jiri Olsa
We're missing ctx lock when iterating children siblings within the
perf_readpath for group reading. Following race and crash can happen:User space doing read syscall on event group leader:
T1:
perf_readlock event->ctx->mutex perf_read_group lock leader->child_mutex __perf_read_group_add(child) list_for_each_entry(sub, &leader->sibling_list, group_entry)----> sub might be invalid at this point, because it could
get removed viaperf_event_exit_task_contextin T2Child exiting and cleaning up its events:
T2:
perf_event_exit_task_contextlock ctx->mutex list_for_each_entry_safe(child_event, next, &child_ctx->event_list,... perf_event_exit_event(child) lock ctx->lock perf_group_detach(child) unlock ctx->lock----> child is removed from
sibling_listwithout any sync
with T1 path above
Quoted 7 lines
...
free_event(child)Before the child is removed from the leader's
child_list, (and thus is omitted fromperf_read_groupprocessing), we need to ensure thatperf_read_grouptouches child's siblings under itsctx->lock.
One additional note; this bug got exposed by commit:
ba5213ae6b88 ("perf/core: Correct event creation with PERF_FORMAT_GROUP")
which made it possible to actually trigger this code-path.
So while it doesn't fix a bug in that commit per-se, we should maybe have a Fixes: tag with that commit in because that commit exposed it and this should be added to any kernel that commit goes into.
Quoted 2 lines
Tested-by Andi Kleen <[email protected]>Signed-off-by Jiri Olsa <[email protected]>
Thanks Jiri!
Quoted 37 lines
--- kernel/events/core.c | 5 +++++ 1 file changed, 5 insertions(+)kernel/events/core.c +5
-0@@ -4365,7 +4365,9 @@ EXPORT_SYMBOL_GPL(perf_event_read_value);static int __perf_read_group_add (struct perf_event *leader,u64 read_format,u64 *values) { +struct perf_event_context *ctx = leader->ctx;struct perf_event *sub; +unsigned long flags;int n =1 ;/* skip @nr */ int ret;@@ -4395,12 +4397,15 @@ static int __perf_read_group_add(struct perf_event *leader,if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (leader); +raw_spin_lock_irqsave (&ctx->lock, flags); +list_for_each_entry (sub, &leader->sibling_list, group_entry) { values[n++] +=perf_event_count (sub);if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (sub); } +raw_spin_unlock_irqrestore (&ctx->lock, flags);return 0 ; }Signature · 1 line
2.9.4
tip-bot for Jiri Olsa
merge bot[tip:perf/urgent] perf/core: Fix locking for children siblings group read
In reply to Jiri Olsaperf/core: Fix locking for children siblings group read
Commit-ID: 2aeb1883547626d82c597cce2c99f0b9c62e2425 Gitweb: http://git.kernel.org/tip/2aeb1883547626d82c597cce2c99f0b9c62e2425 Author: Jiri Olsa <[email protected]> AuthorDate: Thu, 20 Jul 2017 16:14:55 +0200 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 21 Jul 2017 09:54:23 +0200
perf/core: Fix locking for children siblings group read
We're missing ctx lock when iterating children siblings within the perf_read path for group reading. Following race and crash can happen:
User space doing read syscall on event group leader:
T1:
perf_read
lock event->ctx->mutex
perf_read_group
lock leader->child_mutex
__perf_read_group_add(child)
list_for_each_entry(sub, &leader->sibling_list, group_entry)----> sub might be invalid at this point, because it could
get removed via perf_event_exit_task_context in T2
Child exiting and cleaning up its events:
T2:
perf_event_exit_task_context
lock ctx->mutex
list_for_each_entry_safe(child_event, next, &child_ctx->event_list,...
perf_event_exit_event(child)
lock ctx->lock
perf_group_detach(child)
unlock ctx->lock----> child is removed from sibling_list without any sync
with T1 path above
...
free_event(child)
Before the child is removed from the leader's child_list, (and thus is omitted from perf_read_group processing), we need to ensure that perf_read_group touches child's siblings under its ctx->lock.
Peter further notes:
| One additional note; this bug got exposed by commit:
|
| ba5213ae6b88 ("perf/core: Correct event creation with PERF_FORMAT_GROUP")
|
| which made it possible to actually trigger this code-path.--- kernel/events/core.c | 5 +++++ 1 file changed, 5 insertions(+)
kernel/events/core.c +5 -0
@@ -4372,7 +4372,9 @@ EXPORT_SYMBOL_GPL(perf_event_read_value);static int __perf_read_group_add (struct perf_event *leader,u64 read_format,u64 *values) { +struct perf_event_context *ctx = leader->ctx;struct perf_event *sub; +unsigned long flags;int n =1 ;/* skip @nr */ int ret;
@@ -4402,12 +4404,15 @@ static int __perf_read_group_add(struct perf_event *leader,if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (leader); +raw_spin_lock_irqsave (&ctx->lock, flags); +list_for_each_entry (sub, &leader->sibling_list, group_entry) { values[n++] +=perf_event_count (sub);if (read_format &PERF_FORMAT_ID ) values[n++] =primary_event_id (sub); } +raw_spin_unlock_irqrestore (&ctx->lock, flags);return 0 ; }