All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long
@ 2026-07-29 17:57 Artem Lytkin
  2026-07-29 18:28 ` Matthew Wilcox
                   ` (5 more replies)
  0 siblings, 6 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-29 17:57 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, shivamkalra98, linux-kernel

A vmalloc area can hold more than 2^32 pages, but the page counts
tracking it are 32 bit. That has produced the same truncation bug twice
already, once in vread_iter() and once in the vrealloc() grow-in-place
check, and it keeps the casts that hide it scattered around the file.

Widen the field and everything that feeds or consumes it, so the byte
counts derived from it are computed in 64 bit arithmetic without a cast
at each site:

  - vm_area_alloc_pages() takes and returns a page count, and the result
    is stored into nr_pages, so its parameter, return type and the
    nr_allocated and nr_remaining accumulators widen too. The 100 page
    cap on a bulk request stays narrow, only its literal becomes 100UL.

  - nr_small_pages in __vmalloc_area_node() is derived from a 64 bit
    size and compared against nr_pages, so it widens as well. Before
    this the store truncated for a size of 16 TiB or more, which sized
    area->pages from the wrapped count while __vmap_pages_range() still
    walked the full size. The cast on array_size next to it was already
    redundant and goes away with it.

  - new_nr_pages and old_nr_pages in vrealloc_node_align_noprof() widen,
    which drops the four casts on them, and the casts added by the two
    preceding patches in vrealloc_node_align_noprof() and vread_iter()
    are no longer needed either.

  - vm_area_free_pages() takes a page index range.

Three loop counters that index area->pages were plain int and would have
overflowed at 2^31 pages: in set_area_direct_map(), in vm_reset_perms()
and in the bulk allocation loop, where the index is seeded from
nr_allocated. They become unsigned long.

Printing needed fixing in two places. vmalloc_dump_obj() used %u, and
vmalloc_info_show() printed the unsigned field with %d, which would have
shown a negative page count for an area of 8 TiB or more.
show_numa_info() used a single unsigned int as both the page index and
the node id, so the page loop gets its own unsigned long counter.

The page table walkers are not covered here. vmap_pages_pte_range() and
the levels above it carry the page cursor as int *nr, so a mapping is
still capped independently of the counters this patch widens. Widening
that cursor touches the whole walker and is separate work.

Nothing outside mm/vmalloc.c needs a change to build or to behave as
before. Those users either index the pages array or multiply the count
by PAGE_SIZE, which is unsigned long already. kexec_handover stores the
count into a 32 bit field of its own ABI, which caps that interface
exactly where it did before.

On x86-64 this leaves sizeof(struct vm_struct) at 72 bytes when
CONFIG_HAVE_ARCH_HUGE_VMALLOC=n, since the existing padding before
phys_addr absorbs the change, and takes it to 80 when the config is
enabled. Both sizes come from the same kmalloc-96 bucket that
__get_vm_area_node() allocates from, so the footprint per area does not
change either way.

Suggested-by: Andrew Morton <akpm@linux-foundation.org>
Assisted-by: Claude:claude-fable-5
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 include/linux/vmalloc.h |  2 +-
 mm/vmalloc.c            | 62 ++++++++++++++++++++---------------------
 2 files changed, 31 insertions(+), 33 deletions(-)

diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
index d87dc7f77f4e8..a39c20c77efcd 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 baf7e396e3fc7..efd5e42c64e2e 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -3338,7 +3338,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++)
@@ -3354,7 +3354,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
@@ -3427,10 +3427,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++)
@@ -3642,12 +3642,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;
@@ -3695,7 +3695,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
@@ -3703,7 +3703,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,
@@ -3849,12 +3849,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))
@@ -4352,7 +4352,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))
@@ -4381,7 +4381,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
@@ -4394,16 +4394,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);
 		}
@@ -4415,7 +4412,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
 	/*
 	 * We already have the bytes available in the allocation; use them.
 	 */
