FAILED: patch "[PATCH] perf: Fix use-after-free when perf mmap() revival races with" failed to apply to 6.18-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 58a8108bc73de0740d5b88150465d6690ea5f85f
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090946-pacify-trodden-af31@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 58a8108bc73de0740d5b88150465d6690ea5f85f Mon Sep 17 00:00:00 2001
From: Yilin Zhang <[email protected]>
Date: Tue, 1 Sep 2026 00:21:55 +0800
Subject: [PATCH] perf: Fix use-after-free when perf mmap() revival races with
the last munmap()
perf_mmap_close() drops rb->mmap_count *without* holding event->mmap_mutex (the refcount_dec_and_test() right before the refcount_dec_and_mutex_lock() of event->mmap_count). A concurrent perf_mmap_rb() can slot its entire "revival" path into that window (perf_mmap holds event->mmap_mutex for its whole duration, including rb_alloc):
munmap side (perf_mmap_close) mmap side (perf_mmap_rb)
----------------------------------- --------------------------------
rb->mmap_count 1 -> 0 (no lock) (holds event->mmap_mutex)
inc_not_zero(rb->mmap_count) fails
ring_buffer_attach(event, NULL)
rb_alloc() + attach new rb
refcount_set(&event->mmap_count, 1) lock; event->mmap_count 1 -> 0
ring_buffer_attach(event, NULL)
ring_buffer_put() -> frees the *new* rb
The revival's refcount_set(&event->mmap_count, 1) is an invisible 1 -> 1 write: the close frees the just-revived buffer although the other process still has it mapped -- a page-level use-after-free allowing local privilege escalation to root by any unprivileged user (default kernel.perf_event_paranoid=2).
Swap the order of the two counter updates: event->mmap_count is dropped first via refcount_dec_and_mutex_lock(), so its 1 -> 0 transition and the ring_buffer_attach() stay serialized with perf_mmap(). rb->mmap_count == 0 then implies every event using the buffer is detached already, so the result of the rb->mmap_count drop can gate the remaining teardown directly and detach_rest is no longer needed.
An earlier fix for this race from Kyle Zeng and David Lee takes event->mmap_mutex around both counter updates [0]; here the not-last close stays lockless.
kernel/events/core.c +10 -10
@@ -7029,7 +7029,6 @@ static void perf_mmap_close(struct vm_area_struct *vma)mapped_f unmapped =get_mapped (event, event_unmapped);struct perf_buffer *rb =ring_buffer_get (event);struct user_struct *mmap_user = rb->mmap_user; -bool detach_rest = false;/* FIXIES vs perf_pmu_unregister() */ if (unmapped)
@@ -7060,17 +7059,18 @@ static void perf_mmap_close(struct vm_area_struct *vma)mutex_unlock (&rb->aux_mutex); } -if (refcount_dec_and_test (&rb->mmap_count)) - detach_rest = true; - -if (!refcount_dec_and_mutex_lock (&event->mmap_count, &event->mmap_mutex)) - goto out_put; - -ring_buffer_attach (event,NULL ); -mutex_unlock (&event->mmap_mutex); +/* +* Drop references in reverse order of perf_mmap() to prevent +* rb revival after rb->mmap_count reaches zero. +*/ +if (refcount_dec_and_mutex_lock (&event->mmap_count, + &event->mmap_mutex)) { +ring_buffer_attach (event,NULL ); +mutex_unlock (&event->mmap_mutex); + }/* If there's still other mmap()s of this buffer, we're done. */ -if (!detach_rest) +if (!refcount_dec_and_test (&rb->mmap_count)) goto out_put;/*
Sasha Levin
maintainerPATCH 1/2 perf/core: Fix deadlock in perf_mmap() failure path
From: Peter Zijlstra <[email protected]>
[ Upstream commit c69df06e4e26e50611190ce04eab92c5cc261b61 ]
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.
Stable-dep-of: 58a8108bc73d ("perf: Fix use-after-free when perf mmap() revival races with the last munmap()")
--- kernel/events/core.c | 70 +++++++++++++++++++++++++++++-------- kernel/events/internal.h | 1 + kernel/events/ring_buffer.c | 2 ++ 3 files changed, 58 insertions(+), 15 deletions(-)
kernel/events/core.c +55 -15
@@ -6737,6 +6737,7 @@ static void perf_mmap_open(struct vm_area_struct *vma) }static void perf_pmu_output_stop (struct perf_event *event); +static void perf_mmap_unaccount (struct vm_area_struct *vma,struct perf_buffer *rb);/* * A buffer can be mmap()ed multiple times; either directly through the same
@@ -6752,8 +6753,6 @@ static void perf_mmap_close(struct vm_area_struct *vma)mapped_f unmapped =get_mapped (event, event_unmapped);struct perf_buffer *rb =ring_buffer_get (event);struct user_struct *mmap_user = rb->mmap_user; -int mmap_locked = rb->mmap_locked; -unsigned long size =perf_data_size (rb);bool detach_rest = false;/* FIXIES vs perf_pmu_unregister() */
@@ -6848,11 +6847,7 @@ static void perf_mmap_close(struct vm_area_struct *vma)* 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); out_put:ring_buffer_put (rb);/* could be last */
@@ -6994,6 +6989,15 @@ static void perf_mmap_account(struct vm_area_struct *vma, long user_extra, longatomic64_add (extra, &vma->vm_mm->pinned_vm); } +static void perf_mmap_unaccount (struct vm_area_struct *vma,struct perf_buffer *rb) +{ +struct user_struct *user = 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 *event,unsigned long nr_pages) {
@@ -7056,8 +7060,6 @@ static int perf_mmap_rb(struct vm_area_struct *vma, struct perf_event *event,if (!rb)return -ENOMEM ; -refcount_set (&rb->mmap_count,1 ); - rb->mmap_user =get_current_user (); rb->mmap_locked = extra;ring_buffer_attach (event, rb);
@@ -7207,16 +7209,54 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma)mapped (event, vma->vm_mm);/* -* 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 =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) ==1 ) { +/* +* Minimal perf_mmap_close(); there can't be AUX or +* other events on account of this being the first. +*/ + mapped =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(). +*/ } +perf_mmap_close (vma);return ret; }
kernel/events/internal.h +1 -0
@@ -67,6 +67,7 @@ static inline void rb_free_rcu(struct rcu_head *rcu_head)struct perf_buffer *rb; rb = container_of(rcu_head, struct perf_buffer, rcu_head); +free_uid (rb->mmap_user);rb_free (rb); }
kernel/events/ring_buffer.c +2 -0
@@ -340,6 +340,8 @@ ring_buffer_init(struct perf_buffer *rb, long watermark, int flags) rb->paused =1 ;mutex_init (&rb->aux_mutex); + rb->mmap_user =get_current_user (); +refcount_set (&rb->mmap_count,1 ); }void perf_aux_output_flag (struct perf_output_handle *handle,u64 flags)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 perf: Fix use-after-free when perf mmap() revival races with the last munmap()
From: Yilin Zhang <[email protected]>
[ Upstream commit 58a8108bc73de0740d5b88150465d6690ea5f85f ]
perf_mmap_close() drops rb->mmap_count *without* holding event->mmap_mutex (the refcount_dec_and_test() right before the refcount_dec_and_mutex_lock() of event->mmap_count). A concurrent perf_mmap_rb() can slot its entire "revival" path into that window (perf_mmap holds event->mmap_mutex for its whole duration, including rb_alloc):
munmap side (perf_mmap_close) mmap side (perf_mmap_rb)
----------------------------------- --------------------------------
rb->mmap_count 1 -> 0 (no lock) (holds event->mmap_mutex)
inc_not_zero(rb->mmap_count) fails
ring_buffer_attach(event, NULL)
rb_alloc() + attach new rb
refcount_set(&event->mmap_count, 1) lock; event->mmap_count 1 -> 0
ring_buffer_attach(event, NULL)
ring_buffer_put() -> frees the *new* rb
The revival's refcount_set(&event->mmap_count, 1) is an invisible 1 -> 1 write: the close frees the just-revived buffer although the other process still has it mapped -- a page-level use-after-free allowing local privilege escalation to root by any unprivileged user (default kernel.perf_event_paranoid=2).
Swap the order of the two counter updates: event->mmap_count is dropped first via refcount_dec_and_mutex_lock(), so its 1 -> 0 transition and the ring_buffer_attach() stay serialized with perf_mmap(). rb->mmap_count == 0 then implies every event using the buffer is detached already, so the result of the rb->mmap_count drop can gate the remaining teardown directly and detach_rest is no longer needed.
An earlier fix for this race from Kyle Zeng and David Lee takes event->mmap_mutex around both counter updates [0]; here the not-last close stays lockless.
--- kernel/events/core.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-)
kernel/events/core.c +10 -10
@@ -6753,7 +6753,6 @@ static void perf_mmap_close(struct vm_area_struct *vma)mapped_f unmapped =get_mapped (event, event_unmapped);struct perf_buffer *rb =ring_buffer_get (event);struct user_struct *mmap_user = rb->mmap_user; -bool detach_rest = false;/* FIXIES vs perf_pmu_unregister() */ if (unmapped)
@@ -6784,17 +6783,18 @@ static void perf_mmap_close(struct vm_area_struct *vma)mutex_unlock (&rb->aux_mutex); } -if (refcount_dec_and_test (&rb->mmap_count)) - detach_rest = true; - -if (!refcount_dec_and_mutex_lock (&event->mmap_count, &event->mmap_mutex)) - goto out_put; - -ring_buffer_attach (event,NULL ); -mutex_unlock (&event->mmap_mutex); +/* +* Drop references in reverse order of perf_mmap() to prevent +* rb revival after rb->mmap_count reaches zero. +*/ +if (refcount_dec_and_mutex_lock (&event->mmap_count, + &event->mmap_mutex)) { +ring_buffer_attach (event,NULL ); +mutex_unlock (&event->mmap_mutex); + }/* If there's still other mmap()s of this buffer, we're done. */ -if (!detach_rest) +if (!refcount_dec_and_test (&rb->mmap_count)) goto out_put;/*
Signature · 1 line
2.53.0