--- /dev/null
+From 650d471664e56e4099c838d0f8978a533996bad1 Mon Sep 17 00:00:00 2001
+From: Thomas Gleixner <tglx@linutronix.de>
+Date: Wed, 30 Jul 2025 23:01:21 +0200
+Subject: perf/core: Prevent VMA split of buffer mappings
+
+From: Thomas Gleixner <tglx@linutronix.de>
+
+commit b024d7b56c77191cde544f838debb7f8451cd0d6 upstream.
+
+The perf mmap code is careful about mmap()'ing the user page with the
+ringbuffer and additionally the auxiliary buffer, when the event supports
+it. Once the first mapping is established, subsequent mapping have to use
+the same offset and the same size in both cases. The reference counting for
+the ringbuffer and the auxiliary buffer depends on this being correct.
+
+Though perf does not prevent that a related mapping is split via mmap(2),
+munmap(2) or mremap(2). A split of a VMA results in perf_mmap_open() calls,
+which take reference counts, but then the subsequent perf_mmap_close()
+calls are not longer fulfilling the offset and size checks. This leads to
+reference count leaks.
+
+As perf already has the requirement for subsequent mappings to match the
+initial mapping, the obvious consequence is that VMA splits, caused by
+resizing of a mapping or partial unmapping, have to be prevented.
+
+Implement the vm_operations_struct::may_split() callback and return
+unconditionally -EINVAL.
+
+That ensures that the mapping offsets and sizes cannot be changed after the
+fact. Remapping to a different fixed address with the same size is still
+possible as it takes the references for the new mapping and drops those of
+the old mapping.
+
+Fixes: 45bfb2e50471 ("perf/core: Add AUX area to ring buffer for raw data streams")
+Reported-by: zdi-disclosures@trendmicro.com # ZDI-CAN-27504
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Acked-by: Arnaldo Carvalho de Melo <acme@redhat.com>
+Acked-by: Vlastimil Babka <vbabka@suse.cz>
+Cc: stable@vger.kernel.org
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/events/core.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/kernel/events/core.c
++++ b/kernel/events/core.c
+@@ -6285,11 +6285,21 @@ out_put:
+ ring_buffer_put(rb); /* could be last */
+ }
+
++static int perf_mmap_may_split(struct vm_area_struct *vma, unsigned long addr)
++{
++ /*
++ * Forbid splitting perf mappings to prevent refcount leaks due to
++ * the resulting non-matching offsets and sizes. See open()/close().
++ */
++ return -EINVAL;
++}
++
+ static const struct vm_operations_struct perf_mmap_vmops = {
+ .open = perf_mmap_open,
+ .close = perf_mmap_close, /* non mergeable */
+ .fault = perf_mmap_fault,
+ .page_mkwrite = perf_mmap_fault,
++ .may_split = perf_mmap_may_split,
+ };
+
+ static int perf_mmap(struct file *file, struct vm_area_struct *vma)
--- /dev/null
+From 9f90d60eb3247b74f31ff599fafc73b6456eedc1 Mon Sep 17 00:00:00 2001
+From: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Date: Sat, 2 Aug 2025 22:55:35 +0200
+Subject: selftests/perf_events: Add a mmap() correctness test
+
+From: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+
+commit 084d2ac4030c5919e85bba1f4af26e33491469cb upstream.
+
+Exercise various mmap(), munmap() and mremap() invocations, which might
+cause a perf buffer mapping to be split or truncated.
+
+To avoid hard coding the perf event and having dependencies on
+architectures and configuration options, scan through event types in sysfs
+and try to open them. On success, try to mmap() and if that succeeds try to
+mmap() the AUX buffer.
+
+In case that no AUX buffer supporting event is found, only test the base
+buffer mapping. If no mappable event is found or permissions are not
+sufficient, skip the tests.
+
+Reserve a PROT_NONE region for both rb and aux tests to allow testing the
+case where mremap unmaps beyond the end of a mapped VMA to prevent it from
+unmapping unrelated mappings.
+
+Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Co-developed-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ tools/testing/selftests/perf_events/.gitignore | 1
+ tools/testing/selftests/perf_events/Makefile | 2
+ tools/testing/selftests/perf_events/mmap.c | 236 +++++++++++++++++++++++++
+ 3 files changed, 238 insertions(+), 1 deletion(-)
+ create mode 100644 tools/testing/selftests/perf_events/mmap.c
+
+--- a/tools/testing/selftests/perf_events/.gitignore
++++ b/tools/testing/selftests/perf_events/.gitignore
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ sigtrap_threads
+ remove_on_exec
++mmap
+--- a/tools/testing/selftests/perf_events/Makefile
++++ b/tools/testing/selftests/perf_events/Makefile
+@@ -2,5 +2,5 @@
+ CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
+ LDFLAGS += -lpthread
+
+-TEST_GEN_PROGS := sigtrap_threads remove_on_exec
++TEST_GEN_PROGS := sigtrap_threads remove_on_exec mmap
+ include ../lib.mk
+--- /dev/null
++++ b/tools/testing/selftests/perf_events/mmap.c
+@@ -0,0 +1,236 @@
++// SPDX-License-Identifier: GPL-2.0-only
++#define _GNU_SOURCE
++
++#include <dirent.h>
++#include <sched.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <unistd.h>
++
++#include <sys/ioctl.h>
++#include <sys/mman.h>
++#include <sys/syscall.h>
++#include <sys/types.h>
++
++#include <linux/perf_event.h>
++
++#include "../kselftest_harness.h"
++
++#define RB_SIZE 0x3000
++#define AUX_SIZE 0x10000
++#define AUX_OFFS 0x4000
++
++#define HOLE_SIZE 0x1000
++
++/* Reserve space for rb, aux with space for shrink-beyond-vma testing. */
++#define REGION_SIZE (2 * RB_SIZE + 2 * AUX_SIZE)
++#define REGION_AUX_OFFS (2 * RB_SIZE)
++
++#define MAP_BASE 1
++#define MAP_AUX 2
++
++#define EVENT_SRC_DIR "/sys/bus/event_source/devices"
++
++FIXTURE(perf_mmap)
++{
++ int fd;
++ void *ptr;
++ void *region;
++};
++
++FIXTURE_VARIANT(perf_mmap)
++{
++ bool aux;
++ unsigned long ptr_size;
++};
++
++FIXTURE_VARIANT_ADD(perf_mmap, rb)
++{
++ .aux = false,
++ .ptr_size = RB_SIZE,
++};
++
++FIXTURE_VARIANT_ADD(perf_mmap, aux)
++{
++ .aux = true,
++ .ptr_size = AUX_SIZE,
++};
++
++static bool read_event_type(struct dirent *dent, __u32 *type)
++{
++ char typefn[512];
++ FILE *fp;
++ int res;
++
++ snprintf(typefn, sizeof(typefn), "%s/%s/type", EVENT_SRC_DIR, dent->d_name);
++ fp = fopen(typefn, "r");
++ if (!fp)
++ return false;
++
++ res = fscanf(fp, "%u", type);
++ fclose(fp);
++ return res > 0;
++}
++
++FIXTURE_SETUP(perf_mmap)
++{
++ struct perf_event_attr attr = {
++ .size = sizeof(attr),
++ .disabled = 1,
++ .exclude_kernel = 1,
++ .exclude_hv = 1,
++ };
++ struct perf_event_attr attr_ok = {};
++ unsigned int eacces = 0, map = 0;
++ struct perf_event_mmap_page *rb;
++ struct dirent *dent;
++ void *aux, *region;
++ DIR *dir;
++
++ self->ptr = NULL;
++
++ dir = opendir(EVENT_SRC_DIR);
++ if (!dir)
++ SKIP(return, "perf not available.");
++
++ region = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
++ ASSERT_NE(region, MAP_FAILED);
++ self->region = region;
++
++ // Try to find a suitable event on this system
++ while ((dent = readdir(dir))) {
++ int fd;
++
++ if (!read_event_type(dent, &attr.type))
++ continue;
++
++ fd = syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0);
++ if (fd < 0) {
++ if (errno == EACCES)
++ eacces++;
++ continue;
++ }
++
++ // Check whether the event supports mmap()
++ rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
++ if (rb == MAP_FAILED) {
++ close(fd);
++ continue;
++ }
++
++ if (!map) {
++ // Save the event in case that no AUX capable event is found
++ attr_ok = attr;
++ map = MAP_BASE;
++ }
++
++ if (!variant->aux)
++ continue;
++
++ rb->aux_offset = AUX_OFFS;
++ rb->aux_size = AUX_SIZE;
++
++ // Check whether it supports a AUX buffer
++ aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
++ MAP_SHARED | MAP_FIXED, fd, AUX_OFFS);
++ if (aux == MAP_FAILED) {
++ munmap(rb, RB_SIZE);
++ close(fd);
++ continue;
++ }
++
++ attr_ok = attr;
++ map = MAP_AUX;
++ munmap(aux, AUX_SIZE);
++ munmap(rb, RB_SIZE);
++ close(fd);
++ break;
++ }
++ closedir(dir);
++
++ if (!map) {
++ if (!eacces)
++ SKIP(return, "No mappable perf event found.");
++ else
++ SKIP(return, "No permissions for perf_event_open()");
++ }
++
++ self->fd = syscall(SYS_perf_event_open, &attr_ok, 0, -1, -1, 0);
++ ASSERT_NE(self->fd, -1);
++
++ rb = mmap(region, RB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, self->fd, 0);
++ ASSERT_NE(rb, MAP_FAILED);
++
++ if (!variant->aux) {
++ self->ptr = rb;
++ return;
++ }
++
++ if (map != MAP_AUX)
++ SKIP(return, "No AUX event found.");
++
++ rb->aux_offset = AUX_OFFS;
++ rb->aux_size = AUX_SIZE;
++ aux = mmap(region + REGION_AUX_OFFS, AUX_SIZE, PROT_READ | PROT_WRITE,
++ MAP_SHARED | MAP_FIXED, self->fd, AUX_OFFS);
++ ASSERT_NE(aux, MAP_FAILED);
++ self->ptr = aux;
++}
++
++FIXTURE_TEARDOWN(perf_mmap)
++{
++ ASSERT_EQ(munmap(self->region, REGION_SIZE), 0);
++ if (self->fd != -1)
++ ASSERT_EQ(close(self->fd), 0);
++}
++
++TEST_F(perf_mmap, remap)
++{
++ void *tmp, *ptr = self->ptr;
++ unsigned long size = variant->ptr_size;
++
++ // Test the invalid remaps
++ ASSERT_EQ(mremap(ptr, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
++ ASSERT_EQ(mremap(ptr + HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
++ ASSERT_EQ(mremap(ptr + size - HOLE_SIZE, HOLE_SIZE, size, MREMAP_MAYMOVE), MAP_FAILED);
++ // Shrink the end of the mapping such that we only unmap past end of the VMA,
++ // which should succeed and poke a hole into the PROT_NONE region
++ ASSERT_NE(mremap(ptr + size - HOLE_SIZE, size, HOLE_SIZE, MREMAP_MAYMOVE), MAP_FAILED);
++
++ // Remap the whole buffer to a new address
++ tmp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
++ ASSERT_NE(tmp, MAP_FAILED);
++
++ // Try splitting offset 1 hole size into VMA, this should fail
++ ASSERT_EQ(mremap(ptr + HOLE_SIZE, size - HOLE_SIZE, size - HOLE_SIZE,
++ MREMAP_MAYMOVE | MREMAP_FIXED, tmp), MAP_FAILED);
++ // Remapping the whole thing should succeed fine
++ ptr = mremap(ptr, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, tmp);
++ ASSERT_EQ(ptr, tmp);
++ ASSERT_EQ(munmap(tmp, size), 0);
++}
++
++TEST_F(perf_mmap, unmap)
++{
++ unsigned long size = variant->ptr_size;
++
++ // Try to poke holes into the mappings
++ ASSERT_NE(munmap(self->ptr, HOLE_SIZE), 0);
++ ASSERT_NE(munmap(self->ptr + HOLE_SIZE, HOLE_SIZE), 0);
++ ASSERT_NE(munmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE), 0);
++}
++
++TEST_F(perf_mmap, map)
++{
++ unsigned long size = variant->ptr_size;
++
++ // Try to poke holes into the mappings by mapping anonymous memory over it
++ ASSERT_EQ(mmap(self->ptr, HOLE_SIZE, PROT_READ | PROT_WRITE,
++ MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
++ ASSERT_EQ(mmap(self->ptr + HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
++ MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
++ ASSERT_EQ(mmap(self->ptr + size - HOLE_SIZE, HOLE_SIZE, PROT_READ | PROT_WRITE,
++ MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0), MAP_FAILED);
++}
++
++TEST_HARNESS_MAIN