From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753303AbdCNPHy (ORCPT ); Tue, 14 Mar 2017 11:07:54 -0400 Received: from bombadil.infradead.org ([65.50.211.133]:51279 "EHLO bombadil.infradead.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750795AbdCNPHv (ORCPT ); Tue, 14 Mar 2017 11:07:51 -0400 Date: Tue, 14 Mar 2017 16:07:39 +0100 From: Peter Zijlstra To: Oleg Nesterov Cc: Dmitry Vyukov , Ingo Molnar , Arnaldo Carvalho de Melo , Alexander Shishkin , LKML , Mathieu Desnoyers , syzkaller Subject: Re: perf: use-after-free in perf_release Message-ID: <20170314150739.GD32474@worktop> References: <20170306131459.GC6515@twins.programming.kicks-ass.net> <20170307140414.GA31678@redhat.com> <20170307165131.GA6097@redhat.com> <20170314125508.GK3343@twins.programming.kicks-ass.net> <20170314140302.GA28146@redhat.com> <20170314140754.GG3328@twins.programming.kicks-ass.net> <20170314143010.GA30351@redhat.com> <20170314150241.GO5680@worktop> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20170314150241.GO5680@worktop> User-Agent: Mutt/1.5.22.1 (2013-10-16) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Tue, Mar 14, 2017 at 04:02:41PM +0100, Peter Zijlstra wrote: > On Tue, Mar 14, 2017 at 03:30:11PM +0100, Oleg Nesterov wrote: > > > But. perf_event_init_task() adds child_event to parent_event->child_list. > > > > If perf_event_release_kernel(parent_event) is called before copy_process() > > does perf_event_free_task() which (in particular) removes it from child_list, > > perf_event_release_kernel() can find this child_event and do get_ctx(ctx) > > (under the list_for_each_entry(child, &event->child_list, child_list) loop). > > Right; the child_list is the only thing that is exposed. And yes, it > looks like that can interleave just right. > > > Then it does put_ctx(ctx), but ctx->task can be already freed by > > copy_process()->free_task() in this case. > > > Task1 Task2 > > fork() > perf_event_init_task() > /* ... */ > goto bad_fork_$foo; > /* ... */ > perf_event_free_task() > mutex_lock(ctx->lock) > perf_free_event(B) > > perf_event_release_kernel(A) > mutex_lock(A->child_mutex) > list_for_each_entry(child, ...) { > /* child == B */ > ctx = B->ctx; > get_ctx(ctx); > mutex_unlock(A->child_mutex); > > mutex_lock(A->child_mutex) > list_del_init(B->child_list) > mutex_unlock(A->child_mutex) > > /* ... */ > > mutex_unlock(ctx->lock); > put_ctx() /* >0 */ > free_task(); > mutex_lock(ctx->lock); > mutex_lock(A->child_mutex); > /* ... */ > mutex_unlock(A->child_mutex); > mutex_unlock(ctx->lock) > put_ctx() /* 0 */ > ctx->task && !TOMBSTONE > put_task_struct() /* UAF */ > > > Something like that, right? > > > Let me see if it makes sense to retain perf_event_free_task() at all; > maybe we should always do perf_event_exit_task(). Do we want a WARN_ON_ONCE(atomic_read(&tsk->usage)); in free_task()? Because in the above scenario we're freeing it with references on.