From: Artem Lytkin <iprintercanon@gmail.com>
To: linux-mm@kvack.org
Cc: akpm@linux-foundation.org, urezki@gmail.com, willy@infradead.org,
shivamkalra98@zohomail.in, linux-kernel@vger.kernel.org
Subject: [PATCH v3] mm/vmalloc: make vm_struct.nr_pages an unsigned long
Date: Thu, 30 Jul 2026 20:11:42 +0300 [thread overview]
Message-ID: <20260730171142.76817-1-iprintercanon@gmail.com> (raw)
In-Reply-To: <20260730090628.65814-1-iprintercanon@gmail.com>
vm_struct::nr_pages is an unsigned int, and the file keeps deriving byte
counts from it as nr_pages << PAGE_SHIFT. A shift is evaluated in the type
of its promoted left operand, so those are 32-bit arithmetic and wrap at
4 GiB of bytes, which is 2^20 pages. Every site depends on a cast being
remembered; vmap() has one, two recent commits did not. vread_iter() then
computes a size of zero for a 4 GiB VM_ALLOC area and /proc/kcore returns
it as zeros while reporting a successful read, which drgn, crash or gdb
cannot tell from real memory, and the vrealloc() grow-in-place check
declines a request that would have fit.
Widen the field so the class of bug goes away instead of one site at a
time. Everything feeding or consuming it widens too: vm_area_alloc_pages()
and its accumulators, nr_small_pages, new_nr_pages and old_nr_pages, the
index range of vm_area_free_pages(), and three page indexes that were
plain int. Five casts go. Two prints needed fixing as well, %u in
vmalloc_dump_obj() and %d for the unsigned field in vmalloc_info_show().
No bug report behind this, I found it reading the code. The 4 GiB wrap
needs only a machine with over 4 GiB of memory. Neither larger threshold
is a practical concern: 2^32 pages, where the field itself truncates, is
16 TiB and beyond what hardware can populate, and 2^31, where the plain
int indexes break, is 8 TiB and larger than anything in the tree asks for.
The int *nr cursor in the mapping path is unchanged and is separate work.
Users outside mm/vmalloc.c need no change either; those handing the count
to a narrower parameter cannot drive it near 2^31, and kexec_handover
still caps its own 32-bit ABI field where it did.
sizeof(struct vm_struct) on x86-64 stays 72 bytes with
CONFIG_HAVE_ARCH_HUGE_VMALLOC=n and goes from 72 to 80 with it enabled,
both inside the kmalloc-96 bucket it already comes from.
Fixes: 0bca23804632 ("mm/vmalloc: use physical page count in vread_iter() for VM_ALLOC areas")
Fixes: d57ac904ffdc ("mm/vmalloc: use physical page count for vrealloc() grow-in-place check")
Suggested-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Uladzislau Rezki (Sony) <urezki@gmail.com>
Assisted-by: Claude:claude-fable-5
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
v3:
- collapsed to a single patch. v2 put the two cast fixes ahead of this
one so the series would apply, and this one then undid them; widening
the field fixes both sites by itself (Uladzislau)
- shorter changelog (Uladzislau)
v2: https://lore.kernel.org/linux-mm/20260730090628.65814-1-iprintercanon@gmail.com/
v1: https://lore.kernel.org/linux-mm/20260729175708.7074-1-iprintercanon@gmail.com/
include/linux/vmalloc.h | 2 +-
mm/vmalloc.c | 58 ++++++++++++++++++++---------------------
2 files changed, 29 insertions(+), 31 deletions(-)
diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
index e4d8d0a9f30f9..aed121d729b01 100644
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -62,7 +62,7 @@ struct vm_struct {
#ifdef CONFIG_HAVE_ARCH_HUGE_VMALLOC
unsigned int page_order;
#endif
- unsigned int nr_pages;
+ unsigned long nr_pages;
phys_addr_t phys_addr;
const void *caller;
unsigned long requested_size;
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 26f32949c2f2e..196da8738cf10 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -3404,7 +3404,7 @@ struct vm_struct *remove_vm_area(const void *addr)
static inline void set_area_direct_map(const struct vm_struct *area,
int (*set_direct_map)(struct page *page))
{
- int i;
+ unsigned long i;
/* HUGE_VMALLOC passes small pages to set_direct_map */
for (i = 0; i < area->nr_pages; i++)
@@ -3420,7 +3420,7 @@ static void vm_reset_perms(struct vm_struct *area)
unsigned long start = ULONG_MAX, end = 0;
unsigned int page_order = vm_area_page_order(area);
int flush_dmap = 0;
- int i;
+ unsigned long i;
/*
* Find the start and end range of the direct mappings to make sure that
@@ -3493,10 +3493,10 @@ void vfree_atomic(const void *addr)
* Caller is responsible for unmapping (vunmap_range) and KASAN
* poisoning before calling this.
*/
-static void vm_area_free_pages(struct vm_struct *vm, unsigned int start_idx,
- unsigned int end_idx)
+static void vm_area_free_pages(struct vm_struct *vm, unsigned long start_idx,
+ unsigned long end_idx)
{
- unsigned int i;
+ unsigned long i;
if (!(vm->flags & VM_MAP_PUT_PAGES)) {
for (i = start_idx; i < end_idx; i++)
@@ -3819,12 +3819,12 @@ static inline gfp_t vmalloc_gfp_adjust(gfp_t flags, const bool large)
return flags;
}
-static inline unsigned int
+static inline unsigned long
vm_area_alloc_pages(gfp_t gfp, int nid,
- unsigned int order, unsigned int nr_pages, struct page **pages)
+ unsigned int order, unsigned long nr_pages, struct page **pages)
{
- unsigned int nr_allocated = 0;
- unsigned int nr_remaining = nr_pages;
+ unsigned long nr_allocated = 0;
+ unsigned long nr_remaining = nr_pages;
unsigned int max_attempt_order = MAX_PAGE_ORDER;
struct page *page;
int i;
@@ -3872,7 +3872,7 @@ vm_area_alloc_pages(gfp_t gfp, int nid,
if (!order) {
while (nr_allocated < nr_pages) {
unsigned int nr, nr_pages_request;
- int i;
+ unsigned long i;
/*
* A maximum allowed request is hard-coded and is 100
@@ -3880,7 +3880,7 @@ vm_area_alloc_pages(gfp_t gfp, int nid,
* long preemption off scenario in the bulk-allocator
* so the range is [1:100].
*/
- nr_pages_request = min(100U, nr_pages - nr_allocated);
+ nr_pages_request = min(100UL, nr_pages - nr_allocated);
/* memory allocation should consider mempolicy, we can't
* wrongly use nearest node when nid == NUMA_NO_NODE,
@@ -4026,12 +4026,12 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
unsigned long addr = (unsigned long)area->addr;
unsigned long size = get_vm_area_size(area);
unsigned long array_size;
- unsigned int nr_small_pages = size >> PAGE_SHIFT;
+ unsigned long nr_small_pages = size >> PAGE_SHIFT;
unsigned int page_order;
unsigned int flags;
int ret;
- array_size = (unsigned long)nr_small_pages * sizeof(struct page *);
+ array_size = nr_small_pages * sizeof(struct page *);
/* __GFP_NOFAIL and "noblock" flags are mutually exclusive. */
if (!gfpflags_allow_blocking(gfp_mask))
@@ -4525,7 +4525,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
}
if (size <= old_size) {
- unsigned int new_nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
+ unsigned long new_nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
/* Zero out "freed" memory, potentially for future realloc. */
if (want_init_on_free() || want_init_on_alloc(flags))
@@ -4554,7 +4554,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
!(vm->flags & (VM_FLUSH_RESET_PERMS | VM_USERMAP)) &&
gfp_has_io_fs(flags)) {
unsigned long addr = (unsigned long)kasan_reset_tag(p);
- unsigned int old_nr_pages = vm->nr_pages;
+ unsigned long old_nr_pages = vm->nr_pages;
/*
* Use the node lock to synchronize with concurrent
@@ -4567,16 +4567,13 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
spin_unlock(&vn->busy.lock);
/* Notify kmemleak of the reduced allocation size before unmapping. */
- kmemleak_free_part(
- (void *)addr + ((unsigned long)new_nr_pages
- << PAGE_SHIFT),
- (unsigned long)(old_nr_pages - new_nr_pages)
- << PAGE_SHIFT);
+ kmemleak_free_part((void *)addr +
+ (new_nr_pages << PAGE_SHIFT),
+ (old_nr_pages - new_nr_pages)
+ << PAGE_SHIFT);
- vunmap_range(addr + ((unsigned long)new_nr_pages
- << PAGE_SHIFT),
- addr + ((unsigned long)old_nr_pages
- << PAGE_SHIFT));
+ vunmap_range(addr + (new_nr_pages << PAGE_SHIFT),
+ addr + (old_nr_pages << PAGE_SHIFT));
vm_area_free_pages(vm, new_nr_pages, old_nr_pages);
}
@@ -5400,7 +5397,7 @@ bool vmalloc_dump_obj(void *object)
struct vmap_area *va;
struct vmap_node *vn;
unsigned long addr;
- unsigned int nr_pages;
+ unsigned long nr_pages;
addr = PAGE_ALIGN((unsigned long) object);
vn = addr_to_node(addr);
@@ -5420,7 +5417,7 @@ bool vmalloc_dump_obj(void *object)
nr_pages = vm->nr_pages;
spin_unlock(&vn->busy.lock);
- pr_cont(" %u-page vmalloc region starting at %#lx allocated at %pS\n",
+ pr_cont(" %lu-page vmalloc region starting at %#lx allocated at %pS\n",
nr_pages, addr, caller);
return true;
@@ -5438,16 +5435,17 @@ bool vmalloc_dump_obj(void *object)
static void show_numa_info(struct seq_file *m, struct vm_struct *v,
unsigned int *counters)
{
- unsigned int nr;
unsigned int step = 1U << vm_area_page_order(v);
+ unsigned long i;
+ unsigned int nr;
if (!counters)
return;
memset(counters, 0, nr_node_ids * sizeof(unsigned int));
- for (nr = 0; nr < v->nr_pages; nr += step)
- counters[page_to_nid(v->pages[nr])] += step;
+ for (i = 0; i < v->nr_pages; i += step)
+ counters[page_to_nid(v->pages[i])] += step;
for_each_node_state(nr, N_HIGH_MEMORY)
if (counters[nr])
seq_printf(m, " N%u=%u", nr, counters[nr]);
@@ -5505,7 +5503,7 @@ static int vmalloc_info_show(struct seq_file *m, void *p)
seq_printf(m, " %pS", v->caller);
if (v->nr_pages)
- seq_printf(m, " pages=%d", v->nr_pages);
+ seq_printf(m, " pages=%lu", v->nr_pages);
if (v->phys_addr)
seq_printf(m, " phys=%pa", &v->phys_addr);
base-commit: eee677bbc48890b2bcaa42ea7942478302937a09
--
2.43.0
next prev parent reply other threads:[~2026-07-30 17:12 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-29 17:57 [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
2026-07-29 18:28 ` Matthew Wilcox
2026-07-29 21:45 ` Andrew Morton
2026-07-30 9:07 ` Artem Lytkin
2026-07-30 9:06 ` [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages Artem Lytkin
2026-07-30 17:11 ` Artem Lytkin [this message]
2026-07-30 20:09 ` Andrew Morton
2026-07-30 9:06 ` [PATCH v2 1/3] mm/vmalloc: fix 32-bit truncation of the area size in vread_iter() Artem Lytkin
2026-07-30 9:06 ` [PATCH v2 2/3] mm/vmalloc: fix 32-bit truncation in the vrealloc() grow-in-place check Artem Lytkin
2026-07-30 9:06 ` [PATCH v2 3/3] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
2026-07-30 13:04 ` [PATCH] " 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=20260730171142.76817-1-iprintercanon@gmail.com \
--to=iprintercanon@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=shivamkalra98@zohomail.in \
--cc=urezki@gmail.com \
--cc=willy@infradead.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.