CVE-2016-6787 (C, published 2016-12-28T07:59Z)
Description: value: kernel/events/core.c in the performance subsystem in the Linux kernel before 4.0 mismanages locks during certain migrations, which allows local users to gain privileges via a crafted application, aka Android internal bug 31095224.
Severity: MEDIUM (CVSS v3 base score: 7)
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/torvalds/linux
Files changed:
kernel/events/core.c
Commit message: perf: Fix event->ctx locking

There have been a few reported issues wrt. the lack of locking around
changing event->ctx. This patch tries to address those.

It avoids the whole rwsem thing; and while it appears to work, please
give it some thought in review.

What I did fail at is sensible runtime checks on the use of
event->ctx, the RCU use makes it very hard.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Link: http://lkml.kernel.org/r/20150123125834.209535886@infradead.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>


Vulnerable code:
void perf_event_disable(struct perf_event *event)
void perf_event_enable(struct perf_event *event)
int perf_event_refresh(struct perf_event *event, int refresh)
	perf_event_enable(event);
		mutex_lock(&owner->perf_event_mutex);
	int n = 0, size = 0, ret = -EFAULT;
	u64 values[5];
	mutex_lock(&ctx->mutex);
		goto unlock;
			ret = -EFAULT;
			goto unlock;
unlock:
	mutex_unlock(&ctx->mutex);
	return perf_read_hw(event, buf, count);
static void perf_event_reset(struct perf_event *event)
	WARN_ON_ONCE(ctx->parent_ctx);
	mutex_lock(&ctx->mutex);
	mutex_unlock(&ctx->mutex);
static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
	struct perf_event *event = file->private_data;
		func = perf_event_enable;
		func = perf_event_disable;
		func = perf_event_reset;
		return perf_event_refresh(event, arg);
	list_for_each_entry(event, &current->perf_event_list, owner_entry)
		perf_event_for_each_child(event, perf_event_enable);
	list_for_each_entry(event, &current->perf_event_list, owner_entry)
		perf_event_for_each_child(event, perf_event_disable);
	struct perf_event_context *ctx;
		struct perf_event_context *gctx = group_leader->ctx;
		mutex_lock(&gctx->mutex);
		mutex_unlock(&gctx->mutex);
		put_ctx(gctx);
	mutex_lock(&ctx->mutex);
	mutex_lock(&src_ctx->mutex);
	mutex_unlock(&src_ctx->mutex);
	mutex_lock(&dst_ctx->mutex);

Fixed code:
/*
 * Because of perf_event::ctx migration in sys_perf_event_open::move_group and
 * perf_pmu_migrate_context() we need some magic.
 *
 * Those places that change perf_event::ctx will hold both
 * perf_event_ctx::mutex of the 'old' and 'new' ctx value.
 *
 * Lock ordering is by mutex address. There is one other site where
 * perf_event_context::mutex nests and that is put_event(). But remember that
 * that is a parent<->child context relation, and migration does not affect
 * children, therefore these two orderings should not interact.
 *
 * The change in perf_event::ctx does not affect children (as claimed above)
 * because the sys_perf_event_open() case will install a new event and break
 * the ctx parent<->child relation, and perf_pmu_migrate_context() is only
 * concerned with cpuctx and that doesn't have children.
 *
 * The places that change perf_event::ctx will issue:
 *
 *   perf_remove_from_context();
 *   synchronize_rcu();
 *   perf_install_in_context();
 *
 * to affect the change. The remove_from_context() + synchronize_rcu() should
 * quiesce the event, after which we can install it in the new location. This
 * means that only external vectors (perf_fops, prctl) can perturb the event
 * while in transit. Therefore all such accessors should also acquire
 * perf_event_context::mutex to serialize against this.
 *
 * However; because event->ctx can change while we're waiting to acquire
 * ctx->mutex we must be careful and use the below perf_event_ctx_lock()
 * function.
 *
 * Lock order:
 *	task_struct::perf_event_mutex
 *	  perf_event_context::mutex
 *	    perf_event_context::lock
 *	    perf_event::child_mutex;
 *	    perf_event::mmap_mutex
 *	    mmap_sem
 */
static struct perf_event_context *perf_event_ctx_lock(struct perf_event *event)
{
	struct perf_event_context *ctx;

again:
	rcu_read_lock();
	ctx = ACCESS_ONCE(event->ctx);
	if (!atomic_inc_not_zero(&ctx->refcount)) {
		rcu_read_unlock();
		goto again;
	}
	rcu_read_unlock();

