From: Pranjal Arya <pranjal.arya@oss.qualcomm.com>
To: Andrew Morton <akpm@linux-foundation.org>,
Uladzislau Rezki <urezki@gmail.com>,
"Liam R. Howlett" <liam@infradead.org>,
Alice Ryhl <aliceryhl@google.com>,
Andrew Ballance <andrewjballance@gmail.com>
Cc: linux-arm-msm@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, maple-tree@lists.infradead.org,
Lorenzo Stoakes <ljs@kernel.org>,
Pranjal Shrivastava <praan@google.com>,
Will Deacon <will@kernel.org>,
Suzuki K Poulose <Suzuki.Poulose@arm.com>,
Neil Armstrong <neil.armstrong@linaro.org>,
Mostafa Saleh <smostafa@google.com>,
Balbir Singh <balbirs@nvidia.com>,
Suren Baghdasaryan <surenb@google.com>,
Marco Elver <elver@google.com>,
Dmitry Vyukov <dvyukov@google.com>,
Alexander Potapenko <glider@google.com>,
Shuah Khan <shuah@kernel.org>, Dev Jain <dev.jain@arm.com>,
Brendan Jackman <jackmanb@google.com>,
Puranjay Mohan <puranjay@kernel.org>,
Santosh Shukla <santosh.shukla@amd.com>,
Wyes Karny <wkarny@gmail.com>,
Pranjal Arya <pranjal.arya@oss.qualcomm.com>,
Sudeep Holla <sudeep.holla@kernel.org>
Subject: [PATCH RFC 12/12] mm/vmalloc: harden bump-allocator alloc/free against UBSAN array bounds
Date: Sat, 13 Jun 2026 22:49:54 +0530 [thread overview]
Message-ID: <20260613-vmalloc_maple-v1-12-0aa740bb944b@oss.qualcomm.com> (raw)
In-Reply-To: <20260613-vmalloc_maple-v1-0-0aa740bb944b@oss.qualcomm.com>
Real-hardware testing on a Snapdragon X1E80100 exposed a panic during
boot-time module loading via finit_module -> kernel_read_file ->
vmalloc:
Internal error: UBSAN: array index out of bounds
Call trace:
vmap_bump_alloc
alloc_vmap_area
__vmalloc_node_range_noprof
vmalloc_noprof
kernel_read_file
__arm64_sys_finit_module
UBSAN's array-bounds sanitiser triggers on the indexed write loop:
for (i = 0; i < n_pages; i++)
chunk->page_va[idx + i] = va;
Harden the bump path:
- Centralise the eligibility predicate in vmap_bump_eligible() and
add it to alloc_vmap_area() so vmap_bump_refill() is only called
for requests the bump path can actually serve. Add PAGE_ALIGNED(size)
and align > 0 to the predicate (defensive; alloc_vmap_area's
callers always satisfy these but the explicit check is cheap and
prevents the trap path from being entered with bad inputs).
- In vmap_bump_alloc(), use check_add_overflow() for the new bump
pointer, validate aligned >= chunk->base (defensive against
metadata corruption), and bounds-check idx and idx + n_pages
against VMAP_BUMP_CHUNK_PAGES before touching page_va[]. Replace
the indexed page_va[] store loop with a pointer walk:
slot = &chunk->page_va[idx];
for (i = n_pages; i > 0; i--)
*slot++ = va;
The pointer-increment form is not subject to the array-bounds
sanitiser instrumentation that fires on chunk->page_va[idx + i].
- In vmap_bump_unlink(), validate n_pages > 0 and n_pages <=
VMAP_BUMP_CHUNK_PAGES - idx before the memset, so a corrupted
va->va_end cannot drive a write past the end of page_va[].
- Track the chunk's owner CPU at refill time and compare against
per_cpu(vmap_bump_cur, owner_cpu) on unlink. The previous
this_cpu_read(vmap_bump_cur) compared the chunk against the
*current* CPU's chunk, which is wrong when free runs on a CPU
other than the chunk owner: it could either retire a chunk that
is still the owner's current, or skip retirement on a chunk that
has already been replaced.
No semantic change to the bump-path policy or to the addresses
returned. Builds clean on x86_64 and arm64 (full bzImage / Image).
Signed-off-by: Pranjal Arya <pranjal.arya@oss.qualcomm.com>
---
mm/vmalloc.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 49 insertions(+), 13 deletions(-)
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 6991054e1cba..03f10b6b815c 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -2508,6 +2508,7 @@ struct vmap_bump_chunk {
unsigned long limit;
unsigned long bump;
atomic_t alloced; /* # outstanding pages */
+ int owner_cpu;
struct list_head link; /* on vmap_bump_chunks */
struct rcu_head rcu; /* deferred free */
struct vmap_area *page_va[VMAP_BUMP_CHUNK_PAGES];
@@ -2517,6 +2518,16 @@ static DEFINE_PER_CPU(struct vmap_bump_chunk *, vmap_bump_cur);
static LIST_HEAD(vmap_bump_chunks);
static DEFINE_SPINLOCK(vmap_bump_chunks_lock);
+static __always_inline bool
+vmap_bump_eligible(unsigned long size, unsigned long align,
+ unsigned long vstart, unsigned long vend)
+{
+ return vstart == VMALLOC_START && vend == VMALLOC_END &&
+ size > 0 && PAGE_ALIGNED(size) &&
+ size <= VMAP_BUMP_CHUNK_SIZE / 2 &&
+ align > 0 && align <= VMAP_BUMP_CHUNK_SIZE / 2;
+}
+
/*
* Coarse [lo, hi) bounds covering every active vmap_bump_chunk's
* range. vmap_chunk_lookup() rejects out-of-range addresses (e.g.
@@ -2582,11 +2593,10 @@ vmap_bump_alloc(unsigned long size, unsigned long align,
{
struct vmap_bump_chunk *chunk;
struct vmap_area *va;
- unsigned long aligned, idx, n_pages, i;
+ struct vmap_area **slot;
+ unsigned long aligned, new_bump, idx, n_pages, i;
- if (vstart != VMALLOC_START || vend != VMALLOC_END ||
- size == 0 || size > VMAP_BUMP_CHUNK_SIZE / 2 ||
- align > VMAP_BUMP_CHUNK_SIZE / 2)
+ if (!vmap_bump_eligible(size, align, vstart, vend))
return NULL;
va = kmem_cache_alloc_node(vmap_area_cachep, gfp_mask, node);
@@ -2607,22 +2617,34 @@ vmap_bump_alloc(unsigned long size, unsigned long align,
kmem_cache_free(vmap_area_cachep, va);
return NULL;
}
+
aligned = ALIGN(chunk->bump, align);
- if (aligned + size > chunk->limit) {
+ if (aligned < chunk->base ||
+ check_add_overflow(aligned, size, &new_bump) ||
+ new_bump > chunk->limit) {
preempt_enable();
kmem_cache_free(vmap_area_cachep, va);
return NULL;
}
- chunk->bump = aligned + size;
+
idx = vmap_chunk_page_idx(chunk, aligned);
n_pages = size >> PAGE_SHIFT;
- for (i = 0; i < n_pages; i++)
- chunk->page_va[idx + i] = va;
+ if (unlikely(idx >= VMAP_BUMP_CHUNK_PAGES ||
+ n_pages > VMAP_BUMP_CHUNK_PAGES - idx)) {
+ preempt_enable();
+ kmem_cache_free(vmap_area_cachep, va);
+ return NULL;
+ }
+
+ chunk->bump = new_bump;
+ slot = &chunk->page_va[idx];
+ for (i = n_pages; i > 0; i--)
+ *slot++ = va;
atomic_add(n_pages, &chunk->alloced);
preempt_enable();
va->va_start = aligned;
- va->va_end = aligned + size;
+ va->va_end = new_bump;
va->vm = NULL;
/*
* Encode the destination vmap_node so the existing per-node pool
@@ -2651,6 +2673,7 @@ vmap_bump_refill(gfp_t gfp_mask)
{
struct vmap_bump_chunk *new_chunk;
unsigned long base;
+ int cpu;
new_chunk = kvzalloc(sizeof(*new_chunk), gfp_mask);
if (!new_chunk)
@@ -2670,6 +2693,7 @@ vmap_bump_refill(gfp_t gfp_mask)
new_chunk->limit = base + VMAP_BUMP_CHUNK_SIZE;
new_chunk->bump = base;
atomic_set(&new_chunk->alloced, 0);
+ new_chunk->owner_cpu = -1;
INIT_LIST_HEAD(&new_chunk->link);
spin_lock(&vmap_bump_chunks_lock);
@@ -2681,6 +2705,8 @@ vmap_bump_refill(gfp_t gfp_mask)
spin_unlock(&vmap_bump_chunks_lock);
preempt_disable();
+ cpu = smp_processor_id();
+ new_chunk->owner_cpu = cpu;
this_cpu_write(vmap_bump_cur, new_chunk);
preempt_enable();
@@ -2699,6 +2725,7 @@ static struct vmap_area *
vmap_bump_unlink(unsigned long addr)
{
struct vmap_bump_chunk *chunk;
+ struct vmap_bump_chunk *owner_cur;
struct vmap_area *va;
unsigned long idx, n_pages;
@@ -2715,6 +2742,8 @@ vmap_bump_unlink(unsigned long addr)
return NULL;
n_pages = (va->va_end - va->va_start) >> PAGE_SHIFT;
+ if (unlikely(!n_pages || n_pages > VMAP_BUMP_CHUNK_PAGES - idx))
+ return NULL;
memset(&chunk->page_va[idx], 0, n_pages * sizeof(va));
/*
@@ -2725,8 +2754,12 @@ vmap_bump_unlink(unsigned long addr)
* TLB entries until the next lazy-purge flush, so reusing them
* before the flush is unsafe. Forward-only bump avoids that.
*/
+ if (unlikely(chunk->owner_cpu < 0 || chunk->owner_cpu >= nr_cpu_ids))
+ return va;
+
+ owner_cur = READ_ONCE(per_cpu(vmap_bump_cur, chunk->owner_cpu));
if (atomic_sub_return(n_pages, &chunk->alloced) == 0 &&
- chunk != this_cpu_read(vmap_bump_cur)) {
+ chunk != owner_cur) {
spin_lock(&vmap_bump_chunks_lock);
list_del_rcu(&chunk->link);
spin_unlock(&vmap_bump_chunks_lock);
@@ -2781,11 +2814,14 @@ static struct vmap_area *alloc_vmap_area(unsigned long size,
* find_unlink_vmap_area() consult vmap_chunk_lookup() before
* falling back to busy.mt.
*/
- va = vmap_bump_alloc(size, align, vstart, vend, gfp_mask, node,
- va_flags);
- if (!va && vmap_bump_refill(gfp_mask) == 0)
+ va = NULL;
+ if (vmap_bump_eligible(size, align, vstart, vend)) {
va = vmap_bump_alloc(size, align, vstart, vend, gfp_mask, node,
va_flags);
+ if (!va && vmap_bump_refill(gfp_mask) == 0)
+ va = vmap_bump_alloc(size, align, vstart, vend, gfp_mask,
+ node, va_flags);
+ }
if (va) {
if (vm) {
vm->addr = (void *)va->va_start;
--
2.34.1
next prev parent reply other threads:[~2026-06-13 17:22 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-13 17:19 [PATCH RFC 00/12] mm/vmalloc: migrate vmap_area indexing from rb-tree to maple-tree Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 01/12] mm/vmalloc: introduce maple_tree-based indexing for vmap_area Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 02/12] mm/vmalloc: convert allocation-side gap finding and insertion to maple_tree Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 03/12] mm/vmalloc: convert free, purge, and pcpu paths " Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 04/12] mm/vmalloc: finalize maple-only indexing and shrink struct vmap_area Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 05/12] mm/vmalloc: tighten failure handling under memory pressure Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 06/12] mm/vmalloc: tighten alloc/free hot paths Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 07/12] mm/vmalloc: consolidate occupied tree as authoritative index on hot path Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 08/12] mm/vmalloc: track lazy-purge queue as a list_head Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 09/12] mm/vmalloc: collapse busy-tree find-then-unlink into a single mas_erase Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 10/12] mm/vmalloc: per-CPU caching of free ranges from the maple_tree allocator Pranjal Arya
2026-06-13 17:19 ` [PATCH RFC 11/12] mm/vmalloc: O(1) lookup of cached vmap_areas with bounded fast-reject Pranjal Arya
2026-06-13 17:19 ` Pranjal Arya [this message]
2026-06-13 23:15 ` [PATCH RFC 00/12] mm/vmalloc: migrate vmap_area indexing from rb-tree to maple-tree Matthew Wilcox
2026-06-14 6:35 ` [syzbot ci] " syzbot ci
2026-06-14 6:58 ` [PATCH RFC 00/12] " Uladzislau Rezki
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260613-vmalloc_maple-v1-12-0aa740bb944b@oss.qualcomm.com \
--to=pranjal.arya@oss.qualcomm.com \
--cc=Suzuki.Poulose@arm.com \
--cc=akpm@linux-foundation.org \
--cc=aliceryhl@google.com \
--cc=andrewjballance@gmail.com \
--cc=balbirs@nvidia.com \
--cc=dev.jain@arm.com \
--cc=dvyukov@google.com \
--cc=elver@google.com \
--cc=glider@google.com \
--cc=jackmanb@google.com \
--cc=liam@infradead.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=maple-tree@lists.infradead.org \
--cc=neil.armstrong@linaro.org \
--cc=praan@google.com \
--cc=puranjay@kernel.org \
--cc=santosh.shukla@amd.com \
--cc=shuah@kernel.org \
--cc=smostafa@google.com \
--cc=sudeep.holla@kernel.org \
--cc=surenb@google.com \
--cc=urezki@gmail.com \
--cc=will@kernel.org \
--cc=wkarny@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox