From nobody Sat Jun 13 23:11:06 2026 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 61D5540F8DF; Tue, 5 May 2026 10:50:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777978208; cv=none; b=g9mzIDb363hqp4kc/Vuclqp8R9pvPOk6+MAREriYihQY8Y5VgscxeFvFh/0t58wvZBVeZ7LzUS80J0poekApwjcMn9aAmwK1I5Lsb30tLdfBuO/mzv/Rew/+w3fTP2U5QdPDTumuP/RZ0/Qda75rqHKan537V3nJWDQRZn4To6A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777978208; c=relaxed/simple; bh=NwK+JLiYWovKKvKZVXidp2I055TyeZgb32GcODA5VcE=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=UZMzJiEbaI2OMnCAXwTkGGS+i06BCqpQpmbVciFxkYU+gMHNRX6+N/nQeoP5PKNA6PTrRHR57of2VWtvt8l4k+tX3tD2jmHihOCg4XxI7U8/hByieMDTlP6ctLE+Qm+wPuN9+vFgupG5d8EEe6/gwnaCC1u7rRXrkRRZrqoLH4M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=zJEeV5Wt; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=WwBBVU/+; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="zJEeV5Wt"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="WwBBVU/+" Date: Tue, 05 May 2026 10:50:03 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1777978204; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=nESYpD/IlRCiS1ABTzZgKATlDmUGjlUmKH1zxURMNqU=; b=zJEeV5WtgwR7Ds7Ouqb/FUNW8F49RZDpmMBzv7w3wDushTwfX9j/uL+1BNHeI4m1ndIFLg f42rrN1qt3gpnJyw3mijn24cf73c52n6Lk1KbY93JSOLX82Zp23IhmwQ3OIk2ecSJCcSp6 +2pMN79Zow4JBOslH+H8thk/Y3LYslhYoPt6JcwC+hHcRxphHXlqgdcme/wLywi1N83Fhq qMvganbeXgiDdRGRLHxdHVRplp81SKgYODq7CBvaq1NErmYOPd6TCmeCIWGQOsaRgNNXb5 lXdwEu+b65CNOye977rgjto/v+L/MqK8nnk0HrwNBNd5oJIJE7o+3vWv8f0csw== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1777978204; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=nESYpD/IlRCiS1ABTzZgKATlDmUGjlUmKH1zxURMNqU=; b=WwBBVU/++xPU4Oh5Xuj3SiQntFnYSRxmiSBVMv+0yUHk5aXLEha5tUtCgP8BreLNcY4zsK pplbDpDTFOuEeWAw== From: "tip-bot2 for Peter Zijlstra" Sender: tip-bot2@linutronix.de Reply-to: linux-kernel@vger.kernel.org To: linux-tip-commits@vger.kernel.org Subject: [tip: perf/core] perf/core: Fix deadlock in perf_mmap() failure path Cc: Ian Rogers , "Peter Zijlstra (Intel)" , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: <20260326112821.GK3738786@noisy.programming.kicks-ass.net> References: <20260326112821.GK3738786@noisy.programming.kicks-ass.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-ID: <177797820328.424702.1692691083935892466.tip-bot2@tip-bot2> Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails Precedence: bulk Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The following commit has been merged into the perf/core branch of tip: Commit-ID: c69df06e4e26e50611190ce04eab92c5cc261b61 Gitweb: https://git.kernel.org/tip/c69df06e4e26e50611190ce04eab92c5c= c261b61 Author: Peter Zijlstra AuthorDate: Thu, 26 Mar 2026 12:28:21 +01:00 Committer: Peter Zijlstra CommitterDate: Tue, 05 May 2026 12:47:20 +02:00 perf/core: Fix deadlock in perf_mmap() failure path Ian noted that commit 77de62ad3de3 ("perf/core: Fix refcount bug and potential UAF in perf_mmap") would cause a deadlock due to event->mmap_mutex recursion. This happens because we're now calling perf_mmap_close() under mmap_mutex, while that function itself can also take mmap_mutex. Solve this by noting that perf_mmap_close() is far more complicated than we need at this particular point, since it deals with scenarios that cannot happen in this particular case. Replace the call to perf_mmap_close() with a very narrow undo for the case of first-exposure. If this is not the first mmap(), there is no race and it is fine to drop the lock and call perf_mmap_close() to handle to more complicated scenarios. Note: move the rb->mmap_user (namespace) handling into the rb init/free code such that it does not complicate the mmap handling. Fixes: 77de62ad3de3 ("perf/core: Fix refcount bug and potential UAF in perf= _mmap") Reported-by: Ian Rogers Closes: https://patch.msgid.link/CAP-5%3DfVJyVMZw%3DDqP53Kxg58nUmJ_0bxoaeOK= AbC03BVc11HaA%40mail.gmail.com Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260326112821.GK3738786@noisy.programming.k= icks-ass.net --- kernel/events/core.c | 70 ++++++++++++++++++++++++++++-------- kernel/events/internal.h | 1 +- kernel/events/ring_buffer.c | 2 +- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 6d1f8ba..7935d56 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7006,6 +7006,7 @@ static void perf_mmap_open(struct vm_area_struct *vma) } =20 static void perf_pmu_output_stop(struct perf_event *event); +static void perf_mmap_unaccount(struct vm_area_struct *vma, struct perf_bu= ffer *rb); =20 /* * A buffer can be mmap()ed multiple times; either directly through the sa= me @@ -7021,8 +7022,6 @@ static void perf_mmap_close(struct vm_area_struct *vm= a) mapped_f unmapped =3D get_mapped(event, event_unmapped); struct perf_buffer *rb =3D ring_buffer_get(event); struct user_struct *mmap_user =3D rb->mmap_user; - int mmap_locked =3D rb->mmap_locked; - unsigned long size =3D perf_data_size(rb); bool detach_rest =3D false; =20 /* FIXIES vs perf_pmu_unregister() */ @@ -7117,11 +7116,7 @@ again: * Aside from that, this buffer is 'fully' detached and unmapped, * undo the VM accounting. */ - - atomic_long_sub((size >> PAGE_SHIFT) + 1 - mmap_locked, - &mmap_user->locked_vm); - atomic64_sub(mmap_locked, &vma->vm_mm->pinned_vm); - free_uid(mmap_user); + perf_mmap_unaccount(vma, rb); =20 out_put: ring_buffer_put(rb); /* could be last */ @@ -7261,6 +7256,15 @@ static void perf_mmap_account(struct vm_area_struct = *vma, long user_extra, long=20 atomic64_add(extra, &vma->vm_mm->pinned_vm); } =20 +static void perf_mmap_unaccount(struct vm_area_struct *vma, struct perf_bu= ffer *rb) +{ + struct user_struct *user =3D rb->mmap_user; + + atomic_long_sub((perf_data_size(rb) >> PAGE_SHIFT) + 1 - rb->mmap_locked, + &user->locked_vm); + atomic64_sub(rb->mmap_locked, &vma->vm_mm->pinned_vm); +} + static int perf_mmap_rb(struct vm_area_struct *vma, struct perf_event *eve= nt, unsigned long nr_pages) { @@ -7323,8 +7327,6 @@ static int perf_mmap_rb(struct vm_area_struct *vma, s= truct perf_event *event, if (!rb) return -ENOMEM; =20 - refcount_set(&rb->mmap_count, 1); - rb->mmap_user =3D get_current_user(); rb->mmap_locked =3D extra; =20 ring_buffer_attach(event, rb); @@ -7474,16 +7476,54 @@ static int perf_mmap(struct file *file, struct vm_a= rea_struct *vma) mapped(event, vma->vm_mm); =20 /* - * Try to map it into the page table. On fail, invoke - * perf_mmap_close() to undo the above, as the callsite expects - * full cleanup in this case and therefore does not invoke - * vmops::close(). + * Try to map it into the page table. On fail undo the above, + * as the callsite expects full cleanup in this case and + * therefore does not invoke vmops::close(). */ ret =3D map_range(event->rb, vma); - if (ret) - perf_mmap_close(vma); + if (likely(!ret)) + return 0; + + /* Error path */ + + /* + * If this is the first mmap(), then event->mmap_count should + * be stable at 1. It is only modified by: + * perf_mmap_{open,close}() and perf_mmap(). + * + * The former are not possible because this mmap() hasn't been + * successful yet, and the latter is serialized by + * event->mmap_mutex which we still hold (note that mmap_lock + * is not strictly sufficient here, because the event fd can + * be passed to another process through trivial means like + * fork(), leading to concurrent mmap() from different mm). + * + * Make sure to remove event->rb before releasing + * event->mmap_mutex, such that any concurrent mmap() will not + * attempt use this failed buffer. + */ + if (refcount_read(&event->mmap_count) =3D=3D 1) { + /* + * Minimal perf_mmap_close(); there can't be AUX or + * other events on account of this being the first. + */ + mapped =3D get_mapped(event, event_unmapped); + if (mapped) + mapped(event, vma->vm_mm); + perf_mmap_unaccount(vma, event->rb); + ring_buffer_attach(event, NULL); /* drops last rb->refcount */ + refcount_set(&event->mmap_count, 0); + return ret; + } + + /* + * Otherwise this is an already existing buffer, and there is + * no race vs first exposure, so fall-through and call + * perf_mmap_close(). + */ } =20 + perf_mmap_close(vma); return ret; } =20 diff --git a/kernel/events/internal.h b/kernel/events/internal.h index d9cc570..c03c4f2 100644 --- a/kernel/events/internal.h +++ b/kernel/events/internal.h @@ -67,6 +67,7 @@ static inline void rb_free_rcu(struct rcu_head *rcu_head) struct perf_buffer *rb; =20 rb =3D container_of(rcu_head, struct perf_buffer, rcu_head); + free_uid(rb->mmap_user); rb_free(rb); } =20 diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 3e7de26..9fe9216 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -340,6 +340,8 @@ ring_buffer_init(struct perf_buffer *rb, long watermark= , int flags) rb->paused =3D 1; =20 mutex_init(&rb->aux_mutex); + rb->mmap_user =3D get_current_user(); + refcount_set(&rb->mmap_count, 1); } =20 void perf_aux_output_flag(struct perf_output_handle *handle, u64 flags)