Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dbe08d82 authored by Oleg Nesterov's avatar Oleg Nesterov Committed by Ingo Molnar
Browse files

perf: Fix find_get_context() vs perf_event_exit_task() race



find_get_context() must not install the new perf_event_context
if the task has already passed perf_event_exit_task().

If nothing else, this means the memory leak. Initially
ctx->refcount == 2, it is supposed that
perf_event_exit_task_context() should participate and do the
necessary put_ctx().

find_lively_task_by_vpid() checks PF_EXITING but this buys
nothing, by the time we call find_get_context() this task can be
already dead. To the point, cmpxchg() can succeed when the task
has already done the last schedule().

Change find_get_context() to populate task->perf_event_ctxp[]
under task->perf_event_mutex, this way we can trust PF_EXITING
because perf_event_exit_task() takes the same mutex.

Also, change perf_event_exit_task_context() to use
rcu_dereference(). Probably this is not strictly needed, but
with or without this change find_get_context() can race with
setup_new_exec()->perf_event_exit_task(), rcu_dereference()
looks better.

Signed-off-by: default avatarOleg Nesterov <oleg@redhat.com>
Acked-by: default avatarPeter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Prasad <prasad@linux.vnet.ibm.com>
Cc: Roland McGrath <roland@redhat.com>
LKML-Reference: <20110119182207.GB12183@redhat.com>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent c56eb8fb
Loading
Loading
Loading
Loading
+20 −14
Original line number Original line Diff line number Diff line
@@ -2201,13 +2201,6 @@ find_lively_task_by_vpid(pid_t vpid)
	if (!task)
	if (!task)
		return ERR_PTR(-ESRCH);
		return ERR_PTR(-ESRCH);


	/*
	 * Can't attach events to a dying task.
	 */
	err = -ESRCH;
	if (task->flags & PF_EXITING)
		goto errout;

	/* Reuse ptrace permission checks for now. */
	/* Reuse ptrace permission checks for now. */
	err = -EACCES;
	err = -EACCES;
	if (!ptrace_may_access(task, PTRACE_MODE_READ))
	if (!ptrace_may_access(task, PTRACE_MODE_READ))
@@ -2268,14 +2261,27 @@ find_get_context(struct pmu *pmu, struct task_struct *task, int cpu)


		get_ctx(ctx);
		get_ctx(ctx);


		if (cmpxchg(&task->perf_event_ctxp[ctxn], NULL, ctx)) {
		err = 0;
		mutex_lock(&task->perf_event_mutex);
		/*
		/*
			 * We raced with some other task; use
		 * If it has already passed perf_event_exit_task().
			 * the context they set.
		 * we must see PF_EXITING, it takes this mutex too.
		 */
		 */
		if (task->flags & PF_EXITING)
			err = -ESRCH;
		else if (task->perf_event_ctxp[ctxn])
			err = -EAGAIN;
		else
			rcu_assign_pointer(task->perf_event_ctxp[ctxn], ctx);
		mutex_unlock(&task->perf_event_mutex);

		if (unlikely(err)) {
			put_task_struct(task);
			put_task_struct(task);
			kfree(ctx);
			kfree(ctx);

			if (err == -EAGAIN)
				goto retry;
				goto retry;
			goto errout;
		}
		}
	}
	}


@@ -6127,7 +6133,7 @@ static void perf_event_exit_task_context(struct task_struct *child, int ctxn)
	 * scheduled, so we are now safe from rescheduling changing
	 * scheduled, so we are now safe from rescheduling changing
	 * our context.
	 * our context.
	 */
	 */
	child_ctx = child->perf_event_ctxp[ctxn];
	child_ctx = rcu_dereference(child->perf_event_ctxp[ctxn]);
	task_ctx_sched_out(child_ctx, EVENT_ALL);
	task_ctx_sched_out(child_ctx, EVENT_ALL);


	/*
	/*