Skip to content

Commit 767ae08

Browse files
virtuosoIngo Molnar
authored andcommitted
perf/core: Fix a race between mmap_close() and set_output() of AUX events
In the mmap_close() path we need to stop all the AUX events that are writing data to the AUX area that we are unmapping, before we can safely free the pages. To determine if an event needs to be stopped, we're comparing its ->rb against the one that's getting unmapped. However, a SET_OUTPUT ioctl may turn up inside an AUX transaction and swizzle event::rb to some other ring buffer, but the transaction will keep writing data to the old ring buffer until the event gets scheduled out. At this point, mmap_close() will skip over such an event and will proceed to free the AUX area, while it's still being used by this event, which will set off a warning in the mmap_close() path and cause a memory corruption. To avoid this, always stop an AUX event before its ->rb is updated; this will release the (potentially) last reference on the AUX area of the buffer. If the event gets restarted, its new ring buffer will be used. If another SET_OUTPUT comes and switches it back to the old ring buffer that's getting unmapped, it's also fine: this ring buffer's aux_mmap_count will be zero and AUX transactions won't start any more. Reported-by: Vince Weaver <[email protected]> Signed-off-by: Alexander Shishkin <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Stephane Eranian <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: [email protected] Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent 7d762e4 commit 767ae08

File tree

1 file changed

+25
-6
lines changed

1 file changed

+25
-6
lines changed

kernel/events/core.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,11 +2496,11 @@ static int __perf_event_stop(void *info)
24962496
return 0;
24972497
}
24982498

2499-
static int perf_event_restart(struct perf_event *event)
2499+
static int perf_event_stop(struct perf_event *event, int restart)
25002500
{
25012501
struct stop_event_data sd = {
25022502
.event = event,
2503-
.restart = 1,
2503+
.restart = restart,
25042504
};
25052505
int ret = 0;
25062506

@@ -4845,6 +4845,19 @@ static void ring_buffer_attach(struct perf_event *event,
48454845
spin_unlock_irqrestore(&rb->event_lock, flags);
48464846
}
48474847

4848+
/*
4849+
* Avoid racing with perf_mmap_close(AUX): stop the event
4850+
* before swizzling the event::rb pointer; if it's getting
4851+
* unmapped, its aux_mmap_count will be 0 and it won't
4852+
* restart. See the comment in __perf_pmu_output_stop().
4853+
*
4854+
* Data will inevitably be lost when set_output is done in
4855+
* mid-air, but then again, whoever does it like this is
4856+
* not in for the data anyway.
4857+
*/
4858+
if (has_aux(event))
4859+
perf_event_stop(event, 0);
4860+
48484861
rcu_assign_pointer(event->rb, rb);
48494862

48504863
if (old_rb) {
@@ -6120,7 +6133,7 @@ static void perf_event_addr_filters_exec(struct perf_event *event, void *data)
61206133
raw_spin_unlock_irqrestore(&ifh->lock, flags);
61216134

61226135
if (restart)
6123-
perf_event_restart(event);
6136+
perf_event_stop(event, 1);
61246137
}
61256138

61266139
void perf_event_exec(void)
@@ -6164,7 +6177,13 @@ static void __perf_event_output_stop(struct perf_event *event, void *data)
61646177

61656178
/*
61666179
* In case of inheritance, it will be the parent that links to the
6167-
* ring-buffer, but it will be the child that's actually using it:
6180+
* ring-buffer, but it will be the child that's actually using it.
6181+
*
6182+
* We are using event::rb to determine if the event should be stopped,
6183+
* however this may race with ring_buffer_attach() (through set_output),
6184+
* which will make us skip the event that actually needs to be stopped.
6185+
* So ring_buffer_attach() has to stop an aux event before re-assigning
6186+
* its rb pointer.
61686187
*/
61696188
if (rcu_dereference(parent->rb) == rb)
61706189
ro->err = __perf_event_stop(&sd);
@@ -6678,7 +6697,7 @@ static void __perf_addr_filters_adjust(struct perf_event *event, void *data)
66786697
raw_spin_unlock_irqrestore(&ifh->lock, flags);
66796698

66806699
if (restart)
6681-
perf_event_restart(event);
6700+
perf_event_stop(event, 1);
66826701
}
66836702

66846703
/*
@@ -7867,7 +7886,7 @@ static void perf_event_addr_filters_apply(struct perf_event *event)
78677886
mmput(mm);
78687887

78697888
restart:
7870-
perf_event_restart(event);
7889+
perf_event_stop(event, 1);
78717890
}
78727891

78737892
/*

0 commit comments

Comments
 (0)