* [PATCH] mm/ksm: avoid anon_vma UAF during page merging
@ 2026-10-31 18:22 Zhao Gongyi
2026-11-01 18:11 ` David Hildenbrand (Arm)
2026-11-02 10:18 ` xu.xin
0 siblings, 2 replies; 7+ messages in thread
From: Zhao Gongyi @ 2026-10-31 18:22 UTC (permalink / raw)
To: akpm, david
Cc: xu.xin, chengming.zhou, aarcange, riel, linux-mm, linux-kernel,
Zhao Gongyi
KASAN reports a use-after-free in page_address_in_vma() while ksmd
merges pages. ksmd keeps a folio reference across an mmap_lock drop,
during which mremap() can remove its last mapping and free its
anon_vma. page_address_in_vma() then dereferences the stale pointer
from folio->mapping.
KSM needs only a candidate address. write_protect_page() checks that
the PTE maps the folio, and replace_page() verifies the saved PTE
before replacing it. Compute the candidate from the folio index and
VMA instead of dereferencing the folio-side anon_vma. Reject KSM
folios because their page index is not meaningful.
Fixes: 21d0d443cdc1 ("rmap: resurrect page_address_in_vma anon_vma check")
Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com>
---
Testing:
Unpatched mm-new: fixed-pool KSM/mremap reproduced the KASAN UAF.
Patched mm-new: the same workload ran for 12 minutes without KASAN,
BUG or WARNING; fork/COW KSM smoke passed.
mm/ksm.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/ksm.c b/mm/ksm.c
index d6f736a..f8af5ee 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -1307,7 +1307,7 @@ static int write_protect_page(struct vm_area_struct *vma, struct folio *folio,
if (WARN_ON_ONCE(folio_test_large(folio)))
return err;
- pvmw.address = page_address_in_vma(folio, folio_page(folio, 0), vma);
+ pvmw.address = vma_anon_address(vma, folio_pgoff(folio), 1);
if (pvmw.address == -EFAULT)
goto out;
@@ -1408,7 +1408,7 @@ static int replace_page(struct vm_area_struct *vma, struct page *page,
int err = -EFAULT;
struct mmu_notifier_range range;
- addr = page_address_in_vma(folio, page, vma);
+ addr = vma_anon_address(vma, page_pgoff(folio, page), 1);
if (addr == -EFAULT)
goto out;
@@ -1515,7 +1515,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma,
if (page == kpage) /* ksm page forked */
return 0;
- if (!folio_test_anon(folio))
+ if (!folio_test_anon(folio) || folio_test_ksm(folio))
goto out;
/*
base-commit: 243e8cdab64129d6207c398f8cf5fbb1962ff7e1
--
2.39.5 (Apple Git-154)
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-10-31 18:22 [PATCH] mm/ksm: avoid anon_vma UAF during page merging Zhao Gongyi @ 2026-11-01 18:11 ` David Hildenbrand (Arm) 2026-11-02 3:32 ` Zhao Gongyi 2026-11-02 11:23 ` Lorenzo Stoakes (ARM) 2026-11-02 10:18 ` xu.xin 1 sibling, 2 replies; 7+ messages in thread From: David Hildenbrand (Arm) @ 2026-11-01 18:11 UTC (permalink / raw) To: Zhao Gongyi, akpm Cc: xu.xin, chengming.zhou, aarcange, riel, linux-mm, linux-kernel, Lorenzo Stoakes (Arm) On 10/31/26 19:22, Zhao Gongyi wrote: Hi, thanks for the report! CCing Lorenzo. > KASAN reports a use-after-free in page_address_in_vma() while ksmd > merges pages. ksmd keeps a folio reference across an mmap_lock drop, > during which mremap() can remove its last mapping and free its > anon_vma. page_address_in_vma() then dereferences the stale pointer > from folio->mapping. Do you have a link to the report or the actual KASAN report? > > KSM needs only a candidate address. write_protect_page() checks that > the PTE maps the folio, and replace_page() verifies the saved PTE > before replacing it. Compute the candidate from the folio index and > VMA instead of dereferencing the folio-side anon_vma. Reject KSM > folios because their page index is not meaningful. > > Fixes: 21d0d443cdc1 ("rmap: resurrect page_address_in_vma anon_vma check") > Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com> > --- > Testing: > Unpatched mm-new: fixed-pool KSM/mremap reproduced the KASAN UAF. > Patched mm-new: the same workload ran for 12 minutes without KASAN, > BUG or WARNING; fork/COW KSM smoke passed. > > mm/ksm.c | 6 +++--- > 1 file changed, 3 insertions(+), 3 deletions(-) > > diff --git a/mm/ksm.c b/mm/ksm.c > index d6f736a..f8af5ee 100644 > --- a/mm/ksm.c > +++ b/mm/ksm.c > @@ -1307,7 +1307,7 @@ static int write_protect_page(struct vm_area_struct *vma, struct folio *folio, > if (WARN_ON_ONCE(folio_test_large(folio))) > return err; > > - pvmw.address = page_address_in_vma(folio, folio_page(folio, 0), vma); > + pvmw.address = vma_anon_address(vma, folio_pgoff(folio), 1); > if (pvmw.address == -EFAULT) > goto out; > > @@ -1408,7 +1408,7 @@ static int replace_page(struct vm_area_struct *vma, struct page *page, > int err = -EFAULT; > struct mmu_notifier_range range; > > - addr = page_address_in_vma(folio, page, vma); > + addr = vma_anon_address(vma, page_pgoff(folio, page), 1); > if (addr == -EFAULT) > goto out; > > @@ -1515,7 +1515,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, > if (page == kpage) /* ksm page forked */ > return 0; > > - if (!folio_test_anon(folio)) > + if (!folio_test_anon(folio) || folio_test_ksm(folio)) > goto out; > > /* > base-commit: 243e8cdab64129d6207c398f8cf5fbb1962ff7e1 -- Cheers, David ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-11-01 18:11 ` David Hildenbrand (Arm) @ 2026-11-02 3:32 ` Zhao Gongyi 2026-11-02 11:23 ` Lorenzo Stoakes (ARM) 1 sibling, 0 replies; 7+ messages in thread From: Zhao Gongyi @ 2026-11-02 3:32 UTC (permalink / raw) To: David Hildenbrand (Arm), akpm Cc: xu.xin, chengming.zhou, aarcange, linux-mm, linux-kernel, Lorenzo Stoakes (Arm), riel, ljs Hi David, I don't have a public link for this report, but the KASAN splat is below, followed by the standalone reproducer I used. The reproducer needs CONFIG_KSM and writes the KSM sysfs knobs, so I ran it as root in a throwaway KASAN VM. One invocation I used was: ./ksm_mremap_dontunmap_repro 4 2048 1 100000 64 It keeps mergeable anonymous pages at stable addresses while repeatedly using mremap(MREMAP_FIXED | MREMAP_DONTUNMAP) over the destination VMAs, so ksmd can race with removal of the last anon mapping. KASAN report: ================================================================== BUG: KASAN: slab-use-after-free in page_address_in_vma+0x104/0x32c Read of size 8 at addr ffff0000d0c24c80 by task ksmd/34 Call trace: show_stack+0x2c/0x3c (C) dump_stack_lvl+0x94/0xc0 print_report+0x174/0x71c kasan_report+0xc8/0x124 __asan_report_load8_noabort+0x20/0x2c page_address_in_vma+0x104/0x32c try_to_merge_one_page+0x304/0x14b0 try_to_merge_with_ksm_page+0x138/0x2e0 ksm_scan_thread+0x2f70/0x4ae8 kthread+0x310/0x400 ret_from_fork+0x10/0x20 Allocated by task 465110: kasan_save_track+0x40/0x78 kasan_save_alloc_info+0x44/0x54 __kasan_slab_alloc+0x58/0x70 kmem_cache_alloc_noprof+0x144/0x39c __anon_vma_prepare+0xd8/0x370 handle_mm_fault+0x3a74/0x43cc __get_user_pages+0x1228/0x1d50 __mm_populate+0x284/0x388 __arm64_sys_mlockall+0x2fc/0x378 invoke_syscall+0x8c/0x1f8 el0_svc_common+0xe8/0x23c do_el0_svc+0x48/0x58 el0_svc+0x3c/0x98 el0t_64_sync_handler+0x84/0x12c el0t_64_sync+0x198/0x19c Freed by task 461050: kasan_save_track+0x40/0x78 kasan_save_free_info+0x58/0x70 __kasan_slab_free+0x5c/0x8c slab_free_after_rcu_debug+0xa8/0x1d8 rcu_core+0x6bc/0xd8c rcu_core_si+0x10/0x1c handle_softirqs+0x1d4/0x55c __do_softirq+0x14/0x20 Last potentially related work creation: kasan_save_stack+0x40/0x6c kasan_record_aux_stack+0x90/0xa4 kmem_cache_free+0x2f4/0x3b0 __put_anon_vma+0xcc/0x1f8 unlink_anon_vmas+0x350/0x444 free_pgtables+0x254/0x5c0 unmap_region+0x1e8/0x23c vms_complete_munmap_vmas+0x300/0x7e8 do_vmi_align_munmap+0x2b8/0x360 do_vmi_munmap+0x190/0x1dc do_munmap+0x7c/0xb0 mremap_to+0x210/0x5dc __arm64_sys_mremap+0x850/0xc70 invoke_syscall+0x8c/0x1f8 el0_svc_common+0xe8/0x23c do_el0_svc+0x48/0x58 el0_svc+0x3c/0x98 el0t_64_sync_handler+0x84/0x12c el0t_64_sync+0x198/0x19c C reproducer: // SPDX-License-Identifier: GPL-2.0 // Fixed-address KSM + MREMAP_DONTUNMAP stressor for page_address_in_vma(). #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #ifndef MADV_MERGEABLE #define MADV_MERGEABLE 12 #endif #ifndef MREMAP_MAYMOVE #define MREMAP_MAYMOVE 1 #endif #ifndef MREMAP_FIXED #define MREMAP_FIXED 2 #endif #ifndef MREMAP_DONTUNMAP #define MREMAP_DONTUNMAP 4 #endif static size_t page_size; static volatile sig_atomic_t stop; static void on_signal(int sig) { (void)sig; stop = 1; } static void write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "warn: open %s: %s\n", path, strerror(errno)); return; } if (write(fd, val, strlen(val)) < 0) fprintf(stderr, "warn: write %s: %s\n", path, strerror(errno)); close(fd); } static void tune_ksm(void) { write_file("/sys/kernel/mm/ksm/run", "1\n"); write_file("/sys/kernel/mm/ksm/pages_to_scan", "200000\n"); write_file("/sys/kernel/mm/ksm/sleep_millisecs", "0\n"); } static void fill_pattern(char *p, size_t len) { size_t off; for (off = 0; off < len; off += page_size) { memset(p + off, 0x5a, page_size); memcpy(p + off, "ksm-fixed-pool", 14); } } static void *new_mergeable_area(size_t len) { void *p; p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) return MAP_FAILED; if (madvise(p, len, MADV_MERGEABLE) < 0) { fprintf(stderr, "madvise(MADV_MERGEABLE): %s\n", strerror(errno)); munmap(p, len); return MAP_FAILED; } fill_pattern(p, len); return p; } static void worker_loop(int id, int slots, int pages, int expose_us, int seed_mb) { size_t len = (size_t)pages * page_size; void **src = calloc((size_t)slots, sizeof(*src)); void **dst = calloc((size_t)slots, sizeof(*dst)); void *seed = NULL; unsigned long iter = 0; int i; if (!src || !dst) _exit(1); if (seed_mb > 0) { seed = new_mergeable_area((size_t)seed_mb << 20); fprintf(stderr, "worker%d seed=%p seed_mb=%d\n", id, seed, seed_mb); } for (i = 0; i < slots; i++) { src[i] = new_mergeable_area(len); dst[i] = new_mergeable_area(len); if (src[i] == MAP_FAILED || dst[i] == MAP_FAILED) { fprintf(stderr, "worker%d initial mmap failed at slot %d\n", id, i); _exit(1); } } while (!stop) { /* * Let ksmd observe stable virtual addresses and cache rmap_items. * It may return holding a folio ref after dropping mmap_lock while * this process proceeds to replace the destination mapping below. */ if (expose_us > 0) usleep(expose_us); for (i = 0; i < slots && !stop; i++) { void *ret; ret = (void *)syscall(SYS_mremap, src[i], len, len, MREMAP_MAYMOVE | MREMAP_FIXED | MREMAP_DONTUNMAP, dst[i]); if (ret == MAP_FAILED) { if (errno == EINVAL) fprintf(stderr, "mremap DONTUNMAP EINVAL\n"); continue; } /* * Source VMA remains in place after DONTUNMAP, but its PTEs * were zapped. Fault it back in so the next cycle can again * replace the same destination address. */ fill_pattern(src[i], len); (void)madvise(src[i], len, MADV_MERGEABLE); (void)madvise(dst[i], len, MADV_MERGEABLE); } if (id == 0 && (++iter & 0x3f) == 0) fprintf(stderr, "iter=%lu slots=%d pages=%d expose_us=%d\n", iter, slots, pages, expose_us); else iter++; } (void)seed; } int main(int argc, char **argv) { int workers = argc > 1 ? atoi(argv[1]) : 4; int slots = argc > 2 ? atoi(argv[2]) : 2048; int pages = argc > 3 ? atoi(argv[3]) : 1; int expose_us = argc > 4 ? atoi(argv[4]) : 100000; int seed_mb = argc > 5 ? atoi(argv[5]) : 64; int i; page_size = (size_t)sysconf(_SC_PAGESIZE); if (workers <= 0) workers = 1; if (slots <= 0) slots = 1; if (pages <= 0) pages = 1; signal(SIGINT, on_signal); signal(SIGTERM, on_signal); tune_ksm(); fprintf(stderr, "workers=%d slots=%d pages=%d expose_us=%d seed_mb=%d\n", workers, slots, pages, expose_us, seed_mb); for (i = 0; i < workers; i++) { pid_t pid = fork(); if (pid == 0) worker_loop(i, slots, pages, expose_us, seed_mb); if (pid < 0) { perror("fork"); stop = 1; break; } } while (!stop) sleep(1); kill(0, SIGTERM); while (wait(NULL) > 0) ; return 0; } Thanks, Gongyi ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-11-01 18:11 ` David Hildenbrand (Arm) 2026-11-02 3:32 ` Zhao Gongyi @ 2026-11-02 11:23 ` Lorenzo Stoakes (ARM) 1 sibling, 0 replies; 7+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-11-02 11:23 UTC (permalink / raw) To: David Hildenbrand (Arm) Cc: Zhao Gongyi, akpm, xu.xin, chengming.zhou, aarcange, riel, linux-mm, linux-kernel On Sun, Nov 01, 2026 at 07:11:25PM +0100, David Hildenbrand (Arm) wrote: > On 10/31/26 19:22, Zhao Gongyi wrote: > > Hi, > > thanks for the report! CCing Lorenzo. > > > KASAN reports a use-after-free in page_address_in_vma() while ksmd > > merges pages. ksmd keeps a folio reference across an mmap_lock drop, > > during which mremap() can remove its last mapping and free its > > anon_vma. page_address_in_vma() then dereferences the stale pointer > > from folio->mapping. Fun fun. KSM is dropping the lock then assuming the VMA doesn't change. I want to take a different approach to this fix so I'll take it over. > > Do you have a link to the report or the actual KASAN report? Thanks, the attached report is useful. -- Cheers, Lorenzo ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-10-31 18:22 [PATCH] mm/ksm: avoid anon_vma UAF during page merging Zhao Gongyi 2026-11-01 18:11 ` David Hildenbrand (Arm) @ 2026-11-02 10:18 ` xu.xin 2026-11-02 11:14 ` Zhao Gongyi 2026-11-02 12:14 ` Lorenzo Stoakes (ARM) 1 sibling, 2 replies; 7+ messages in thread From: xu.xin @ 2026-11-02 10:18 UTC (permalink / raw) To: zhaogongyi Cc: aarcange, akpm, chengming.zhou, david, linux-kernel, linux-mm, riel, xu.xin > KASAN reports a use-after-free in page_address_in_vma() while ksmd > merges pages. ksmd keeps a folio reference across an mmap_lock drop, > during which mremap() can remove its last mapping and free its > anon_vma. page_address_in_vma() then dereferences the stale pointer > from folio->mapping. > > KSM needs only a candidate address. write_protect_page() checks that > the PTE maps the folio, and replace_page() verifies the saved PTE > before replacing it. Compute the candidate from the folio index and > VMA instead of dereferencing the folio-side anon_vma. Reject KSM > folios because their page index is not meaningful. > > Fixes: 21d0d443cdc1 ("rmap: resurrect page_address_in_vma anon_vma check") > Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com> > --- > Testing: > Unpatched mm-new: fixed-pool KSM/mremap reproduced the KASAN UAF. > Patched mm-new: the same workload ran for 12 minutes without KASAN, > BUG or WARNING; fork/COW KSM smoke passed. Thanks for the report. I’d like to ask whether this issue is easy to reproduce on 6.18 since I suspect the accuracy of the “fixes” tag in the commit description. Besides, adding an extra folio_test_ksm() check is unneeded, see reasons below. > > mm/ksm.c | 6 +++--- > 1 file changed, 3 insertions(+), 3 deletions(-) > > diff --git a/mm/ksm.c b/mm/ksm.c > index d6f736a..f8af5ee 100644 > --- a/mm/ksm.c > +++ b/mm/ksm.c > @@ -1307,7 +1307,7 @@ static int write_protect_page(struct vm_area_struct *vma, struct folio *folio, > if (WARN_ON_ONCE(folio_test_large(folio))) > return err; > > - pvmw.address = page_address_in_vma(folio, folio_page(folio, 0), vma); > + pvmw.address = vma_anon_address(vma, folio_pgoff(folio), 1); > if (pvmw.address == -EFAULT) > goto out; > > @@ -1408,7 +1408,7 @@ static int replace_page(struct vm_area_struct *vma, struct page *page, > int err = -EFAULT; > struct mmu_notifier_range range; > > - addr = page_address_in_vma(folio, page, vma); > + addr = vma_anon_address(vma, page_pgoff(folio, page), 1); > if (addr == -EFAULT) > goto out; > > @@ -1515,7 +1515,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, > if (page == kpage) /* ksm page forked */ > return 0; > > - if (!folio_test_anon(folio)) > + if (!folio_test_anon(folio) || folio_test_ksm(folio)) > goto out; The check folio_test_ksm(folio)) in try_to_merge_one_page is unnecessary, because before try_to_merge_one_page() is called, cmp_and_merge_page() invokes folio_stable_node(), which already checks whether this is a KSM page. Only KSM fork pages are allowed to pass through; even if they do pass, they will ultimately be intercepted by the if (page == kpage) check in try_to_merge_one_page. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-11-02 10:18 ` xu.xin @ 2026-11-02 11:14 ` Zhao Gongyi 2026-11-02 12:14 ` Lorenzo Stoakes (ARM) 1 sibling, 0 replies; 7+ messages in thread From: Zhao Gongyi @ 2026-11-02 11:14 UTC (permalink / raw) To: xu.xin; +Cc: aarcange, akpm, chengming.zhou, david, linux-kernel, linux-mm, riel Hi Xu, Thanks for the review. On 11/2/26 6:18 PM, xu.xin@linux.dev wrote: > > I’d like to ask whether this issue is easy to reproduce on 6.18 since > I suspect the accuracy of the “fixes” tag in the commit description. > > Besides, adding an extra folio_test_ksm() check is unneeded, see reasons below. > I reproduced the issue twice on v6.18 (7d0a66e4bb90, CONFIG_KSM=y, CONFIG_KASAN=y and CONFIG_KASAN_GENERIC=y). The exact invocation from my previous email: /tmp/ksm_fixed_pool_static 4 2048 1 100000 64 reproduced the issue after about 220 seconds: BUG: KASAN: slab-use-after-free in page_address_in_vma+0x292/0x2b0 Read of size 8 by task ksmd page_address_in_vma write_protect_page try_to_merge_one_page try_to_merge_with_ksm_page ksm_scan_thread The corresponding free path included: __put_anon_vma unlink_anon_vmas ... mremap_to do_mremap A second run with 4096 slots reproduced the same UAF after about 142 seconds. This confirms that the issue is present in v6.18. I also rechecked the Fixes tag. Before 21d0d443cdc1 ("rmap: resurrect page_address_in_vma anon_vma check"), the anonymous page path in page_address_in_vma() computed the address without dereferencing the page-side anon_vma. That commit added the comparison between vma->anon_vma->root and page_anon_vma(page)->root. At that time, the KSM scan path already retained a page reference, dropped mmap_sem, and later called page_address_in_vma() from write_protect_page() or replace_page(). The page reference does not pin the page-side anon_vma. Commit 4829b906cc06 ("ksm: fix page_address_in_vma anon_vma oops") added a NULL check, but the helper still dereferences the anon_vma when it is non-NULL but stale. MREMAP_DONTUNMAP, especially commit 1583aa278f5f ("mm: mremap: unlink anon_vmas when mremap with MREMAP_DONTUNMAP success"), makes this race easy to reproduce and explains the unlink_anon_vmas() free path. However, it did not introduce the unpinned anon_vma dereference that this patch removes. Based on that, I think 21d0d443cdc1 remains the earliest appropriate Fixes commit. Please let me know if I am missing a lifetime guarantee that made the dereference safe before the MREMAP_DONTUNMAP changes. You are also right that the folio_test_ksm() check is unnecessary. cmp_and_merge_page() already checks folio_stable_node(), and a KSM fork page that continues through this path is handled by the page == kpage check in try_to_merge_one_page(). I will drop that hunk and the corresponding commit-message text in v2. Thanks, Gongyi ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging 2026-11-02 10:18 ` xu.xin 2026-11-02 11:14 ` Zhao Gongyi @ 2026-11-02 12:14 ` Lorenzo Stoakes (ARM) 1 sibling, 0 replies; 7+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-11-02 12:14 UTC (permalink / raw) To: xu.xin Cc: zhaogongyi, aarcange, akpm, chengming.zhou, david, linux-kernel, linux-mm, riel On Mon, Nov 02, 2026 at 06:18:31PM +0800, xu.xin@linux.dev wrote: > > KASAN reports a use-after-free in page_address_in_vma() while ksmd > > merges pages. ksmd keeps a folio reference across an mmap_lock drop, > > during which mremap() can remove its last mapping and free its > > anon_vma. page_address_in_vma() then dereferences the stale pointer > > from folio->mapping. > > > > KSM needs only a candidate address. write_protect_page() checks that > > the PTE maps the folio, and replace_page() verifies the saved PTE > > before replacing it. Compute the candidate from the folio index and > > VMA instead of dereferencing the folio-side anon_vma. Reject KSM > > folios because their page index is not meaningful. > > > > Fixes: 21d0d443cdc1 ("rmap: resurrect page_address_in_vma anon_vma check") > > Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com> > > --- > > Testing: > > Unpatched mm-new: fixed-pool KSM/mremap reproduced the KASAN UAF. > > Patched mm-new: the same workload ran for 12 minutes without KASAN, > > BUG or WARNING; fork/COW KSM smoke passed. > > Thanks for the report. > > I’d like to ask whether this issue is easy to reproduce on 6.18 since > I suspect the accuracy of the “fixes” tag in the commit description. > > Besides, adding an extra folio_test_ksm() check is unneeded, see reasons below. > > > > > mm/ksm.c | 6 +++--- > > 1 file changed, 3 insertions(+), 3 deletions(-) > > > > diff --git a/mm/ksm.c b/mm/ksm.c > > index d6f736a..f8af5ee 100644 > > --- a/mm/ksm.c > > +++ b/mm/ksm.c > > @@ -1307,7 +1307,7 @@ static int write_protect_page(struct vm_area_struct *vma, struct folio *folio, > > if (WARN_ON_ONCE(folio_test_large(folio))) > > return err; > > > > - pvmw.address = page_address_in_vma(folio, folio_page(folio, 0), vma); > > + pvmw.address = vma_anon_address(vma, folio_pgoff(folio), 1); > > if (pvmw.address == -EFAULT) > > goto out; > > > > @@ -1408,7 +1408,7 @@ static int replace_page(struct vm_area_struct *vma, struct page *page, > > int err = -EFAULT; > > struct mmu_notifier_range range; > > > > - addr = page_address_in_vma(folio, page, vma); > > + addr = vma_anon_address(vma, page_pgoff(folio, page), 1); > > if (addr == -EFAULT) > > goto out; > > > > @@ -1515,7 +1515,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, > > if (page == kpage) /* ksm page forked */ > > return 0; > > > > - if (!folio_test_anon(folio)) > > + if (!folio_test_anon(folio) || folio_test_ksm(folio)) > > goto out; > > The check folio_test_ksm(folio)) in try_to_merge_one_page is unnecessary, because before > try_to_merge_one_page() is called, cmp_and_merge_page() invokes folio_stable_node(), which already checks > whether this is a KSM page. Only KSM fork pages are allowed to pass through; even if they do pass, they > will ultimately be intercepted by the if (page == kpage) check in try_to_merge_one_page. This doesn't quite cover it, as cmp_and_merge_page() doesn't bail if !folio_test_ksm(), it can then fall through to stable_tree_search() which can return NULL legitimately there, so the page ends up in th eunstable tree. Actually right now what's saving us is page_address_in_vma() implicitly rejecting KSM folios (since folio_anon_vma() returns NULL for them). So if you remove that, you have to re-add the check there. This is very fiddly, and KSM is very worrying :) (I'm sending a fix separately.) -- Cheers, Lorenzo ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-02 12:14 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-31 18:22 [PATCH] mm/ksm: avoid anon_vma UAF during page merging Zhao Gongyi 2026-11-01 18:11 ` David Hildenbrand (Arm) 2026-11-02 3:32 ` Zhao Gongyi 2026-11-02 11:23 ` Lorenzo Stoakes (ARM) 2026-11-02 10:18 ` xu.xin 2026-11-02 11:14 ` Zhao Gongyi 2026-11-02 12:14 ` Lorenzo Stoakes (ARM)
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox