From: "Zhao Gongyi" <zhaogongyi@bytedance.com>
To: "David Hildenbrand (Arm)" <david@kernel.org>,
<akpm@linux-foundation.org>
Cc: <xu.xin@linux.dev>, <chengming.zhou@linux.dev>,
<aarcange@redhat.com>, <linux-mm@kvack.org>,
<linux-kernel@vger.kernel.org>,
"Lorenzo Stoakes (Arm)" <ljs@kernel.org>, <riel@surriel.com>,
<ljs@kernel.org>
Subject: Re: [PATCH] mm/ksm: avoid anon_vma UAF during page merging
Date: Mon, 2 Nov 2026 11:32:38 +0800 [thread overview]
Message-ID: <9f09eebd-a8bb-4622-a307-1ba9e7478f26@bytedance.com> (raw)
In-Reply-To: <79820b33-7e74-4e12-926f-1e03987d7bda@kernel.org>
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
prev parent reply other threads:[~2026-11-02 3:33 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 18:22 Zhao Gongyi
2026-11-01 18:11 ` David Hildenbrand (Arm)
2026-11-02 3:32 ` Zhao Gongyi [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=9f09eebd-a8bb-4622-a307-1ba9e7478f26@bytedance.com \
--to=zhaogongyi@bytedance.com \
--cc=aarcange@redhat.com \
--cc=akpm@linux-foundation.org \
--cc=chengming.zhou@linux.dev \
--cc=david@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=riel@surriel.com \
--cc=xu.xin@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®