[PATCH 1/5] mm/execmem: free ROX cache chunks only when they span an entire vm area

From: Mike Rapoport (Microsoft)

Date: Thu Sep 03 2026 - 13:23:30 EST


When execmem refills the ROX cache, it vmalloc()s multiples of PMD_SIZE
aligned to PMD_SIZE. For every such allocation vmalloc creates a
vm area.

The first part of the vmalloc()ed chunk is returned to the allocation
that triggered the cache refill and the remaining part is added to the
cache and handed out for subsequent allocations with execmem_alloc().

When only the first part is freed, the entire vm area remains in the ROX
cache and can be handed out again.

In the case when the first allocation is larger than PMD_SIZE and the
second allocation from the freed first part of the chunk is exactly
PMD_SIZE, execmem_cache_clean() will free the entire chunk while part of
it is still in use.

For example:

/*
* vmalloc(4M), return p0 to the caller
* add [p0 + 3M, p0 + 4M) to the cache
*/
p0 = execmem_alloc(3M);

/* return p0 + 3M from the cache to the caller */
p1 = execmem_alloc(1M);

/* put [p0, p0 + 3M) back into the cache */
execmem_free(p0);

/* return p0 from the cache to the caller */
p2 = execmem_alloc(2M);

/* return p0 + 2M from the cache to the caller */
p3 = execmem_alloc(1M);

/* bah! execmem_cache_clean() frees the entire 4M chunk */
execmem_free(p2);

Make sure that the ranges that execmem_cache_clean() frees cover the
entire vm area.

Fixes: 2e45474ab14f ("execmem: add support for cache of large ROX pages")
Assisted-by: copilot:claude-opus-5
Signed-off-by: Mike Rapoport (Microsoft) <rppt@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
mm/execmem.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mm/execmem.c b/mm/execmem.c
index ad07cae9ed585..ba277790e3132 100644
--- a/mm/execmem.c
+++ b/mm/execmem.c
@@ -143,9 +143,11 @@ static void execmem_cache_clean(struct work_struct *work)

mutex_lock(mutex);
mas_for_each(&mas, area, ULONG_MAX) {
+ struct vm_struct *vm = find_vm_area(area);
size_t size = mas_range_len(&mas);

- if (IS_ALIGNED(size, PMD_SIZE) &&
+ if (vm && get_vm_area_size(vm) == size &&
+ IS_ALIGNED(size, PMD_SIZE) &&
IS_ALIGNED(mas.index, PMD_SIZE)) {
mas_store_gfp(&mas, NULL, GFP_KERNEL);
vfree(area);

--
2.53.0