Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] mm/vmalloc: fix 32-bit truncation of the area size in vread_iter()
@ 2026-07-25 13:22 Artem Lytkin
  2026-07-25 21:48 ` Andrew Morton
  0 siblings, 1 reply; 4+ messages in thread
From: Artem Lytkin @ 2026-07-25 13:22 UTC (permalink / raw)
  To: linux-mm; +Cc: akpm, urezki, 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". Two paths
that might be expected to reach it cannot: alloc_large_system_hash()
caps a table at a sixteenth of memory, and the KVM dirty bitmap is
bounded by KVM_MEM_MAX_NR_PAGES to 512 MiB.

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 1afca3568b9b6..44647e189f7d6 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -4722,7 +4722,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);

base-commit: 248951ddc14de84de3910f9b13f51491a8cd91df
-- 
2.43.0



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

end of thread, other threads:[~2026-07-26 15:25 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25 13:22 [PATCH 1/2] mm/vmalloc: fix 32-bit truncation of the area size in vread_iter() Artem Lytkin
2026-07-25 21:48 ` Andrew Morton
2026-07-26  9:46   ` Artem Lytkin
2026-07-26 15:25   ` Uladzislau Rezki

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox