Skip to contentSkip to conversation

perf: use-after-free in perf_event_for_each

DISCUSSION Dmitry Vyukov 4 participants 9 messages Jan 23, 2017 – Jan 30, 2017 perf events

lore
Share & tools
lore
#0 lore raw

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

BUG: KASAN: use-after-free in perf_event_for_each_child+0x15f/0x180
kernel/events/core.c:4495 at addr ffff8800680ec248
Read of size 8 by task a.out/19370
CPU: 3 PID: 19370 Comm: a.out Not tainted 4.10.0-rc5+ #186
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
Call Trace:
 __asan_report_load8_noabort+0x3e/0x40 mm/kasan/report.c:328
 perf_event_for_each_child+0x15f/0x180 kernel/events/core.c:4495
 perf_event_for_each kernel/events/core.c:4514 [inline]
 _perf_ioctl kernel/events/core.c:4671 [inline]
 perf_ioctl+0x9b5/0x1480 kernel/events/core.c:4685
 vfs_ioctl fs/ioctl.c:43 [inline]
 do_vfs_ioctl+0x1bf/0x1790 fs/ioctl.c:683
 SYSC_ioctl fs/ioctl.c:698 [inline]
 SyS_ioctl+0x8f/0xc0 fs/ioctl.c:689
 entry_SYSCALL_64_fastpath+0x1f/0xc2
RIP: 0033:0x44d289
RSP: 002b:00007fb128517cd8 EFLAGS: 00000217 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 000000000044d289
RDX: 0000000000010001 RSI: 0000000000002400 RDI: 0000000000000004
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000217 R12: 0000000000000000
R13: 0000000000000000 R14: 00007fb1285189c0 R15: 00007fb128518700
Object at ffff8800680ec040, in cache kmalloc-2048 size: 2048
Allocated:
PID = 19367
[<ffffffff81a0942b>] kmem_cache_alloc_trace+0x10b/0x670 mm/slab.c:3629
[<ffffffff818328e5>] kzalloc include/linux/slab.h:490 [inline]
[<ffffffff818328e5>] perf_event_alloc+0x1c5/0x1ef0 kernel/events/core.c:9134
[<ffffffff8184432d>] SYSC_perf_event_open+0xb8d/0x31b0 kernel/events/core.c:9692
[<ffffffff81850419>] SyS_perf_event_open+0x39/0x50 kernel/events/core.c:9586
[<ffffffff841c8c81>] entry_SYSCALL_64_fastpath+0x1f/0xc2
Freed:
PID = 19372
[<ffffffff81a0b103>] __cache_free mm/slab.c:3505 [inline]
[<ffffffff81a0b103>] kfree+0xd3/0x250 mm/slab.c:3822
[<ffffffff81819b6c>] free_event_rcu+0x5c/0x70 kernel/events/core.c:3828
[<ffffffff81608600>] __rcu_reclaim kernel/rcu/rcu.h:118 [inline]
[<ffffffff81608600>] rcu_do_batch.isra.70+0x9e0/0xdf0 kernel/rcu/tree.c:2780
[<ffffffff81608e82>] invoke_rcu_callbacks kernel/rcu/tree.c:3043 [inline]
[<ffffffff81608e82>] __rcu_process_callbacks kernel/rcu/tree.c:3010 [inline]
[<ffffffff81608e82>] rcu_process_callbacks+0x472/0xc70 kernel/rcu/tree.c:3027
[<ffffffff841cbfbf>] __do_softirq+0x31f/0xbe7 kernel/softirq.c:284

On commit 095cbe66973771fecd8e8b1e8763181363ef703e (Jan 22).

maintainer
#1 lore raw
Quoted 5 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

Quoted 8 lines
BUG: KASAN: use-after-free in perf_event_for_each_child+0x15f/0x180
kernel/events/core.c:4495 at addr ffff8800680ec248
Read of size 8 by task a.out/19370
CPU: 3 PID: 19370 Comm: a.out Not tainted 4.10.0-rc5+ #186
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
Call Trace:
 __asan_report_load8_noabort+0x3e/0x40 mm/kasan/report.c:328
 perf_event_for_each_child+0x15f/0x180 kernel/events/core.c:4495

That's the WARN_ON_ONCE() in there, and since the object that's being UAF'ed is an event, this would be the first dereference in there, this means that this is the event passed in from the next function.

Quoted 1 line

perf_event_for_each kernel/events/core.c:4514 [inline]

And this is the first caller (ie, !sibling), suggesting that the line above:

  event = event->group_leader;

