Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

zsmalloc: don't underflow size calculation in zs_obj_write()

Do not mix class->size and object size during offsets/sizes calculation in
zs_obj_write(). Size classes can merge into clusters, based on
objects-per-zspage and pages-per-zspage characteristics, so some size
classes can store objects smaller than class->size. This becomes
problematic when object size is much smaller than class->size. zsmalloc
can falsely decide that object spans two physical pages, because a larger
class->size value is used for that check, while the actual object is much
smaller and fits the free space of the first physical page, so there is
nothing to write to the second page and memcpy() size calculation
underflows.

Unable to handle kernel paging request at virtual address ffffc00081ff4000
pc : __memcpy+0x10/0x24
lr : zs_obj_write+0x1b0/0x1d0 [zsmalloc]
Call trace:
__memcpy+0x10/0x24 (P)
zram_write_page+0x150/0x4fc [zram]
zram_submit_bio+0x5e0/0x6a4 [zram]
__submit_bio+0x168/0x220
submit_bio_noacct_nocheck+0x128/0x2c8
submit_bio_noacct+0x19c/0x2f8

This is mostly seen on system with larger page-sizes, because size class
cluters of such systems hold wider size ranges than on 4K PAGE_SIZE
systems.

Assume a 16K PAGE_SIZE system, a write of 820 bytes object to a 864-bytes
size class at offset 15560. 15560 + 864 is more than 16384 so zsmalloc
attempts to memcpy() it to two physical pages. However, 16384 - 15560 =
824 which is more than 820, so the object in fact doesn't span two
physical pages, and there is no data to write to the second physical page.

We always know the exact size in bytes of the object that we are about to
write (store), so use it instead of class->size.

Link: https://lkml.kernel.org/r/20250507054312.4135983-1-senozhatsky@chromium.org
Fixes: 44f76413496e ("zsmalloc: introduce new object mapping API")
Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
Reported-by: Igor Belousov <igor.b@beldev.am>
Tested-by: Igor Belousov <igor.b@beldev.am>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: Minchan Kim <minchan@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Sergey Senozhatsky and committed by
Andrew Morton
02f5bf89 fefc0751

+4 -4
+4 -4
mm/zsmalloc.c
··· 1243 1243 class = zspage_class(pool, zspage); 1244 1244 off = offset_in_page(class->size * obj_idx); 1245 1245 1246 - if (off + class->size <= PAGE_SIZE) { 1246 + if (!ZsHugePage(zspage)) 1247 + off += ZS_HANDLE_SIZE; 1248 + 1249 + if (off + mem_len <= PAGE_SIZE) { 1247 1250 /* this object is contained entirely within a page */ 1248 1251 void *dst = kmap_local_zpdesc(zpdesc); 1249 1252 1250 - if (!ZsHugePage(zspage)) 1251 - off += ZS_HANDLE_SIZE; 1252 1253 memcpy(dst + off, handle_mem, mem_len); 1253 1254 kunmap_local(dst); 1254 1255 } else { 1255 1256 /* this object spans two pages */ 1256 1257 size_t sizes[2]; 1257 1258 1258 - off += ZS_HANDLE_SIZE; 1259 1259 sizes[0] = PAGE_SIZE - off; 1260 1260 sizes[1] = mem_len - sizes[0]; 1261 1261