	mutex_lock(&ctx->mutex);
	if (event->ctx != ctx) {
		mutex_unlock(&ctx->mutex);
		put_ctx(ctx);
		goto again;
	}

	return ctx;
}

static void perf_event_ctx_unlock(struct perf_event *event,
				  struct perf_event_context *ctx)
{
	mutex_unlock(&ctx->mutex);
	put_ctx(ctx);
}

static void _perf_event_disable(struct perf_event *event)

/*
 * Strictly speaking kernel users cannot create groups and therefore this
 * interface does not need the perf_event_ctx_lock() magic.
 */
void perf_event_disable(struct perf_event *event)
{
	struct perf_event_context *ctx;

	ctx = perf_event_ctx_lock(event);
	_perf_event_disable(event);
	perf_event_ctx_unlock(event, ctx);
}
static void _perf_event_enable(struct perf_event *event)

/*
 * See perf_event_disable();
 */
void perf_event_enable(struct perf_event *event)
{
	struct perf_event_context *ctx;

	ctx = perf_event_ctx_lock(event);
	_perf_event_enable(event);
	perf_event_ctx_unlock(event, ctx);
}
static int _perf_event_refresh(struct perf_event *event, int refresh)
	_perf_event_enable(event);

/*
 * See perf_event_disable()
 */
int perf_event_refresh(struct perf_event *event, int refresh)
{
	struct perf_event_context *ctx;
	int ret;

	ctx = perf_event_ctx_lock(event);
	ret = _perf_event_refresh(event, refresh);
	perf_event_ctx_unlock(event, ctx);

	return ret;
}
		/*
		 * If we're here through perf_event_exit_task() we're already
		 * holding ctx->mutex which would be an inversion wrt. the
		 * normal lock order.
		 *
		 * However we can safely take this lock because its the child
		 * ctx->mutex.
		 */
		mutex_lock_nested(&owner->perf_event_mutex, SINGLE_DEPTH_NESTING);

	int n = 0, size = 0, ret;
	u64 values[5];

	lockdep_assert_held(&ctx->mutex);
		return -EFAULT;
			return -EFAULT;
	struct perf_event_context *ctx;
	int ret;
	ctx = perf_event_ctx_lock(event);
	ret = perf_read_hw(event, buf, count);
	perf_event_ctx_unlock(event, ctx);

	return ret;
static void _perf_event_reset(struct perf_event *event)

	lockdep_assert_held(&ctx->mutex);

static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned long arg)
		func = _perf_event_enable;
		func = _perf_event_disable;
		func = _perf_event_reset;
		return _perf_event_refresh(event, arg);
static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct perf_event *event = file->private_data;
	struct perf_event_context *ctx;
	long ret;

	ctx = perf_event_ctx_lock(event);
	ret = _perf_ioctl(event, cmd, arg);
	perf_event_ctx_unlock(event, ctx);

	return ret;
}

	struct perf_event_context *ctx;
	list_for_each_entry(event, &current->perf_event_list, owner_entry) {
		ctx = perf_event_ctx_lock(event);
		perf_event_for_each_child(event, _perf_event_enable);
		perf_event_ctx_unlock(event, ctx);
	}
	struct perf_event_context *ctx;
	list_for_each_entry(event, &current->perf_event_list, owner_entry) {
		ctx = perf_event_ctx_lock(event);
		perf_event_for_each_child(event, _perf_event_disable);
		perf_event_ctx_unlock(event, ctx);
	}
static void mutex_lock_double(struct mutex *a, struct mutex *b)
{
	if (b < a)
		swap(a, b);

	mutex_lock(a);
	mutex_lock_nested(b, SINGLE_DEPTH_NESTING);
}

	struct perf_event_context *ctx, *uninitialized_var(gctx);
		gctx = group_leader->ctx;

		/*
		 * See perf_event_ctx_lock() for comments on the details
		 * of swizzling perf_event::ctx.
		 */
		mutex_lock_double(&gctx->mutex, &ctx->mutex);
	} else {
		mutex_lock(&ctx->mutex);
		/*
		 * Wait for everybody to stop referencing the events through
		 * the old lists, before installing it on new lists.
		 */


	if (move_group) {
		mutex_unlock(&gctx->mutex);
		put_ctx(gctx);
	}
	/*
	 * See perf_event_ctx_lock() for comments on the details
	 * of swizzling perf_event::ctx.
	 */
	mutex_lock_double(&src_ctx->mutex, &dst_ctx->mutex);
	mutex_unlock(&src_ctx->mutex);