is the curlprit, because the earlier event->ctx, dereference is fine (and we're holding ctx->mutex).

Quoted 3 lines
 _perf_ioctl kernel/events/core.c:4671 [inline]
 perf_ioctl+0x9b5/0x1480 kernel/events/core.c:4685
 vfs_ioctl fs/ioctl.c:43 [inline]

This also matches with that the program does; which is something along the lines of:

  event = sys_perf_event_open();
  sibling = sys_perf_event_open(.group_fd = event);
  dup3(sibling, event, ..);
  ioctl(sibling);

Where the dup3() will close event (which should then promote the sibling to its own full event) and ioctl() on the sibling, which them comes apart.

So it looks like something fishy on the close the leader and promote the sibling code, but I cannot put my finger on it just yet. I've ran out of time for today, but will continue staring at this tomorrow -- of course hoping that this email will give someone else ideas while I'm off :-)

maintainer
#2 lore raw
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

I've been running 60 concurrent instances of that thing for hours now, and have not been able to reproduce :-/

I did enable CONFIG_KASAN but otherwise booted as normal, and the thing says:

[    0.000000] kasan: KernelAddressSanitizer initialized

Is there anything else I should do?

I've ran out of ideas and it would be very helpful if I could prod at something that fails...

#3 lore raw
Quoted 10 lines · Peter Zijlstra
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

I've been running 60 concurrent instances of that thing for hours now, and have not been able to reproduce :-/

Quoted 6 lines

I did enable CONFIG_KASAN but otherwise booted as normal, and the thing says:

[    0.000000] kasan: KernelAddressSanitizer initialized

Is there anything else I should do?

Should be enough.

Quoted 2 lines

I've ran out of ideas and it would be very helpful if I could prod at something that fails...

Try to run more parallel processes at the same time.
This program will run 32 processes in a tight loop:
https://gist.githubusercontent.com/dvyukov/b36aa4398bb016923278fccdd1cc5b45/raw/9607078fd5ac2daf769c13d82561a83e17b06032/gistfile1.txt
It triggered the UAF in several minutes for me. I have 4 CPUs in the VM. If you have more, set number of processes to 8*CPU.

Just in case this is my config:

$ grep "PERF" .config
# CONFIG_CGROUP_PERF is not set
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
CONFIG_HAVE_PERF_EVENTS=y
CONFIG_PERF_EVENTS=y
# CONFIG_DEBUG_PERF_USE_VMALLOC is not set
CONFIG_HAVE_PERF_EVENTS_NMI=y
CONFIG_HAVE_PERF_REGS=y
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
CONFIG_PERF_EVENTS_INTEL_UNCORE=y
CONFIG_PERF_EVENTS_INTEL_RAPL=y
CONFIG_PERF_EVENTS_INTEL_CSTATE=y
# CONFIG_PERF_EVENTS_AMD_POWER is not set
# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
# CONFIG_PCIEASPM_PERFORMANCE is not set
# CONFIG_RCU_PERF_TEST is not set

And here is how I start qemu:

qemu-system-x86_64 -hda wheezy.img -net user,host=10.0.2.10,hostfwd=tcp::10022-:22 -net nic -nographic -kernel arch/x86/boot/bzImage -append "kvm-intel.nested=1 kvm-intel.enable_unrestricted_guest=1 kvm-intel.ept=1 kvm-intel.flexpriority=1 kvm-intel.vpid=1 kvm-intel.emulate_invalid_guest_state=1 kvm-intel.eptad=1 kvm-intel.enable_shadow_vmcs=1 kvm-intel.pml=1 kvm-intel.enable_apicv=1 console=ttyS0 root=/dev/sda earlyprintk=serial slub_debug=UZ vsyscall=native rodata=n oops=panic panic_on_warn=1 panic=86400" -enable-kvm -pidfile vm_pid -m 2G -smp 4 -cpu host -usb -usbdevice mouse -usbdevice tablet -soundhw all

maintainer
#5 lore raw
In reply to Dmitry Vyukov
Quoted 7 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

BUG: KASAN: use-after-free in perf_event_for_each_child+0x15f/0x180

kernel/events/core.c:4495 at addr ffff8800680ec248

The below seems to fix things for me.

---
Subject: perf: Fix use-after-free

Dmitry reported a KASAN use-after-free.

XXX: do a changelog

---
 kernel/events/core.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)