-	if (size <= (unsigned long)vm->nr_pages << PAGE_SHIFT) {
+	if (size <= vm->nr_pages << PAGE_SHIFT) {
 		/*
 		 * No need to zero memory here, as unused memory will have
 		 * already been zeroed at initial allocation time or during
@@ -4722,7 +4719,7 @@ long vread_iter(struct iov_iter *iter, const char *addr, size_t count)
 			 * mapping types (vmap, ioremap) don't set nr_pages.
 			 */
 			size = (vm->flags & VM_ALLOC && vm->nr_pages) ?
-				       ((unsigned long)vm->nr_pages << PAGE_SHIFT) :
+				       (vm->nr_pages << PAGE_SHIFT) :
 				       get_vm_area_size(vm);
 		else
 			size = va_size(va);
@@ -5228,7 +5225,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);
@@ -5248,7 +5245,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;
@@ -5266,16 +5263,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]);
@@ -5333,7 +5331,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: 248951ddc14de84de3910f9b13f51491a8cd91df
prerequisite-patch-id: bc462c518eb9ddcaccad5e1f5fed7cc24512b117
prerequisite-patch-id: 891688c2e93c570d1c9c7802b46aaba974002684
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  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
                   ` (4 subsequent siblings)
  5 siblings, 2 replies; 11+ messages in thread
From: Matthew Wilcox @ 2026-07-29 18:28 UTC (permalink / raw)
  To: Artem Lytkin; +Cc: linux-mm, akpm, urezki, shivamkalra98, linux-kernel

On Wed, Jul 29, 2026 at 08:57:08PM +0300, Artem Lytkin wrote:
> A vmalloc area can hold more than 2^32 pages, but the page counts

I had a "wait, what, really?" moment.  And it can!  2^32 pages is 16TiB,
and x86-64 says:

   ffffc90000000000 |  -55    TB | ffffe8ffffffffff |   32 TB | vmalloc/ioremap space (vmalloc_base)

So we can have _one_ allocation that is more than 2^32 pages.  But I'm
not sure where we'll get the memory to populate it from.
https://blogs.oracle.com/cloud-infrastructure/announcing-oci-compute-e6-acceleron-instances
says I can get 2.3TB of memory in a single machine, which is a factor of
8 away from where we'd need to be to actually hit this.

Intel are adveertising a max memory of 1.5TiB on the 6990E+ (128GiB *
12 channels).  So if you put 8 Xeons in a box and loaded it up, that
could get you to 12TiB.

I'm not saying we shouldn't fix this, but it's all theoretical for now,
right?


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  2026-07-29 18:28 ` Matthew Wilcox
@ 2026-07-29 21:45   ` Andrew Morton
  2026-07-30  9:07   ` Artem Lytkin
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Morton @ 2026-07-29 21:45 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Artem Lytkin, linux-mm, urezki, shivamkalra98, linux-kernel

On Wed, 29 Jul 2026 19:28:36 +0100 Matthew Wilcox <willy@infradead.org> wrote:

> On Wed, Jul 29, 2026 at 08:57:08PM +0300, Artem Lytkin wrote:
> > A vmalloc area can hold more than 2^32 pages, but the page counts
> 
> I had a "wait, what, really?" moment.  And it can!  2^32 pages is 16TiB,
> and x86-64 says:
> 
>    ffffc90000000000 |  -55    TB | ffffe8ffffffffff |   32 TB | vmalloc/ioremap space (vmalloc_base)
> 
> So we can have _one_ allocation that is more than 2^32 pages.  But I'm
> not sure where we'll get the memory to populate it from.
> https://blogs.oracle.com/cloud-infrastructure/announcing-oci-compute-e6-acceleron-instances
> says I can get 2.3TB of memory in a single machine, which is a factor of
> 8 away from where we'd need to be to actually hit this.
> 
> Intel are adveertising a max memory of 1.5TiB on the 6990E+ (128GiB *
> 12 channels).  So if you put 8 Xeons in a box and loaded it up, that
> could get you to 12TiB.
> 
> I'm not saying we shouldn't fix this, but it's all theoretical for now,
> right?

Artem said "That has produced the same truncation bug twice already,
once in vread_iter() and once in the vrealloc() grow-in-place check". 
This might imply that the issue has been hit in practice, unclear.

Anyway, it's a good change so let's do it.

Sashiko wasn't able to apply it, and the mechanical checking would be
useful here.  I was able to get it to apply, took a bit of work.

