* [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-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-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-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