aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Zijlstra <peterz@infradead.org>2013-05-28 04:55:48 -0400
committerIngo Molnar <mingo@kernel.org>2013-05-28 05:05:08 -0400
commit26cb63ad11e04047a64309362674bcbbd6a6f246 (patch)
tree559c0a9778e7aa6639a24d9a7951879dc65d0551
parent7b959fc582741227a1c4cba710d6aff8fb183128 (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.h3
-rw-r--r--kernel/events/core.c37
-rw-r--r--kernel/events/internal.h3
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
2920static void ring_buffer_put(struct ring_buffer *rb); 2920static bool ring_buffer_put(struct ring_buffer *rb);
2921 2921
2922static void free_event(struct perf_event *event) 2922static 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
3585static void ring_buffer_put(struct ring_buffer *rb) 3585static 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
3603static void perf_mmap_open(struct vm_area_struct *vma) 3604static 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};