So Artem, can you please update the changelog to clarify the above,
redo against mm.git's mm-new branch (or linux-next) and send us a v2?

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages
  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-30  9:06 ` Artem Lytkin
  2026-07-30 17:11   ` [PATCH v3] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
  2026-07-30 20:09   ` [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages 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
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30  9:06 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, willy, shivamkalra98, linux-kernel

Andrew asked for a v2 of the nr_pages widening, rebased on mm-new, with
the changelog clarified on what has actually been observed.

The widening reverts the two casts added by the patches I sent on 25
July, so it needs those in the tree first, and they are not in mm-new,
mm-unstable or mm-hotfixes-unstable today. They are here as 1/3 and 2/3 so
that the series applies and Sashiko can chew on it. 2/3 is byte for byte
what I sent; 1/3 has one corrected sentence in its changelog and the same
one-line diff.

Keeping the three separate is deliberate. 1/3 is a one-line fix for a
v7.2 regression and carries a Fixes: tag, so it is the part that wants to
go in on its own. 3/3 is a type change across the file.

On what has been seen in practice, since v1 of 3/3 read as though the bug
had been hit: it has not. There is no report behind any of this, I found
it reading the code. What is reachable is the 4 GiB truncation in
vread_iter(), which needs a machine with more than 4 GiB of memory and
nothing else, and makes /proc/kcore return zeros for such an area while
reporting a successful read. The vrealloc() case has the same cause but
stays latent, no in-tree caller grows an allocation that far. The rest is
unexercised, and 3/3 now separates the two thresholds instead of lumping
them together: 2^32 pages, where the field truncates, is 16 TiB and out
of reach of any hardware, while 2^31, where the plain int page indexes
break, is 8 TiB and only out of reach because nothing asks for an area
that large.

Changes in v2:
 - rebased on mm-new
 - 3/3: the changelog separates what is reachable, what is latent and what
   is merely unexercised, and answers the 16 TiB question directly
 - 3/3: the note on the mapping path no longer calls the int *nr cursor a
   cap, since it is an overflow and not a bound, and no longer claims that
   every user outside mm/vmalloc.c only indexes or shifts the count. Three
   of them pass it to a narrower parameter, which is now named
 - 1/3: dropped the claim that alloc_large_system_hash() cannot reach a
   4 GiB area. Its cap is a sixteenth of memory, so it can on a 64 GiB
   machine, just not without an explicit table size on the command line

v1 of 3/3:
https://lore.kernel.org/linux-mm/20260729175708.7074-1-iprintercanon@gmail.com/
1/3 and 2/3 as first posted:
https://lore.kernel.org/linux-mm/20260725132201.88279-1-iprintercanon@gmail.com/

Checked on x86-64: vmlinux and modules build clean, and mm/vmalloc.c plus
every file that touches vm_struct::nr_pages are clean at W=1, with
HAVE_ARCH_HUGE_VMALLOC, NUMA, KEXEC_HANDOVER, DEBUG_KMEMLEAK,
DMA_API_DEBUG and PROC_KCORE enabled. sizeof(struct vm_struct) measured
against the real headers: 72 bytes before and after with
HAVE_ARCH_HUGE_VMALLOC=n, 72 to 80 with it enabled, both sizes inside the
kmalloc-96 bucket that __get_vm_area_node() already allocates from.

Artem Lytkin (3):
  mm/vmalloc: fix 32-bit truncation of the area size in vread_iter()
  mm/vmalloc: fix 32-bit truncation in the vrealloc() grow-in-place
    check
  mm/vmalloc: make vm_struct.nr_pages an unsigned long

 include/linux/vmalloc.h |  2 +-
 mm/vmalloc.c            | 58 ++++++++++++++++++++---------------------
 2 files changed, 29 insertions(+), 31 deletions(-)


base-commit: eee677bbc48890b2bcaa42ea7942478302937a09
-- 
2.43.0



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 1/3] mm/vmalloc: fix 32-bit truncation of the area size in vread_iter()
  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-30  9:06 ` [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages Artem Lytkin
@ 2026-07-30  9:06 ` 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
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30  9:06 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, willy, shivamkalra98, linux-kernel

Commit 0bca23804632 ("mm/vmalloc: use physical page count in
vread_iter() for VM_ALLOC areas") replaced get_vm_area_size(vm), which
returns a size_t, with vm->nr_pages << PAGE_SHIFT.

struct vm_struct::nr_pages is an unsigned int. The shift operator does
not perform the usual arithmetic conversions: the integer promotions are
applied to each operand and the type of the result is that of the
promoted left operand. The expression is therefore evaluated in 32-bit
arithmetic no matter how PAGE_SHIFT is typed, and no matter that the
result is assigned to a size_t. Once an area reaches 4 GiB the byte
count wraps, at 1 << 20 pages with 4 KiB pages, 1 << 18 with 16 KiB and
1 << 16 with 64 KiB.

nr_pages counts base pages even for huge vmalloc mappings, since
__vmalloc_area_node() requires area->nr_pages == nr_small_pages, so a
huge page_order does not raise the threshold.

The only limit on the size of a single area is the totalram_pages()
check in __vmalloc_node_range_noprof(), so any machine with more than
4 GiB of memory can create an affected area. One example is the zram
metadata table, which is a single vzalloc() of disksize / 256 with
CONFIG_LOCKDEP=n: "echo 1T > /sys/block/zram0/disksize" allocates
exactly 4 GiB and needs no 1 TiB of anything. Sufficiently large BPF
array or prealloc-hash maps do it too, as does
"modprobe test_vmalloc run_test_mask=1 nr_pages=1048576". The KVM
dirty bitmap is one path that might be expected to reach it but cannot,
being bounded by KVM_MEM_MAX_NR_PAGES to 512 MiB.
alloc_large_system_hash() can reach it, but not on its own: it caps a
table at a sixteenth of memory, which is 4 GiB once a machine has
64 GiB, and the automatic sizing stays orders of magnitude below that
cap, so it takes an explicit table size on the command line.

The effect is confined to /proc/kcore, the only caller. For an area
whose size is an exact multiple of 4 GiB the computed size becomes 0,
the

	if (addr >= vaddr + size)
		goto next_va;

test succeeds and the whole area is skipped; for other sizes only the
first nr_pages mod 2^20 pages are read and the rest is skipped. Either
way the bytes are zero-filled at the finished_zero label, which returns
the full requested length, so read_kcore_iter() sees a successful read
and userspace gets neither an error nor a short read. Live inspection
with drgn, crash or gdb silently observes zeros where the area is
populated, and cannot distinguish that from genuinely zeroed memory.
Before the offending commit the size came from vm->size in 64-bit
arithmetic, so this is a v7.2 regression.

The truncated value is always less than or equal to the true size, so
vread_iter() can only under-read; there is no out-of-bounds access. Both
the affected reader and every producer above are privileged: opening
/proc/kcore requires CAP_SYS_RAWIO and is refused under lockdown. This
is a correctness and debuggability problem, not a security one.

Fix it by widening the shift, which also makes the expression consistent
with the four (unsigned long)nr_pages << PAGE_SHIFT expressions in
vrealloc_node_align_noprof().

On 32-bit a widening cast cannot help, size_t being 32 bits there as
well, but a 4 GiB vmalloc area is not reachable on 32-bit either. On
64-bit the cast removes the truncation entirely, which is why replacing
get_vm_area_size() introduced a regression rather than inheriting a
pre-existing wart.

Fixes: 0bca23804632 ("mm/vmalloc: use physical page count in vread_iter() for VM_ALLOC areas")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 mm/vmalloc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 26f32949c2f2e..34e10b825889a 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -4895,7 +4895,7 @@ long vread_iter(struct iov_iter *iter, const char *addr, size_t count)
 			 * mapping types (vmap, ioremap) don't set nr_pages.
 			 */
 			size = (vm->flags & VM_ALLOC && vm->nr_pages) ?
-				       (vm->nr_pages << PAGE_SHIFT) :
+				       ((unsigned long)vm->nr_pages << PAGE_SHIFT) :
 				       get_vm_area_size(vm);
 		else
 			size = va_size(va);
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/3] mm/vmalloc: fix 32-bit truncation in the vrealloc() grow-in-place check
  2026-07-29 17:57 [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
                   ` (2 preceding siblings ...)
  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 ` 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
  5 siblings, 0 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30  9:06 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, willy, shivamkalra98, linux-kernel

Commit d57ac904ffdc ("mm/vmalloc: use physical page count for vrealloc()
grow-in-place check") introduced

	if (size <= vm->nr_pages << PAGE_SHIFT) {

in place of a comparison against a size_t. As vm->nr_pages is an
unsigned int and a shift is evaluated in the type of its promoted left
operand, the right hand side is computed in 32-bit arithmetic and wraps
for areas of 4 GiB or more. Twelve lines above, in the same function,
four expressions cast for exactly this reason:

	kmemleak_free_part((void *)addr + ((unsigned long)new_nr_pages
					   << PAGE_SHIFT), ...);

The consequence here is benign. Truncation only ever makes the right
hand side smaller than the real capacity, so the test can only fail when
it should have succeeded: vrealloc() then falls through to need_realloc,
allocates a new area, copies min(size, old_size) bytes and frees the old
one. That is correct, only wasteful, and no in-tree caller currently
grows an allocation past 4 GiB, so this is a latent fix rather than a
user-visible one.

Widen the shift to match the surrounding code. Note that comparing page
counts instead, as in PAGE_ALIGN(size) >> PAGE_SHIFT <= vm->nr_pages,
would avoid the cast but introduce a real bug: PAGE_ALIGN() wraps to 0
for sizes within a page of ULONG_MAX and the test would then wrongly
succeed.

Fixes: d57ac904ffdc ("mm/vmalloc: use physical page count for vrealloc() grow-in-place check")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 mm/vmalloc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 34e10b825889a..bf9e32cf93fc9 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -4588,7 +4588,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
 	/*
 	 * We already have the bytes available in the allocation; use them.
 	 */
-	if (size <= vm->nr_pages << PAGE_SHIFT) {
+	if (size <= (unsigned long)vm->nr_pages << PAGE_SHIFT) {
 		/*
 		 * No need to zero memory here, as unused memory will have
 		 * already been zeroed at initial allocation time or during
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/3] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  2026-07-29 17:57 [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
                   ` (3 preceding siblings ...)
  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 ` Artem Lytkin
  2026-07-30 13:04 ` [PATCH] " Uladzislau Rezki
  5 siblings, 0 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30  9:06 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, willy, shivamkalra98, linux-kernel

vm_struct::nr_pages is an unsigned int, and the file repeatedly derives a
byte count from it as nr_pages << PAGE_SHIFT. A shift is evaluated in the
type of its promoted left operand, so those expressions are 32-bit
arithmetic and wrap at 4 GiB of bytes, which is only 2^20 pages with
4 KiB pages. Every such site therefore depends on someone remembering a
cast. vmap() has one. The two commits fixed by the preceding two patches
did not.

v1 of this patch was ambiguous about how much of that has been seen in
practice, so to be explicit: none of it comes from a bug report, the
series comes out of reading the code. What is reachable on current
hardware is the 4 GiB wrap. A single vzalloc() of 4 GiB needs a machine
with more than 4 GiB of memory and nothing else, and /proc/kcore then
reads such an area as zeros while reporting a successful read. The
vrealloc() case has the same cause, but no in-tree caller grows an
allocation that far, so it stays latent. The two thresholds above that
are not equally far away. A count above 2^32, where the field itself
truncates, is out of reach: that is 16 TiB, and as Matthew Wilcox notes,
current hardware cannot populate it, topping out near 12 TiB with eight
fully loaded sockets. The 2^31 point, where the plain int page indexes
below stop working, is 8 TiB, which such a machine could in principle
hand to one allocation, since the only size gate on a request is the
totalram_pages() check in __vmalloc_node_range_noprof(). No in-tree caller
comes near it, so that one is unexercised rather than impossible.

Widening the field is still worth doing, because it retires the 4 GiB
class of bug at the type level instead of one call site at a time, and
the casts go with it.

Widen the field and everything that feeds or consumes it:

  - vm_area_alloc_pages() takes and returns a page count that is stored
    into nr_pages, so its parameter, its return type and the nr_allocated
    and nr_remaining accumulators widen with it. The 100 page cap on a
    bulk request stays narrow, only its literal becomes 100UL.

  - nr_small_pages in __vmalloc_area_node() is derived from a 64-bit size
    and compared against nr_pages, so it widens too. Before this the
    store truncated at 16 TiB, which sized area->pages from the wrapped
    count while the mapping still covered the full size. The cast on
    array_size next to it was already redundant and goes away with it.

  - new_nr_pages and old_nr_pages in vrealloc_node_align_noprof() widen,
    which drops four casts, and the casts added by the two preceding
    patches are no longer needed either.

  - vm_area_free_pages() takes a page index range.

Three counters that index area->pages were plain int and would have
overflowed at 2^31 pages: in set_area_direct_map(), in vm_reset_perms()
and in the bulk allocation loop, where the index is seeded from
nr_allocated. They become unsigned long.

Two prints needed fixing. vmalloc_dump_obj() used %u, and
vmalloc_info_show() printed the unsigned field with %d, which would have
shown a negative page count. show_numa_info() used a single unsigned int
as both the page index and the node id, so the page loop gets its own
counter.

The mapping path is deliberately left alone. vmap_pages_pte_range() and
the levels above it carry the page cursor as int *nr, seeded in
vmap_pages_range_noflush_walk(). That is not a cap: past 2^31 base pages
it overflows and indexes area->pages with a negative value. It is also
not made worse here, because an unsigned int nr_pages already spanned
2^31 to 2^32-1, and nothing about a request's size was ever gated on the
width of that cursor. Widening it touches every level of the walker and
is separate work.

Nothing outside mm/vmalloc.c needs a change to build or to behave as
before. Most of those users index the pages array or shift the count by
PAGE_SHIFT, which is unsigned long arithmetic either way. Three hand it
to a narrower parameter: set_memory_rox() in
execmem_cache_populate_alloc() and release_pages() in
free_mod_tags_mem() take an int page count, and clear_pages() in
alloc_thread_stack_node() takes an unsigned int. Two of those already
narrowed an unsigned int, and none of the three can approach 2^31: a
thread stack is THREAD_SIZE / PAGE_SIZE pages, and an execmem block and
the module tag area are both far smaller. kexec_handover stores the count
into a 32-bit field of its own ABI, which caps that interface exactly
where it did before.

On x86-64 sizeof(struct vm_struct) stays 72 bytes with
CONFIG_HAVE_ARCH_HUGE_VMALLOC=n, since the existing padding before
phys_addr absorbs the change, and becomes 80 with it enabled. Both sizes
come from the same kmalloc-96 bucket that __get_vm_area_node() allocates
from, so the footprint per area does not change either way.

Suggested-by: Andrew Morton <akpm@linux-foundation.org>
Assisted-by: Claude:claude-fable-5
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 include/linux/vmalloc.h |  2 +-
 mm/vmalloc.c            | 62 ++++++++++++++++++++---------------------
 2 files changed, 31 insertions(+), 33 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 bf9e32cf93fc9..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);
 		}
