* [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters
@ 2026-09-24 6:16 Ayush Ranjan
2026-09-24 7:12 ` David Hildenbrand (Arm)
2026-09-24 8:34 ` Pedro Falcato
0 siblings, 2 replies; 5+ messages in thread
From: Ayush Ranjan @ 2026-09-24 6:16 UTC (permalink / raw)
To: Hugh Dickins, Matthew Wilcox, Andrew Morton, Jan Kara
Cc: Ayush Ranjan, Baolin Wang, David Hildenbrand, Gregory Price,
Pedro Falcato, linux-mm, linux-fsdevel, linux-kernel
Hi,
We are seeing shmem/tmpfs page cache corruption on production hosts
running a workload that punches holes in a memfd (hole-punch based
memory reclaim) while other threads and forked children fault the
same MAP_SHARED mapping. The kernel taints but does not oops:
BUG: Bad page cache in process ... pfn:...
page dumped because: still mapped when deleted
...
dentry name(?): "memfd:..."
and, more frequently, a paired rss-counter imbalance when the mm is
torn down, always exactly one PMD-order folio (512 pages):
BUG: Bad rss-counter state mm:... type:MM_FILEPAGES val:-512
BUG: Bad rss-counter state mm:... type:MM_SHMEMPAGES val:512
Seen on 6.12 and 6.18, x86_64, bare metal and VM, with
/sys/kernel/mm/transparent_hugepage/shmem_enabled = always.
This looks like the same corruption Gregory reported in March, which
as far as I can tell stalled without a fix; that report needed ~100
ballooning VMs to reproduce:
https://patchew.org/linux/20260326162611.693539-1-gourry@gourry.net/
The reproducer at the end of this mail trips it with a single memfd,
no VMs or ballooning, within a few minutes on a large machine, so
hopefully it makes the race easier to confirm (and to test a fix
against).
Here is my best understanding of the race -- corrections welcome:
shmem guards faults against an in-progress hole punch with
inode->i_private: shmem_fault() -> shmem_falloc_wait() waits while
shmem_fallocate(PUNCH_HOLE) holds i_private. But shmem's .map_pages
is the generic filemap_map_pages() (shmem_vm_ops /
shmem_anon_vm_ops), which does not consult i_private and does not
take invalidate_lock, and shmem does not use invalidate_lock to
serialize faults against truncation the way regular filesystems do --
the i_private + waitq scheme stands in for it, but only shmem_fault()
participates in that scheme.
So while shmem_fallocate(PUNCH_HOLE) is between
unmap_mapping_range() and shmem_truncate_range(), a concurrent
fault-around can (re-)install PTEs for folios that are about to be
truncated:
- filemap_map_pages() samples mm_counter_file(folio) once per batch
and applies it with add_mm_counter() after mapping; if the
folio's swapbacked state changes while it is concurrently torn
down, the map-time counter (MM_FILEPAGES) and the zap-time
counter (MM_SHMEMPAGES) disagree by exactly one folio -- the
+/-512 imbalance above.
- a folio re-mapped in this window (by fault-around directly, or
via a child VMA whose PTEs copy_page_range() installs after
unmap_mapping_range() has already walked the i_mmap tree -- the
dup_mmap() variant discussed in the earlier thread) can be
deleted from the page cache while still mapped -> "still mapped
when deleted".
Reproducer
----------
The race is on PMD-order folios, so khugepaged needs to scan
aggressively (with the default 10s scan interval the punched ranges
are not re-collapsed fast enough to reproduce quickly):
echo always > /sys/kernel/mm/transparent_hugepage/shmem_enabled
cd /sys/kernel/mm/transparent_hugepage/khugepaged
echo 1 > scan_sleep_millisecs
echo 4096 > pages_to_scan
echo 511 > max_ptes_none
cc -O2 -pthread -o repro repro_shmem_punch_race.c
for i in $(seq $(( $(nproc) / 3 ))); do ./repro 60 & done; wait
# watch: dmesg -w
On a 112-CPU host this trips within ~2-5 minutes; this capture is
from 6.12.0-204.92.4.4.3.el9uek.x86_64:
BUG: Bad rss-counter state mm:0000000078314ee0 type:MM_FILEPAGES val:-512
BUG: Bad rss-counter state mm:0000000078314ee0 type:MM_SHMEMPAGES val:512
The rss-counter form is the most frequent. The "still mapped when
deleted" form is what we mostly see in production but is rarer under
the reproducer (as in the earlier thread); I do not have a fresh
capture of it to paste here and will follow up with a full splat if I
catch one.
For background: we originally hit this under a memfd-backed sandbox
runtime (gVisor), which reclaims memory by punching holes in a
MAP_SHARED memfd while it is being faulted -- hence the "memfd:..."
dentry in the splats. The reproducer below has no such dependency: it
only uses memfd_create + mmap(MAP_SHARED) + fallocate(PUNCH_HOLE) +
madvise, so this appears to be a plain shmem issue rather than
anything specific to our setup.
Thanks,
Ayush
---- repro_shmem_punch_race.c ----
/*
* Race FALLOC_FL_PUNCH_HOLE against fault-around on a MAP_SHARED
* memfd mapping.
*
* One memfd is mapped MAP_SHARED into the main process and several
* forked peer processes. All of them fault (and MADV_DONTNEED,
* forcing re-fault via fault-around) random windows of the file while
* the main process punches holes at random offsets. Short-lived
* fork() children exercise the dup_mmap()/copy_page_range() variant.
*
* Usage: ./repro [seconds] [file_MiB] [peers]
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <linux/falloc.h>
#include <linux/memfd.h>
#define PAGE 4096UL
#define HPAGE (2UL << 20) /* PMD-order folio */
static unsigned char *map;
static int fd;
static size_t file_sz;
static volatile int stop;
static _Atomic long n_punch;
static inline uint64_t xorshift(uint64_t *s) {
*s ^= *s << 13; *s ^= *s >> 7; *s ^= *s << 17; return *s;
}
static uint64_t seed(void) {
struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t);
return (t.tv_nsec ^ ((uint64_t)getpid() << 20) ^ (uint64_t)pthread_self()) | 1;
}
/* Read a random ~128 KiB span (spanning several fault-around batches), then drop
* it so the next touch faults again through filemap_map_pages(). */
static void *faulter(void *arg) {
uint64_t s = seed();
size_t span = 32 * PAGE;
while (!stop) {
size_t off = (xorshift(&s) % ((file_sz - span) / PAGE)) * PAGE;
volatile unsigned char sink = 0;
for (size_t o = 0; o < span; o += PAGE)
sink += map[off + o];
if (xorshift(&s) & 1)
madvise(map + off, span, MADV_DONTNEED);
}
return NULL;
}
/* Keep folios present for the puncher to race against. */
static void *writer(void *arg) {
uint64_t s = seed();
size_t span = 64 * PAGE;
while (!stop) {
size_t off = (xorshift(&s) % ((file_sz - span) / PAGE)) * PAGE;
memset(map + off, 0x5a, span);
}
return NULL;
}
/* Punch holes at random offsets, mixing PMD-aligned and unaligned/sub-PMD
* ranges. */
static void *puncher(void *arg) {
uint64_t s = seed();
while (!stop) {
size_t len, off;
if (xorshift(&s) & 1) { /* unaligned, 4K..2M */
len = ((xorshift(&s) % 512) + 1) * PAGE;
off = (xorshift(&s) % ((file_sz - len) / PAGE)) * PAGE;
} else { /* PMD-aligned, 2M/4M */
len = ((xorshift(&s) % 2) + 1) * HPAGE;
off = (xorshift(&s) % ((file_sz - len) / HPAGE)) * HPAGE;
}
fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
(off_t)off, (off_t)len);
atomic_fetch_add(&n_punch, 1);
}
return NULL;
}
/* dup_mmap()/copy_page_range() variant; bounded to one child per thread. */
static void *forker(void *arg) {
uint64_t s = seed();
size_t pages = file_sz / PAGE;
while (!stop) {
pid_t p = fork();
if (p == 0) {
volatile unsigned char sink = 0;
for (int i = 0; i < 16; i++)
sink += map[(xorshift(&s) % pages) * PAGE];
_exit(0);
}
if (p > 0) waitpid(p, NULL, 0);
else usleep(200);
}
return NULL;
}
/* A peer process: maps the same memfd and faults/forks it concurrently. */
static void peer(int secs) {
pthread_t th[4];
pthread_create(&th[0], NULL, faulter, NULL);
pthread_create(&th[1], NULL, faulter, NULL);
pthread_create(&th[2], NULL, writer, NULL);
pthread_create(&th[3], NULL, forker, NULL);
sleep(secs + 2);
_exit(0);
}
int main(int argc, char **argv) {
int secs = argc > 1 ? atoi(argv[1]) : 60;
file_sz = (argc > 2 ? (size_t)atol(argv[2]) : 64) << 20;
int peers = argc > 3 ? atoi(argv[3]) : 2;
fd = memfd_create("repro", MFD_CLOEXEC);
if (fd < 0) { perror("memfd_create"); return 1; }
if (ftruncate(fd, file_sz)) { perror("ftruncate"); return 1; }
map = mmap(NULL, file_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { perror("mmap"); return 1; }
madvise(map, file_sz, MADV_HUGEPAGE); /* request PMD-order folios */
memset(map, 1, file_sz);
pid_t pid[64];
if (peers > 64) peers = 64;
for (int i = 0; i < peers; i++) {
pid[i] = fork();
if (pid[i] == 0) peer(secs); /* child shares the memfd + mapping */
}
enum { NFAULT = 3, NFORK = 1 };
pthread_t th[NFAULT + NFORK + 2];
int n = 0;
for (int i = 0; i < NFAULT; i++) pthread_create(&th[n++], NULL, faulter, NULL);
for (int i = 0; i < NFORK; i++) pthread_create(&th[n++], NULL, forker, NULL);
pthread_create(&th[n++], NULL, writer, NULL);
pthread_create(&th[n++], NULL, puncher, NULL);
sleep(secs);
stop = 1;
for (int i = 0; i < n; i++) pthread_join(th[i], NULL);
for (int i = 0; i < peers; i++) { kill(pid[i], SIGKILL); waitpid(pid[i], NULL, 0); }
while (waitpid(-1, NULL, WNOHANG) > 0) {}
fprintf(stderr, "pid %d: punches=%ld\n", getpid(), atomic_load(&n_punch));
munmap(map, file_sz);
close(fd);
return 0;
}
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters
2026-09-24 6:16 [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters Ayush Ranjan
@ 2026-09-24 7:12 ` David Hildenbrand (Arm)
2026-09-24 8:34 ` Pedro Falcato
1 sibling, 0 replies; 5+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-24 7:12 UTC (permalink / raw)
To: Ayush Ranjan, Hugh Dickins, Matthew Wilcox, Andrew Morton, Jan Kara
Cc: Baolin Wang, Gregory Price, Pedro Falcato, linux-mm,
linux-fsdevel, linux-kernel
On 9/24/26 08:16, Ayush Ranjan wrote:
> Hi,
>
> We are seeing shmem/tmpfs page cache corruption on production hosts
> running a workload that punches holes in a memfd (hole-punch based
> memory reclaim) while other threads and forked children fault the
> same MAP_SHARED mapping. The kernel taints but does not oops:
>
> BUG: Bad page cache in process ... pfn:...
> page dumped because: still mapped when deleted
> ...
> dentry name(?): "memfd:..."
>
> and, more frequently, a paired rss-counter imbalance when the mm is
> torn down, always exactly one PMD-order folio (512 pages):
>
> BUG: Bad rss-counter state mm:... type:MM_FILEPAGES val:-512
> BUG: Bad rss-counter state mm:... type:MM_SHMEMPAGES val:512
>
> Seen on 6.12 and 6.18, x86_64, bare metal and VM, with
> /sys/kernel/mm/transparent_hugepage/shmem_enabled = always.
>
> This looks like the same corruption Gregory reported in March, which
> as far as I can tell stalled without a fix; that report needed ~100
> ballooning VMs to reproduce:
>
> https://patchew.org/linux/20260326162611.693539-1-gourry@gourry.net/
Indeed, it looks like what was reported in
https://lore.kernel.org/all/20260326162611.693539-1-gourry@gourry.net/
--
Cheers,
David
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters
2026-09-24 6:16 [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters Ayush Ranjan
2026-09-24 7:12 ` David Hildenbrand (Arm)
@ 2026-09-24 8:34 ` Pedro Falcato
2026-09-24 9:15 ` Jan Kara
2026-09-24 9:30 ` Baolin Wang
1 sibling, 2 replies; 5+ messages in thread
From: Pedro Falcato @ 2026-09-24 8:34 UTC (permalink / raw)
To: Ayush Ranjan
Cc: Hugh Dickins, Matthew Wilcox, Andrew Morton, Jan Kara,
Baolin Wang, David Hildenbrand, Gregory Price, linux-mm,
linux-fsdevel, linux-kernel
(please use the email I actually use for work, thanks; not sure how
you got to that one)
Hi,
On Thu, Sep 24, 2026 at 06:16:21AM +0000, Ayush Ranjan wrote:
> Hi,
>
> We are seeing shmem/tmpfs page cache corruption on production hosts
> running a workload that punches holes in a memfd (hole-punch based
> memory reclaim) while other threads and forked children fault the
> same MAP_SHARED mapping. The kernel taints but does not oops:
>
> BUG: Bad page cache in process ... pfn:...
> page dumped because: still mapped when deleted
> ...
> dentry name(?): "memfd:..."
>
> and, more frequently, a paired rss-counter imbalance when the mm is
> torn down, always exactly one PMD-order folio (512 pages):
>
> BUG: Bad rss-counter state mm:... type:MM_FILEPAGES val:-512
> BUG: Bad rss-counter state mm:... type:MM_SHMEMPAGES val:512
>
> Seen on 6.12 and 6.18, x86_64, bare metal and VM, with
> /sys/kernel/mm/transparent_hugepage/shmem_enabled = always.
>
> This looks like the same corruption Gregory reported in March, which
> as far as I can tell stalled without a fix; that report needed ~100
> ballooning VMs to reproduce:
Well, I thought (from the lack of replies) that it was probably a bug on
their side. Perhaps that's not true :/
>
> https://patchew.org/linux/20260326162611.693539-1-gourry@gourry.net/
>
> The reproducer at the end of this mail trips it with a single memfd,
> no VMs or ballooning, within a few minutes on a large machine, so
> hopefully it makes the race easier to confirm (and to test a fix
> against).
>
> Here is my best understanding of the race -- corrections welcome:
>
> shmem guards faults against an in-progress hole punch with
> inode->i_private: shmem_fault() -> shmem_falloc_wait() waits while
> shmem_fallocate(PUNCH_HOLE) holds i_private. But shmem's .map_pages
> is the generic filemap_map_pages() (shmem_vm_ops /
> shmem_anon_vm_ops), which does not consult i_private and does not
> take invalidate_lock, and shmem does not use invalidate_lock to
> serialize faults against truncation the way regular filesystems do --
> the i_private + waitq scheme stands in for it, but only shmem_fault()
> participates in that scheme.
>
> So while shmem_fallocate(PUNCH_HOLE) is between
> unmap_mapping_range() and shmem_truncate_range(), a concurrent
> fault-around can (re-)install PTEs for folios that are about to be
> truncated:
>
> - filemap_map_pages() samples mm_counter_file(folio) once per batch
> and applies it with add_mm_counter() after mapping; if the
> folio's swapbacked state changes while it is concurrently torn
But that cannot happen? We hold the folio lock in filemap_map_pages().
The folio (naturally) cannot be torn down while we have the folio lock.
> down, the map-time counter (MM_FILEPAGES) and the zap-time
> counter (MM_SHMEMPAGES) disagree by exactly one folio -- the
> +/-512 imbalance above.
>
> - a folio re-mapped in this window (by fault-around directly, or
> via a child VMA whose PTEs copy_page_range() installs after
> unmap_mapping_range() has already walked the i_mmap tree -- the
> dup_mmap() variant discussed in the earlier thread) can be
> deleted from the page cache while still mapped -> "still mapped
> when deleted".
No, I don't think this paragraph is true. Page cache truncation (via
truncate, or fallocate PUNCH_HOLE) takes the folio lock for each folio
that is about to be truncated out. Mapping folios takes the folio lock
as well, except in the fork() case where a myriad of weird interval tree
+ PTE lock interactions make it safe (AIUI).
>
> Reproducer
> ----------
>
> The race is on PMD-order folios, so khugepaged needs to scan
> aggressively (with the default 10s scan interval the punched ranges
> are not re-collapsed fast enough to reproduce quickly):
>
> echo always > /sys/kernel/mm/transparent_hugepage/shmem_enabled
> cd /sys/kernel/mm/transparent_hugepage/khugepaged
> echo 1 > scan_sleep_millisecs
> echo 4096 > pages_to_scan
> echo 511 > max_ptes_none
>
> cc -O2 -pthread -o repro repro_shmem_punch_race.c
> for i in $(seq $(( $(nproc) / 3 ))); do ./repro 60 & done; wait
> # watch: dmesg -w
>
> On a 112-CPU host this trips within ~2-5 minutes; this capture is
> from 6.12.0-204.92.4.4.3.el9uek.x86_64:
Awesome that you have a reproducer! Have you reproduced this on a mainline
kernel? Enterprise kernels are not supported upstream.
In any case, I'll take a closer look ASAP.
--
Pedro
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters
2026-09-24 8:34 ` Pedro Falcato
@ 2026-09-24 9:15 ` Jan Kara
2026-09-24 9:30 ` Baolin Wang
1 sibling, 0 replies; 5+ messages in thread
From: Jan Kara @ 2026-09-24 9:15 UTC (permalink / raw)
To: Pedro Falcato
Cc: Ayush Ranjan, Hugh Dickins, Matthew Wilcox, Andrew Morton,
Jan Kara, Baolin Wang, David Hildenbrand, Gregory Price,
linux-mm, linux-fsdevel, linux-kernel
On Thu 24-09-26 09:34:24, Pedro Falcato wrote:
> On Thu, Sep 24, 2026 at 06:16:21AM +0000, Ayush Ranjan wrote:
> > Here is my best understanding of the race -- corrections welcome:
> >
> > shmem guards faults against an in-progress hole punch with
> > inode->i_private: shmem_fault() -> shmem_falloc_wait() waits while
> > shmem_fallocate(PUNCH_HOLE) holds i_private. But shmem's .map_pages
> > is the generic filemap_map_pages() (shmem_vm_ops /
> > shmem_anon_vm_ops), which does not consult i_private and does not
> > take invalidate_lock, and shmem does not use invalidate_lock to
> > serialize faults against truncation the way regular filesystems do --
> > the i_private + waitq scheme stands in for it, but only shmem_fault()
> > participates in that scheme.
> >
> > So while shmem_fallocate(PUNCH_HOLE) is between
> > unmap_mapping_range() and shmem_truncate_range(), a concurrent
> > fault-around can (re-)install PTEs for folios that are about to be
> > truncated:
> >
> > - filemap_map_pages() samples mm_counter_file(folio) once per batch
> > and applies it with add_mm_counter() after mapping; if the
> > folio's swapbacked state changes while it is concurrently torn
>
> But that cannot happen? We hold the folio lock in filemap_map_pages().
> The folio (naturally) cannot be torn down while we have the folio lock.
>
> > down, the map-time counter (MM_FILEPAGES) and the zap-time
> > counter (MM_SHMEMPAGES) disagree by exactly one folio -- the
> > +/-512 imbalance above.
> >
> > - a folio re-mapped in this window (by fault-around directly, or
> > via a child VMA whose PTEs copy_page_range() installs after
> > unmap_mapping_range() has already walked the i_mmap tree -- the
> > dup_mmap() variant discussed in the earlier thread) can be
> > deleted from the page cache while still mapped -> "still mapped
> > when deleted".
>
> No, I don't think this paragraph is true. Page cache truncation (via
> truncate, or fallocate PUNCH_HOLE) takes the folio lock for each folio
> that is about to be truncated out. Mapping folios takes the folio lock
> as well, except in the fork() case where a myriad of weird interval tree
> + PTE lock interactions make it safe (AIUI).
Can this be perhaps somehow related to the fixes in partial large folio
truncation Zhang Yi is working on, possibly even the tmpfs bug in handling
of folio split I've found [1]? It seems large folios are used here so that
matches, I just don't immediately see how those bugs would lead to the
errors reported here...
Honza
[1] https://lore.kernel.org/all/5pthbyxtn7q6xi4fmkofvksmcjzfnujcw2g4fxmxjzfin5pbgf@zui3vcimb4cv
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters
2026-09-24 8:34 ` Pedro Falcato
2026-09-24 9:15 ` Jan Kara
@ 2026-09-24 9:30 ` Baolin Wang
1 sibling, 0 replies; 5+ messages in thread
From: Baolin Wang @ 2026-09-24 9:30 UTC (permalink / raw)
To: Pedro Falcato, Ayush Ranjan
Cc: Hugh Dickins, Matthew Wilcox, Andrew Morton, Jan Kara,
David Hildenbrand, Gregory Price, linux-mm, linux-fsdevel,
linux-kernel
On 9/24/26 4:34 PM, Pedro Falcato wrote:
> (please use the email I actually use for work, thanks; not sure how
> you got to that one)
>
> Hi,
>
> On Thu, Sep 24, 2026 at 06:16:21AM +0000, Ayush Ranjan wrote:
>> Hi,
>>
>> We are seeing shmem/tmpfs page cache corruption on production hosts
>> running a workload that punches holes in a memfd (hole-punch based
>> memory reclaim) while other threads and forked children fault the
>> same MAP_SHARED mapping. The kernel taints but does not oops:
>>
>> BUG: Bad page cache in process ... pfn:...
>> page dumped because: still mapped when deleted
>> ...
>> dentry name(?): "memfd:..."
>>
>> and, more frequently, a paired rss-counter imbalance when the mm is
>> torn down, always exactly one PMD-order folio (512 pages):
>>
>> BUG: Bad rss-counter state mm:... type:MM_FILEPAGES val:-512
>> BUG: Bad rss-counter state mm:... type:MM_SHMEMPAGES val:512
>>
>> Seen on 6.12 and 6.18, x86_64, bare metal and VM, with
>> /sys/kernel/mm/transparent_hugepage/shmem_enabled = always.
>>
>> This looks like the same corruption Gregory reported in March, which
>> as far as I can tell stalled without a fix; that report needed ~100
>> ballooning VMs to reproduce:
>
> Well, I thought (from the lack of replies) that it was probably a bug on
> their side. Perhaps that's not true :/
>
>>
>> https://patchew.org/linux/20260326162611.693539-1-gourry@gourry.net/
>>
>> The reproducer at the end of this mail trips it with a single memfd,
>> no VMs or ballooning, within a few minutes on a large machine, so
>> hopefully it makes the race easier to confirm (and to test a fix
>> against).
>>
>> Here is my best understanding of the race -- corrections welcome:
>>
>> shmem guards faults against an in-progress hole punch with
>> inode->i_private: shmem_fault() -> shmem_falloc_wait() waits while
>> shmem_fallocate(PUNCH_HOLE) holds i_private. But shmem's .map_pages
>> is the generic filemap_map_pages() (shmem_vm_ops /
>> shmem_anon_vm_ops), which does not consult i_private and does not
>> take invalidate_lock, and shmem does not use invalidate_lock to
>> serialize faults against truncation the way regular filesystems do --
>> the i_private + waitq scheme stands in for it, but only shmem_fault()
>> participates in that scheme.
>>
>> So while shmem_fallocate(PUNCH_HOLE) is between
>> unmap_mapping_range() and shmem_truncate_range(), a concurrent
>> fault-around can (re-)install PTEs for folios that are about to be
>> truncated:
>>
>> - filemap_map_pages() samples mm_counter_file(folio) once per batch
>> and applies it with add_mm_counter() after mapping; if the
>> folio's swapbacked state changes while it is concurrently torn
>
> But that cannot happen? We hold the folio lock in filemap_map_pages().
> The folio (naturally) cannot be torn down while we have the folio lock.
>
>> down, the map-time counter (MM_FILEPAGES) and the zap-time
>> counter (MM_SHMEMPAGES) disagree by exactly one folio -- the
>> +/-512 imbalance above.
>>
>> - a folio re-mapped in this window (by fault-around directly, or
>> via a child VMA whose PTEs copy_page_range() installs after
>> unmap_mapping_range() has already walked the i_mmap tree -- the
>> dup_mmap() variant discussed in the earlier thread) can be
>> deleted from the page cache while still mapped -> "still mapped
>> when deleted".
>
> No, I don't think this paragraph is true. Page cache truncation (via
> truncate, or fallocate PUNCH_HOLE) takes the folio lock for each folio
> that is about to be truncated out. Mapping folios takes the folio lock
> as well, except in the fork() case where a myriad of weird interval tree
> + PTE lock interactions make it safe (AIUI).
Agree.
However, I did previously fix a race between filemap_map_pages() and
truncation that caused incorrect folio mappings, and I believe this race
also exists in shmem. Ayush, could you check whether that fix is present
in your kernel?
f58df566524e ("mm: filemap: fix nr_pages calculation overflow in
filemap_map_pages()")
>> Reproducer
>> ----------
>>
>> The race is on PMD-order folios, so khugepaged needs to scan
>> aggressively (with the default 10s scan interval the punched ranges
>> are not re-collapsed fast enough to reproduce quickly):
>>
>> echo always > /sys/kernel/mm/transparent_hugepage/shmem_enabled
>> cd /sys/kernel/mm/transparent_hugepage/khugepaged
>> echo 1 > scan_sleep_millisecs
>> echo 4096 > pages_to_scan
>> echo 511 > max_ptes_none
>>
>> cc -O2 -pthread -o repro repro_shmem_punch_race.c
>> for i in $(seq $(( $(nproc) / 3 ))); do ./repro 60 & done; wait
>> # watch: dmesg -w
>>
>> On a 112-CPU host this trips within ~2-5 minutes; this capture is
>> from 6.12.0-204.92.4.4.3.el9uek.x86_64:
>
> Awesome that you have a reproducer! Have you reproduced this on a mainline
> kernel? Enterprise kernels are not supported upstream.
I've been trying to reproduce the issue on v7.3.0-rc1 for half an hour
now with Ayush's reproducer, but haven't been able to trigger it.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-24 9:30 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 6:16 [BUG] shmem: FALLOC_FL_PUNCH_HOLE vs fault-around race corrupts page cache / rss counters Ayush Ranjan
2026-09-24 7:12 ` David Hildenbrand (Arm)
2026-09-24 8:34 ` Pedro Falcato
2026-09-24 9:15 ` Jan Kara
2026-09-24 9:30 ` Baolin Wang
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®