kernel/events/core.c +25 -2
index 896aa53..8c1ad98 100644
@@ -1474,7 +1474,6 @@ ctx_group_list(struct perf_event *event, struct perf_event_context *ctx)
 static void
 list_add_event(struct perf_event *event, struct perf_event_context *ctx)
 {
-
 	lockdep_assert_held(&ctx->lock);
 
 	WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
@@ -1629,6 +1628,8 @@ static void perf_group_attach(struct perf_event *event)
 {
 	struct perf_event *group_leader = event->group_leader, *pos;
 
+	lockdep_assert_held(&event->ctx->lock);
+
 	/*
 	 * We can have double attach due to group movement in perf_event_open.
 	 */
@@ -1702,6 +1703,8 @@ static void perf_group_detach(struct perf_event *event)
 	struct perf_event *sibling, *tmp;
 	struct list_head *list = NULL;
 
+	lockdep_assert_held(&event->ctx->lock);
+
 	/*
 	 * We can have double detach due to exit/hot-unplug + close.
 	 */
@@ -1900,9 +1903,29 @@ __perf_remove_from_context(struct perf_event *event,
  */
 static void perf_remove_from_context(struct perf_event *event, unsigned long flags)
 {
-	lockdep_assert_held(&event->ctx->mutex);
+	struct perf_event_context *ctx = event->ctx;
+
+	lockdep_assert_held(&ctx->mutex);
 
 	event_function_call(event, __perf_remove_from_context, (void *)flags);
+
+	/*
+	 * The above event_function_call() can NO-OP when it hits
+	 * TASK_TOMBSTONE. In that case we must already have been detached
+	 * from the context (by perf_event_exit_event()) but the grouping
+	 * might still be in-tact.
+	 */
+	WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
+	if ((flags & DETACH_GROUP) &&
+	    (event->attach_state & PERF_ATTACH_GROUP)) {
+		/*
+		 * Since in that case we cannot possibly be scheduled, simply
+		 * detach now.
+		 */
+		raw_spin_lock_irq(&ctx->lock);
+		perf_group_detach(event);
+		raw_spin_unlock_irq(&ctx->lock);
+	}
 }
 
 /*
author Tested-by
#6 lore raw
Quoted 10 lines · Peter Zijlstra
Quoted 7 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

BUG: KASAN: use-after-free in perf_event_for_each_child+0x15f/0x180

kernel/events/core.c:4495 at addr ffff8800680ec248

The below seems to fix things for me.

I've run the program again, it crashed machine in <1 min. Then applied your patch and run the program again, no crashes in 20 minutes. So:

Quoted 74 lines

---
Subject: perf: Fix use-after-free

Dmitry reported a KASAN use-after-free.

XXX: do a changelog

---
 kernel/events/core.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)
kernel/events/core.c +25 -2
index 896aa53..8c1ad98 100644
@@ -1474,7 +1474,6 @@ ctx_group_list(struct perf_event *event, struct perf_event_context *ctx)
 static void
 list_add_event(struct perf_event *event, struct perf_event_context *ctx)
 {
-
        lockdep_assert_held(&ctx->lock);
 
        WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
@@ -1629,6 +1628,8 @@ static void perf_group_attach(struct perf_event *event)
 {
        struct perf_event *group_leader = event->group_leader, *pos;
 
+       lockdep_assert_held(&event->ctx->lock);
+
        /*
         * We can have double attach due to group movement in perf_event_open.
         */
@@ -1702,6 +1703,8 @@ static void perf_group_detach(struct perf_event *event)
        struct perf_event *sibling, *tmp;
        struct list_head *list = NULL;
 
+       lockdep_assert_held(&event->ctx->lock);
+
        /*
         * We can have double detach due to exit/hot-unplug + close.
         */
@@ -1900,9 +1903,29 @@ __perf_remove_from_context(struct perf_event *event,
  */
 static void perf_remove_from_context(struct perf_event *event, unsigned long flags)
 {
-       lockdep_assert_held(&event->ctx->mutex);
+       struct perf_event_context *ctx = event->ctx;
+
+       lockdep_assert_held(&ctx->mutex);
 
        event_function_call(event, __perf_remove_from_context, (void *)flags);
+
+       /*
+        * The above event_function_call() can NO-OP when it hits
+        * TASK_TOMBSTONE. In that case we must already have been detached
+        * from the context (by perf_event_exit_event()) but the grouping
+        * might still be in-tact.
+        */
+       WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
+       if ((flags & DETACH_GROUP) &&
+           (event->attach_state & PERF_ATTACH_GROUP)) {
+               /*
+                * Since in that case we cannot possibly be scheduled, simply
+                * detach now.
+                */
+               raw_spin_lock_irq(&ctx->lock);
+               perf_group_detach(event);
+               raw_spin_unlock_irq(&ctx->lock);
+       }
 }
 
 /*
reviewer Reviewed-by
#7 lore raw
In reply to Peter Zijlstra
Quoted 10 lines · Peter Zijlstra
Quoted 7 lines · Dmitry Vyukov

Hello,

The following program triggers use-after-free in perf_event_for_each:
https://gist.githubusercontent.com/dvyukov/f1c354a8356e42f4d0b3d912e1bec956/raw/31d7ecdf6dc2c7327b80ef8581a39c823bbe405d/gistfile1.txt

BUG: KASAN: use-after-free in perf_event_for_each_child+0x15f/0x180

kernel/events/core.c:4495 at addr ffff8800680ec248

The below seems to fix things for me.

FWIW,

#8 lore raw

[tip:perf/core] perf/core: Fix use-after-free bug

In reply to Peter Zijlstra
perf/core: Fix use-after-free bug
Commit-ID:  a76a82a3e38c8d3fb6499e3dfaeb0949241ab588
Gitweb:     http://git.kernel.org/tip/a76a82a3e38c8d3fb6499e3dfaeb0949241ab588
Author:     Peter Zijlstra <[email protected]>
AuthorDate: Thu, 26 Jan 2017 16:39:55 +0100
Committer:  Ingo Molnar <[email protected]>
CommitDate: Mon, 30 Jan 2017 11:41:25 +0100

perf/core: Fix use-after-free bug

Dmitry reported a KASAN use-after-free on event->group_leader.

It turns out there's a hole in perf_remove_from_context() due to event_function_call() not calling its function when the task associated with the event is already dead.

In this case the event will have been detached from the task, but the grouping will have been retained, such that group operations might still work properly while there are live child events etc.

This does however mean that we can miss a perf_group_detach() call when the group decomposes, this in turn can then lead to use-after-free.

Fix it by explicitly doing the group detach if its still required.

Cc Alexander Shishkin <[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 Mathieu Desnoyers <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc syzkaller <[email protected]>
Fixes 63b6da39bb38 ("perf: Fix perf_event_exit_task() race")
---
 kernel/events/core.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)
kernel/events/core.c +25 -2
index 110b38a..4e1f4c0 100644
@@ -1469,7 +1469,6 @@ ctx_group_list(struct perf_event *event, struct perf_event_context *ctx)
 static void
 list_add_event(struct perf_event *event, struct perf_event_context *ctx)
 {
-
 	lockdep_assert_held(&ctx->lock);
 
 	WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
@@ -1624,6 +1623,8 @@ static void perf_group_attach(struct perf_event *event)
 {
 	struct perf_event *group_leader = event->group_leader, *pos;
 
+	lockdep_assert_held(&event->ctx->lock);
+
 	/*
 	 * We can have double attach due to group movement in perf_event_open.
 	 */
@@ -1697,6 +1698,8 @@ static void perf_group_detach(struct perf_event *event)
 	struct perf_event *sibling, *tmp;
 	struct list_head *list = NULL;
 
+	lockdep_assert_held(&event->ctx->lock);
+
 	/*
 	 * We can have double detach due to exit/hot-unplug + close.
 	 */
@@ -1895,9 +1898,29 @@ __perf_remove_from_context(struct perf_event *event,
  */
 static void perf_remove_from_context(struct perf_event *event, unsigned long flags)
 {
-	lockdep_assert_held(&event->ctx->mutex);
+	struct perf_event_context *ctx = event->ctx;
+
+	lockdep_assert_held(&ctx->mutex);
 
 	event_function_call(event, __perf_remove_from_context, (void *)flags);
+
+	/*
+	 * The above event_function_call() can NO-OP when it hits
+	 * TASK_TOMBSTONE. In that case we must already have been detached
+	 * from the context (by perf_event_exit_event()) but the grouping
+	 * might still be in-tact.
+	 */
+	WARN_ON_ONCE(event->attach_state & PERF_ATTACH_CONTEXT);
+	if ((flags & DETACH_GROUP) &&
+	    (event->attach_state & PERF_ATTACH_GROUP)) {
+		/*
+		 * Since in that case we cannot possibly be scheduled, simply
+		 * detach now.
+		 */
+		raw_spin_lock_irq(&ctx->lock);
+		perf_group_detach(event);
+		raw_spin_unlock_irq(&ctx->lock);
+	}
 }
 
 /*