@@ -4588,7 +4585,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
 	/*
 	 * We already have the bytes available in the allocation; use them.
 	 */
-	if (size <= (unsigned long)vm->nr_pages << PAGE_SHIFT) {
+	if (size <= vm->nr_pages << PAGE_SHIFT) {
 		/*
 		 * No need to zero memory here, as unused memory will have
 		 * already been zeroed at initial allocation time or during
@@ -4895,7 +4892,7 @@ long vread_iter(struct iov_iter *iter, const char *addr, size_t count)
 			 * mapping types (vmap, ioremap) don't set nr_pages.
 			 */
 			size = (vm->flags & VM_ALLOC && vm->nr_pages) ?
-				       ((unsigned long)vm->nr_pages << PAGE_SHIFT) :
+				       (vm->nr_pages << PAGE_SHIFT) :
 				       get_vm_area_size(vm);
 		else
 			size = va_size(va);
@@ -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);
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  2026-07-29 18:28 ` Matthew Wilcox
  2026-07-29 21:45   ` Andrew Morton
@ 2026-07-30  9:07   ` Artem Lytkin
  1 sibling, 0 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30  9:07 UTC (permalink / raw)
  To: Matthew Wilcox; +Cc: linux-mm, akpm, urezki, shivamkalra98, linux-kernel

On Wed, Jul 29, 2026 at 07:28:36PM +0100, Matthew Wilcox wrote:
> I'm not saying we shouldn't fix this, but it's all theoretical for now,
> right?

The 2^32 page part is, yes, and my opening sentence pointed at the wrong
thing.

What went wrong twice wasn't a page count, it was a byte count. nr_pages
is unsigned int, so nr_pages << PAGE_SHIFT is 32-bit arithmetic and wraps
at 4GiB of bytes, which is 2^20 pages. In vread_iter() that made
/proc/kcore hand back zeros for a 4GiB area as a successful read, and any
machine with more than 4GiB of memory can allocate one. So the reachable
part is 4GiB and the 16TiB framing was me leading with the wrong number.

To answer the other half of it, and Andrew's question too: no report
behind any of this, I found it reading the code. v2 says so in as many
words.

Artem


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  2026-07-29 17:57 [PATCH] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
                   ` (4 preceding siblings ...)
  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 ` Uladzislau Rezki
  5 siblings, 0 replies; 11+ messages in thread
From: Uladzislau Rezki @ 2026-07-30 13:04 UTC (permalink / raw)
  To: Artem Lytkin; +Cc: linux-mm, akpm, urezki, shivamkalra98, linux-kernel

On Wed, Jul 29, 2026 at 08:57:08PM +0300, Artem Lytkin wrote:
> A vmalloc area can hold more than 2^32 pages, but the page counts
> tracking it are 32 bit. That has produced the same truncation bug twice
> already, once in vread_iter() and once in the vrealloc() grow-in-place
> check, and it keeps the casts that hide it scattered around the file.
> 
> Widen the field and everything that feeds or consumes it, so the byte
> counts derived from it are computed in 64 bit arithmetic without a cast
> at each site:
> 
>   - vm_area_alloc_pages() takes and returns a page count, and the result
>     is stored into nr_pages, so its parameter, return type and the
>     nr_allocated and nr_remaining accumulators widen too. The 100 page
>     cap on a bulk request stays narrow, only its literal becomes 100UL.
> 
>   - nr_small_pages in __vmalloc_area_node() is derived from a 64 bit
>     size and compared against nr_pages, so it widens as well. Before
>     this the store truncated for a size of 16 TiB or more, which sized
>     area->pages from the wrapped count while __vmap_pages_range() still
>     walked the full size. The cast on array_size next to it was already
>     redundant and goes away with it.
> 
>   - new_nr_pages and old_nr_pages in vrealloc_node_align_noprof() widen,
>     which drops the four casts on them, and the casts added by the two
>     preceding patches in vrealloc_node_align_noprof() and vread_iter()
>     are no longer needed either.
> 
>   - vm_area_free_pages() takes a page index range.
> 
> Three loop counters that index area->pages were plain int and would have
> overflowed at 2^31 pages: in set_area_direct_map(), in vm_reset_perms()
> and in the bulk allocation loop, where the index is seeded from
> nr_allocated. They become unsigned long.
> 
> Printing needed fixing in two places. vmalloc_dump_obj() used %u, and
> vmalloc_info_show() printed the unsigned field with %d, which would have
> shown a negative page count for an area of 8 TiB or more.
> show_numa_info() used a single unsigned int as both the page index and
> the node id, so the page loop gets its own unsigned long counter.
> 
> The page table walkers are not covered here. vmap_pages_pte_range() and
> the levels above it carry the page cursor as int *nr, so a mapping is
> still capped independently of the counters this patch widens. Widening
> that cursor touches the whole walker and is separate work.
> 
> Nothing outside mm/vmalloc.c needs a change to build or to behave as
> before. Those users either index the pages array or multiply the count
> by PAGE_SIZE, which is unsigned long already. kexec_handover stores the
> count into a 32 bit field of its own ABI, which caps that interface
> exactly where it did before.
> 
> On x86-64 this leaves sizeof(struct vm_struct) at 72 bytes when
> CONFIG_HAVE_ARCH_HUGE_VMALLOC=n, since the existing padding before
> phys_addr absorbs the change, and takes it to 80 when the config is
> enabled. Both sizes come from the same kmalloc-96 bucket that
> __get_vm_area_node() allocates from, so the footprint per area does not
> change either way.
> 
> Suggested-by: Andrew Morton <akpm@linux-foundation.org>
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
> ---
>  include/linux/vmalloc.h |  2 +-
>  mm/vmalloc.c            | 62 ++++++++++++++++++++---------------------
>  2 files changed, 31 insertions(+), 33 deletions(-)
> 
> diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
> index d87dc7f77f4e8..a39c20c77efcd 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 baf7e396e3fc7..efd5e42c64e2e 100644
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -3338,7 +3338,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++)
> @@ -3354,7 +3354,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
> @@ -3427,10 +3427,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++)
> @@ -3642,12 +3642,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;
> @@ -3695,7 +3695,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
> @@ -3703,7 +3703,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,
> @@ -3849,12 +3849,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))
> @@ -4352,7 +4352,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))
> @@ -4381,7 +4381,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
> @@ -4394,16 +4394,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);
>  		}
> @@ -4415,7 +4412,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align
>  	/*
>  	 * We already have the bytes available in the allocation; use them.
>  	 */
> -	if (size <= (unsigned long)vm->nr_pages << PAGE_SHIFT) {
> +	if (size <= vm->nr_pages << PAGE_SHIFT) {
>  		/*
>  		 * No need to zero memory here, as unused memory will have
>  		 * already been zeroed at initial allocation time or during
> @@ -4722,7 +4719,7 @@ long vread_iter(struct iov_iter *iter, const char *addr, size_t count)
>  			 * mapping types (vmap, ioremap) don't set nr_pages.
>  			 */
>  			size = (vm->flags & VM_ALLOC && vm->nr_pages) ?
> -				       ((unsigned long)vm->nr_pages << PAGE_SHIFT) :
> +				       (vm->nr_pages << PAGE_SHIFT) :
>  				       get_vm_area_size(vm);
>  		else
>  			size = va_size(va);
> @@ -5228,7 +5225,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);
> @@ -5248,7 +5245,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;
> @@ -5266,16 +5263,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]);
> @@ -5333,7 +5331,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: 248951ddc14de84de3910f9b13f51491a8cd91df
> prerequisite-patch-id: bc462c518eb9ddcaccad5e1f5fed7cc24512b117
> prerequisite-patch-id: 891688c2e93c570d1c9c7802b46aaba974002684
> -- 
> 2.43.0
> 
Makes sense to me. The commit message could be shorter describing
just an issue. I was lost when i read it first time.

Also:

<snip>
Artem Lytkin (3):
  mm/vmalloc: fix 32-bit truncation of the area size in vread_iter()
  mm/vmalloc: fix 32-bit truncation in the vrealloc() grow-in-place check
  mm/vmalloc: make vm_struct.nr_pages an unsigned long
<snip>

first two fix overlaps. The latest also do it by converting nr_pages
to unsigned long and removes (unsigned long) casting introduced by 1 and 2.

Maybe just use one commit which is last in this series?

Anyway for this

Reviewed-by: Uladzislau Rezki (Sony) <urezki@gmail.com>

--
Uladzislau Rezki


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v3] mm/vmalloc: make vm_struct.nr_pages an unsigned long
  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
  2026-07-30 20:09   ` [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages Andrew Morton
  1 sibling, 0 replies; 11+ messages in thread
From: Artem Lytkin @ 2026-07-30 17:11 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, willy, shivamkalra98, linux-kernel

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


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages
  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   ` [PATCH v3] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
@ 2026-07-30 20:09   ` Andrew Morton
  1 sibling, 0 replies; 11+ messages in thread
From: Andrew Morton @ 2026-07-30 20:09 UTC (permalink / raw)
  To: Artem Lytkin; +Cc: linux-mm, urezki, willy, shivamkalra98, linux-kernel

On Thu, 30 Jul 2026 12:06:25 +0300 Artem Lytkin <iprintercanon@gmail.com> wrote:

> Andrew asked for a v2 of the nr_pages widening, rebased on mm-new, with
> the changelog clarified on what has actually been observed.
> 
> The widening reverts the two casts added by the patches I sent on 25
> July, so it needs those in the tree first, and they are not in mm-new,
> mm-unstable or mm-hotfixes-unstable today. They are here as 1/3 and 2/3 so
> that the series applies and Sashiko can chew on it. 2/3 is byte for byte
> what I sent; 1/3 has one corrected sentence in its changelog and the same
> one-line diff.

Sashiko couldn't apply it for some reason.

This all seems far too complex for such an obscure issue.

How about we queue just a single switch-to-ulong patch, add that to 7.3
and give it a Fixes:0bca23804632/cc:stable so 7.2.x gets fixed later?


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-07-30 20:09 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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   ` [PATCH v3] mm/vmalloc: make vm_struct.nr_pages an unsigned long Artem Lytkin
2026-07-30 20:09   ` [PATCH v2 0/3] mm/vmalloc: stop truncating byte counts derived from nr_pages 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

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.