mm/migrate: fix do_pages_stat in compat mode
authorChristoph Berg <myon@debian.org>
Tue, 24 Jun 2025 14:44:27 +0000 (16:44 +0200)
committerAndrew Morton <akpm@linux-foundation.org>
Thu, 10 Jul 2025 04:07:54 +0000 (21:07 -0700)
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>
mm/migrate.c

index 8cf0f9c9599d36948d10082834d1094baafa8820..2c88f3b33833ef8b3a5efd33f337b4fa7fe54d5a 100644 (file)
@@ -2399,6 +2399,7 @@ set_status:
 
 static int get_compat_pages_array(const void __user *chunk_pages[],
                                  const void __user * __user *pages,
+                                 unsigned long chunk_offset,
                                  unsigned long chunk_nr)
 {
        compat_uptr_t __user *pages32 = (compat_uptr_t __user *)pages;
@@ -2406,7 +2407,7 @@ static int get_compat_pages_array(const void __user *chunk_pages[],
        int i;
 
        for (i = 0; i < chunk_nr; i++) {
-               if (get_user(p, pages32 + i))
+               if (get_user(p, pages32 + chunk_offset + i))
                        return -EFAULT;
                chunk_pages[i] = compat_ptr(p);
        }
@@ -2425,27 +2426,28 @@ static int do_pages_stat(struct mm_struct *mm, unsigned long nr_pages,
 #define DO_PAGES_STAT_CHUNK_NR 16UL
        const void __user *chunk_pages[DO_PAGES_STAT_CHUNK_NR];
        int chunk_status[DO_PAGES_STAT_CHUNK_NR];
+       unsigned long chunk_offset = 0;
 
        while (nr_pages) {
                unsigned long chunk_nr = min(nr_pages, DO_PAGES_STAT_CHUNK_NR);
 
                if (in_compat_syscall()) {
                        if (get_compat_pages_array(chunk_pages, pages,
-                                                  chunk_nr))
+                                                  chunk_offset, chunk_nr))
                                break;
                } else {
-                       if (copy_from_user(chunk_pages, pages,
+                       if (copy_from_user(chunk_pages, pages + chunk_offset,
                                      chunk_nr * sizeof(*chunk_pages)))
                                break;
                }
 
                do_pages_stat_array(mm, chunk_nr, chunk_pages, chunk_status);
 
-               if (copy_to_user(status, chunk_status, chunk_nr * sizeof(*status)))
+               if (copy_to_user(status + chunk_offset, chunk_status,
+                                chunk_nr * sizeof(*status)))
                        break;
 
-               pages += chunk_nr;
-               status += chunk_nr;
+               chunk_offset += chunk_nr;
                nr_pages -= chunk_nr;
        }
        return nr_pages ? -EFAULT : 0;