From e597bc6ab534549df806604cbcb2050fa6ccbb3b Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Wed, 6 Mar 2019 16:15:01 +0800 Subject: [PATCH] perf: Paper over the hw.target problems euler inclusion category: bugfix bugzilla: 9513/11006 CVE: NA -------------------------------------------------- [ Cheng Jian HULK-Syzkaller reported a problem which has been reported to mainline(lkml) by syzbot early, this patch comes from the reply form lkml. https://lkml.org/lkml/2019/2/28/529 ] 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 Link: https://syzkaller.appspot.com/bug?extid=a24c397a29ad22d86c98 Reported-by: syzbot+a24c397a29ad22d86c98@syzkaller.appspotmail.com Signed-off-by: Cheng Jian Reviewed-by: Li Bin Signed-off-by: Yang Yingliang --- kernel/events/core.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index f47fcac5bba2..2593545323dd 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2104,6 +2104,27 @@ 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 means that we can only get here at the teardown. + */ + if (event->hw.target) { + /* + * 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 -- GitLab