Re: [RFC PATCH] perf: Paper over the hw.target problems

Alexander Shishkin
2019-03-08T12:38:57Z

Xie XiuQi <xiexiuqi@huawei.com> writes:

> From: Cheng Jian <cj.chengjian@huawei.com>
>
> Hi Alexander,

Hi Cheng Jian,

> I have tested this patch and merged into 4.19 stable branch,
> syzkaller reported some new issues(WANRING), the C program
> generated by syzkaller can inevitably reproduce the issue
> (only in patched kernel).
>
> I put the syzkaller log and the manual test log in the attachment.

Thanks a lot for taking the time and providing all the details. Please
find an updated version below, which should not exhibit that warning.

From 1576f66b4311fe81107c3c39c38060178bc89457 Mon Sep 17 00:00:00 2001
From: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Date: Thu, 28 Feb 2019 15:24:04 +0200
Subject: [RFC PATCH v1] perf: Paper over the hw.target problems

First, we have a race between perf_event_release_kernel() and
perf_free_event(), which happens when parent's event is released while the
child's fork fails (because of a fatal signal, for example), that looks
like this:

cpu X cpu Y
----- -----
copy_process() error path
perf_release(parent) +->perf_event_free_task()
+-> lock(child_ctx->mutex) | |
+-> remove_from_context(child) | |
+-> unlock(child_ctx->mutex) | |
| | +-> lock(child_ctx->mutex)
| | +-> unlock(child_ctx->mutex)
| +-> free_task(child_task)
+-> put_task_struct(child_task)

Technically, we're still holding a reference to the task via
parent->hw.target, that's not stopping free_task(), so we end up poking at
free'd memory, as is pointed out by KASAN in the syzkaller report (see Link
below). The straightforward fix is to drop the hw.target reference while
the task is still around.

Therein lies the second problem: the users of hw.target (uprobe) assume
that it's around at ->destroy() callback time, where they use it for
context. So, in order to not break the uprobe teardown and avoid leaking
stuff, we need to call ->destroy() at the same time.

This patch fixes the race and the subsequent fallout by doing both these
things at remove_from_context time.

Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Link: https://syzkaller.appspot.com/bug?extid=a24c397a29ad22d86c98
---
kernel/events/core.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 6797f0db0299..83469aae2774 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -2106,6 +2106,28 @@ static void perf_remove_from_context(struct perf_event *event, unsigned long fla

event_function_call(event, __perf_remove_from_context, (void *)flags);

+ /*
+ * This is as passable as any hw.target handling out there;
+ * hw.target implies task context, therefore, no migration.
+ * Which together with DETACH_GROUP means that this is the
+ * final remove_from_context of a task event.
+ */
+ if (event->hw.target && (flags & DETACH_GROUP)) {
+ /*
+ * Now, the problem with, say uprobes, is that they
+ * use hw.target for context in their ->destroy()
+ * callbacks. Supposedly, they may need to poke at
+ * its contents, so better call it while we still
+ * have the task.
+ */
+ if (event->destroy) {
+ event->destroy(event);
+ event->destroy = NULL;
+ }
+ put_task_struct(event->hw.target);
+ event->hw.target = NULL;
+ }
+
/*
* The above event_function_call() can NO-OP when it hits
* TASK_TOMBSTONE. In that case we must already have been detached
--
2.20.1