diff options
author | Peter Zijlstra <peterz@infradead.org> | 2013-05-28 04:55:48 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2013-05-28 05:05:08 -0400 |
commit | 26cb63ad11e04047a64309362674bcbbd6a6f246 (patch) | |
tree | 559c0a9778e7aa6639a24d9a7951879dc65d0551 | |
parent | 7b959fc582741227a1c4cba710d6aff8fb183128 (diff) |
perf: Fix perf mmap bugs
Vince reported a problem found by his perf specific trinity
fuzzer.
Al noticed 2 problems with perf's mmap():
- it has issues against fork() since we use vma->vm_mm for accounting.
- it has an rb refcount leak on double mmap().
We fix the issues against fork() by using VM_DONTCOPY; I don't
think there's code out there that uses this; we didn't hear
about weird accounting problems/crashes. If we do need this to
work, the previously proposed VM_PINNED could make this work.
Aside from the rb reference leak spotted by Al, Vince's example
prog was indeed doing a double mmap() through the use of
perf_event_set_output().
This exposes another problem, since we now have 2 events with
one buffer, the accounting gets screwy because we account per
event. Fix this by making the buffer responsible for its own
accounting.
Reported-by: Vince Weaver <vincent.weaver@maine.edu>
Signed-off-by: Peter Zijlstra <peterz@infradead.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Arnaldo Carvalho de Melo <acme@ghostprotocols.net>
Link: http://lkml.kernel.org/r/20130528085548.GA12193@twins.programming.kicks-ass.net
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r-- | include/linux/perf_event.h | 3 | ||||
-rw-r--r-- | kernel/events/core.c | 37 | ||||
-rw-r--r-- | kernel/events/internal.h | 3 |
3 files changed, 24 insertions, 19 deletions
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index f463a46424e2..c5b6dbf9c2fc 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h | |||
@@ -389,8 +389,7 @@ struct perf_event { | |||
389 | /* mmap bits */ | 389 | /* mmap bits */ |
390 | struct mutex mmap_mutex; | 390 | struct mutex mmap_mutex; |
391 | atomic_t mmap_count; | 391 | atomic_t mmap_count; |
392 | int mmap_locked; | 392 | |
393 | struct user_struct *mmap_user; | ||
394 | struct ring_buffer *rb; | 393 | struct ring_buffer *rb; |
395 | struct list_head rb_entry; | 394 | struct list_head rb_entry; |
396 | 395 | ||
diff --git a/kernel/events/core.c b/kernel/events/core.c index 9dc297faf7c0..ae752cd4a086 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c | |||
@@ -2917,7 +2917,7 @@ static void free_event_rcu(struct rcu_head *head) | |||
2917 | kfree(event); | 2917 | kfree(event); |
2918 | } | 2918 | } |
2919 | 2919 | ||
2920 | static void ring_buffer_put(struct ring_buffer *rb); | 2920 | static bool ring_buffer_put(struct ring_buffer *rb); |
2921 | 2921 | ||
2922 | static void free_event(struct perf_event *event) | 2922 | static void free_event(struct perf_event *event) |
2923 | { | 2923 | { |
@@ -3582,13 +3582,13 @@ static struct ring_buffer *ring_buffer_get(struct perf_event *event) | |||
3582 | return rb; | 3582 | return rb; |
3583 | } | 3583 | } |
3584 | 3584 | ||
3585 | static void ring_buffer_put(struct ring_buffer *rb) | 3585 | static bool ring_buffer_put(struct ring_buffer *rb) |
3586 | { | 3586 | { |
3587 | struct perf_event *event, *n; | 3587 | struct perf_event *event, *n; |
3588 | unsigned long flags; | 3588 | unsigned long flags; |
3589 | 3589 | ||
3590 | if (!atomic_dec_and_test(&rb->refcount)) | 3590 | if (!atomic_dec_and_test(&rb->refcount)) |
3591 | return; | 3591 | return false; |
3592 | 3592 | ||
3593 | spin_lock_irqsave(&rb->event_lock, flags); | 3593 | spin_lock_irqsave(&rb->event_lock, flags); |
3594 | list_for_each_entry_safe(event, n, &rb->event_list, rb_entry) { | 3594 | list_for_each_entry_safe(event, n, &rb->event_list, rb_entry) { |
@@ -3598,6 +3598,7 @@ static void ring_buffer_put(struct ring_buffer *rb) | |||
3598 | spin_unlock_irqrestore(&rb->event_lock, flags); | 3598 | spin_unlock_irqrestore(&rb->event_lock, flags); |
3599 | 3599 | ||
3600 | call_rcu(&rb->rcu_head, rb_free_rcu); | 3600 | call_rcu(&rb->rcu_head, rb_free_rcu); |
3601 | return true; | ||
3601 | } | 3602 | } |
3602 | 3603 | ||
3603 | static void perf_mmap_open(struct vm_area_struct *vma) | 3604 | static void perf_mmap_open(struct vm_area_struct *vma) |
@@ -3612,18 +3613,20 @@ static void perf_mmap_close(struct vm_area_struct *vma) | |||
3612 | struct perf_event *event = vma->vm_file->private_data; | 3613 | struct perf_event *event = vma->vm_file->private_data; |
3613 | 3614 | ||
3614 | if (atomic_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex)) { | 3615 | if (atomic_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex)) { |
3615 | unsigned long size = perf_data_size(event->rb); | ||
3616 | struct user_struct *user = event->mmap_user; | ||
3617 | struct ring_buffer *rb = event->rb; | 3616 | struct ring_buffer *rb = event->rb; |
3617 | struct user_struct *mmap_user = rb->mmap_user; | ||
3618 | int mmap_locked = rb->mmap_locked; | ||
3619 | unsigned long size = perf_data_size(rb); | ||
3618 | 3620 | ||
3619 | atomic_long_sub((size >> PAGE_SHIFT) + 1, &user->locked_vm); | ||
3620 | vma->vm_mm->pinned_vm -= event->mmap_locked; | ||
3621 | rcu_assign_pointer(event->rb, NULL); | 3621 | rcu_assign_pointer(event->rb, NULL); |
3622 | ring_buffer_detach(event, rb); | 3622 | ring_buffer_detach(event, rb); |
3623 | mutex_unlock(&event->mmap_mutex); | 3623 | mutex_unlock(&event->mmap_mutex); |
3624 | 3624 | ||
3625 | ring_buffer_put(rb); | 3625 | if (ring_buffer_put(rb)) { |
3626 | free_uid(user); | 3626 | atomic_long_sub((size >> PAGE_SHIFT) + 1, &mmap_user->locked_vm); |
3627 | vma->vm_mm->pinned_vm -= mmap_locked; | ||
3628 | free_uid(mmap_user); | ||
3629 | } | ||
3627 | } | 3630 | } |
3628 | } | 3631 | } |
3629 | 3632 | ||
@@ -3676,9 +3679,7 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) | |||
3676 | WARN_ON_ONCE(event->ctx->parent_ctx); | 3679 | WARN_ON_ONCE(event->ctx->parent_ctx); |
3677 | mutex_lock(&event->mmap_mutex); | 3680 | mutex_lock(&event->mmap_mutex); |
3678 | if (event->rb) { | 3681 | if (event->rb) { |
3679 | if (event->rb->nr_pages == nr_pages) | 3682 | if (event->rb->nr_pages != nr_pages) |
3680 | atomic_inc(&event->rb->refcount); | ||
3681 | else | ||
3682 | ret = -EINVAL; | 3683 | ret = -EINVAL; |
3683 | goto unlock; | 3684 | goto unlock; |
3684 | } | 3685 | } |
@@ -3720,12 +3721,14 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma) | |||
3720 | ret = -ENOMEM; | 3721 | ret = -ENOMEM; |
3721 | goto unlock; | 3722 | goto unlock; |
3722 | } | 3723 | } |
3723 | rcu_assign_pointer(event->rb, rb); | 3724 | |
3725 | rb->mmap_locked = extra; | ||
3726 | rb->mmap_user = get_current_user(); | ||
3724 | 3727 | ||
3725 | atomic_long_add(user_extra, &user->locked_vm); | 3728 | atomic_long_add(user_extra, &user->locked_vm); |
3726 | event->mmap_locked = extra; | 3729 | vma->vm_mm->pinned_vm += extra; |
3727 | event->mmap_user = get_current_user(); | 3730 | |
3728 | vma->vm_mm->pinned_vm += event->mmap_locked; | 3731 | rcu_assign_pointer(event->rb, rb); |
3729 | 3732 | ||
3730 | perf_event_update_userpage(event); | 3733 | perf_event_update_userpage(event); |
3731 | 3734 | ||
@@ -3734,7 +3737,7 @@ unlock: | |||
3734 | atomic_inc(&event->mmap_count); | 3737 | atomic_inc(&event->mmap_count); |
3735 | mutex_unlock(&event->mmap_mutex); | 3738 | mutex_unlock(&event->mmap_mutex); |
3736 | 3739 | ||
3737 | vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; | 3740 | vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP; |
3738 | vma->vm_ops = &perf_mmap_vmops; | 3741 | vma->vm_ops = &perf_mmap_vmops; |
3739 | 3742 | ||
3740 | return ret; | 3743 | return ret; |
diff --git a/kernel/events/internal.h b/kernel/events/internal.h index eb675c4d59df..5bc6c8e9b851 100644 --- a/kernel/events/internal.h +++ b/kernel/events/internal.h | |||
@@ -31,6 +31,9 @@ struct ring_buffer { | |||
31 | spinlock_t event_lock; | 31 | spinlock_t event_lock; |
32 | struct list_head event_list; | 32 | struct list_head event_list; |
33 | 33 | ||
34 | int mmap_locked; | ||
35 | struct user_struct *mmap_user; | ||
36 | |||
34 | struct perf_event_mmap_page *user_page; | 37 | struct perf_event_mmap_page *user_page; |
35 | void *data_pages[0]; | 38 | void *data_pages[0]; |
36 | }; | 39 | }; |