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

mm/migrate: fix do_pages_stat in compat mode

For arrays with more than 16 entries, the old code would incorrectly
advance the pages pointer by 16 words instead of 16 compat_uptr_t. Fix by
doing the pointer arithmetic inside get_compat_pages_array where pages32
is already a correctly-typed pointer.

Discovered while working on PostgreSQL 18's new NUMA introspection code.

Link: https://lkml.kernel.org/r/aGREU0XTB48w9CwN@msg.df7cb.de
Fixes: 5b1b561ba73c ("mm: simplify compat_sys_move_pages")
Signed-off-by: Christoph Berg <myon@debian.org>
Acked-by: David Hildenbrand <david@redhat.com>
Suggested-by: David Hildenbrand <david@redhat.com>
Reported-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reported-by: Tomas Vondra <tomas@vondra.me>
Closes: https://www.postgresql.org/message-id/flat/6342f601-77de-4ee0-8c2a-3deb50ceac5b%40vondra.me#86402e3d80c031788f5f55b42c459471
Cc: Alistair Popple <apopple@nvidia.com>
Cc: Byungchul Park <byungchul@sk.com>
Cc: Gregory Price <gourry@gourry.net>
Cc: "Huang, Ying" <ying.huang@linux.alibaba.com>
Cc: Joshua Hahn <joshua.hahnjy@gmail.com>
Cc: Mathew Brost <matthew.brost@intel.com>
Cc: Rakie Kim <rakie.kim@sk.com>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Christoph Berg and committed by
Andrew Morton
10d04c26 bb1b5929

+8 -6
+8 -6
mm/migrate.c
··· 2399 2399 2400 2400 static int get_compat_pages_array(const void __user *chunk_pages[], 2401 2401 const void __user * __user *pages, 2402 + unsigned long chunk_offset, 2402 2403 unsigned long chunk_nr) 2403 2404 { 2404 2405 compat_uptr_t __user *pages32 = (compat_uptr_t __user *)pages; ··· 2407 2406 int i; 2408 2407 2409 2408 for (i = 0; i < chunk_nr; i++) { 2410 - if (get_user(p, pages32 + i)) 2409 + if (get_user(p, pages32 + chunk_offset + i)) 2411 2410 return -EFAULT; 2412 2411 chunk_pages[i] = compat_ptr(p); 2413 2412 } ··· 2426 2425 #define DO_PAGES_STAT_CHUNK_NR 16UL 2427 2426 const void __user *chunk_pages[DO_PAGES_STAT_CHUNK_NR]; 2428 2427 int chunk_status[DO_PAGES_STAT_CHUNK_NR]; 2428 + unsigned long chunk_offset = 0; 2429 2429 2430 2430 while (nr_pages) { 2431 2431 unsigned long chunk_nr = min(nr_pages, DO_PAGES_STAT_CHUNK_NR); 2432 2432 2433 2433 if (in_compat_syscall()) { 2434 2434 if (get_compat_pages_array(chunk_pages, pages, 2435 - chunk_nr)) 2435 + chunk_offset, chunk_nr)) 2436 2436 break; 2437 2437 } else { 2438 - if (copy_from_user(chunk_pages, pages, 2438 + if (copy_from_user(chunk_pages, pages + chunk_offset, 2439 2439 chunk_nr * sizeof(*chunk_pages))) 2440 2440 break; 2441 2441 } 2442 2442 2443 2443 do_pages_stat_array(mm, chunk_nr, chunk_pages, chunk_status); 2444 2444 2445 - if (copy_to_user(status, chunk_status, chunk_nr * sizeof(*status))) 2445 + if (copy_to_user(status + chunk_offset, chunk_status, 2446 + chunk_nr * sizeof(*status))) 2446 2447 break; 2447 2448 2448 - pages += chunk_nr; 2449 - status += chunk_nr; 2449 + chunk_offset += chunk_nr; 2450 2450 nr_pages -= chunk_nr; 2451 2451 } 2452 2452 return nr_pages ? -EFAULT : 0;