* [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
@ 2026-10-15 6:05 Jiayuan Chen
2026-10-16 1:40 ` Andrew Morton
2026-10-16 2:06 ` Andrew Morton
0 siblings, 2 replies; 7+ messages in thread
From: Jiayuan Chen @ 2026-10-15 6:05 UTC (permalink / raw)
To: linux-mm
Cc: Jiayuan Chen, Jiayuan Chen, Andrew Morton, Chris Li, Kairui Song,
Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park,
linux-kernel
From: Jiayuan Chen <jiayuan.chen@shopee.com>
swap_cache_get_folio() reads the folio from the swap table and takes a
reference with folio_try_get(), without any lock. If the folio is
removed from the swap cache and freed in between, and its memory gets
reused, folio_try_get() still succeeds and we return a page that has
nothing to do with the swap entry:
CPU 0 CPU 1
do_swap_page()
swap_cache_get_folio()
swp_tb = swap_table_get()
folio = swp_tb_to_folio(swp_tb)
madvise(MADV_DONTNEED)
zap_nonpresent_ptes()
swap_put_entries_direct()
swap_put_entries_cluster()
__try_to_reclaim_swap()
swap_cache_del_folio()
folio_put() <- freed
page reused, e.g. as an anon
folio of another task
folio_try_get(folio) <- succeeds
return folio
swap_update_readahead() <- may clear PG_readahead on it
folio_lock_or_retry() <- locks it
folio_matches_swap_entry() fails, unlock and put
filemap_get_entry() does the same lockless lookup for the page cache,
and after folio_try_get() it rechecks the xarray, because the folio may
have been freed and reused in between. swap_cache_get_folio() lost
that recheck when the swap cache moved to the swap table.
So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
takes the folio lock of someone else's page, before
folio_matches_swap_entry() finds out the folio is not ours.
Recheck the slot after folio_try_get() and retry if it no longer holds
this folio.
Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
---
https://sashiko.dev/#/patchset/20261014090821.227366-1-jiayuan.chen@linux.dev
---
mm/swap_state.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/mm/swap_state.c b/mm/swap_state.c
index ebef568cd62e..d9e36c505398 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -92,17 +92,27 @@ void show_swap_cache_info(void)
*/
struct folio *swap_cache_get_folio(swp_entry_t entry)
{
+ struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
+ unsigned int ci_off = swp_cluster_offset(entry);
unsigned long swp_tb;
struct folio *folio;
for (;;) {
- swp_tb = swap_table_get(__swap_entry_to_cluster(entry),
- swp_cluster_offset(entry));
+ swp_tb = swap_table_get(ci, ci_off);
if (!swp_tb_is_folio(swp_tb))
return NULL;
folio = swp_tb_to_folio(swp_tb);
- if (likely(folio_try_get(folio)))
+ if (!folio_try_get(folio))
+ continue;
+ /*
+ * The folio may have been freed and its memory reused before
+ * we got the reference. Make sure it is still in the slot.
+ */
+ swp_tb = swap_table_get(ci, ci_off);
+ if (likely(swp_tb_is_folio(swp_tb) &&
+ swp_tb_to_folio(swp_tb) == folio))
return folio;
+ folio_put(folio);
}
return NULL;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-15 6:05 [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache Jiayuan Chen
@ 2026-10-16 1:40 ` Andrew Morton
2026-10-16 2:10 ` Jiayuan Chen
2026-10-16 2:06 ` Andrew Morton
1 sibling, 1 reply; 7+ messages in thread
From: Andrew Morton @ 2026-10-16 1:40 UTC (permalink / raw)
To: Jiayuan Chen
Cc: linux-mm, Jiayuan Chen, Chris Li, Kairui Song, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> swap_cache_get_folio() reads the folio from the swap table and takes a
> reference with folio_try_get(), without any lock. If the folio is
> removed from the swap cache and freed in between, and its memory gets
> reused, folio_try_get() still succeeds and we return a page that has
> nothing to do with the swap entry:
>
> CPU 0 CPU 1
> do_swap_page()
> swap_cache_get_folio()
> swp_tb = swap_table_get()
> folio = swp_tb_to_folio(swp_tb)
> madvise(MADV_DONTNEED)
> zap_nonpresent_ptes()
> swap_put_entries_direct()
> swap_put_entries_cluster()
> __try_to_reclaim_swap()
> swap_cache_del_folio()
> folio_put() <- freed
> page reused, e.g. as an anon
> folio of another task
> folio_try_get(folio) <- succeeds
> return folio
> swap_update_readahead() <- may clear PG_readahead on it
> folio_lock_or_retry() <- locks it
> folio_matches_swap_entry() fails, unlock and put
>
> filemap_get_entry() does the same lockless lookup for the page cache,
> and after folio_try_get() it rechecks the xarray, because the folio may
> have been freed and reused in between. swap_cache_get_folio() lost
> that recheck when the swap cache moved to the swap table.
>
> So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
> takes the folio lock of someone else's page, before
> folio_matches_swap_entry() finds out the folio is not ours.
>
> Recheck the slot after folio_try_get() and retry if it no longer holds
> this folio.
Thanks.
As always, when fixing a bug please fully describe the worst-case
userspace-visible runtime effects of that bug.
I asked $LLM for this and was told
: When the race occurs, a swap fault can acquire a reference to a
: physical page that has been freed and reused for an unrelated purpose,
: potentially by another process. The fault handler may then clear that
: page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
: and potentially attempt to remove it from an unrelated swap cache.
:
: These operations can interfere with unrelated workloads and may cause
: unexpected stalls, impaired reclaim or writeback, and potentially
: system hangs. The subsequent swap-entry validation normally prevents
: the wrong folio from being mapped into the faulting process, and
: neither data corruption nor a kernel crash has been demonstrated.
Do you agree with this? OK with me adding this to your changelog?
> Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
Given the above, I suggest adding cc:stable.
Do you and maintainers agree?
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-15 6:05 [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache Jiayuan Chen
2026-10-16 1:40 ` Andrew Morton
@ 2026-10-16 2:06 ` Andrew Morton
2026-10-16 2:19 ` Jiayuan Chen
1 sibling, 1 reply; 7+ messages in thread
From: Andrew Morton @ 2026-10-16 2:06 UTC (permalink / raw)
To: Jiayuan Chen
Cc: linux-mm, Jiayuan Chen, Chris Li, Kairui Song, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> swap_cache_get_folio() reads the folio from the swap table and takes a
> reference with folio_try_get(), without any lock. If the folio is
> removed from the swap cache and freed in between, and its memory gets
> reused, folio_try_get() still succeeds and we return a page that has
> nothing to do with the swap entry:
>
> CPU 0 CPU 1
> do_swap_page()
> swap_cache_get_folio()
> swp_tb = swap_table_get()
> folio = swp_tb_to_folio(swp_tb)
> madvise(MADV_DONTNEED)
> zap_nonpresent_ptes()
> swap_put_entries_direct()
> swap_put_entries_cluster()
> __try_to_reclaim_swap()
> swap_cache_del_folio()
> folio_put() <- freed
> page reused, e.g. as an anon
> folio of another task
> folio_try_get(folio) <- succeeds
> return folio
> swap_update_readahead() <- may clear PG_readahead on it
> folio_lock_or_retry() <- locks it
> folio_matches_swap_entry() fails, unlock and put
>
> filemap_get_entry() does the same lockless lookup for the page cache,
> and after folio_try_get() it rechecks the xarray, because the folio may
> have been freed and reused in between. swap_cache_get_folio() lost
> that recheck when the swap cache moved to the swap table.
>
> So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
> takes the folio lock of someone else's page, before
> folio_matches_swap_entry() finds out the folio is not ours.
>
> Recheck the slot after folio_try_get() and retry if it no longer holds
> this folio.
LLM says to me:
: The original folio could conceivably be freed, its physical page
: reused, and the same PFN inserted into the same swap-table slot before
: the second lookup. That would pass the check. However, the returned
: reference would then belong to the newly inserted folio, and the later
: swap-entry validation would still be required. I don't see that as
: invalidating this fix.
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -92,17 +92,27 @@ void show_swap_cache_info(void)
> */
> struct folio *swap_cache_get_folio(swp_entry_t entry)
> {
> + struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
> + unsigned int ci_off = swp_cluster_offset(entry);
> unsigned long swp_tb;
> struct folio *folio;
>
> for (;;) {
> - swp_tb = swap_table_get(__swap_entry_to_cluster(entry),
> - swp_cluster_offset(entry));
> + swp_tb = swap_table_get(ci, ci_off);
> if (!swp_tb_is_folio(swp_tb))
> return NULL;
> folio = swp_tb_to_folio(swp_tb);
> - if (likely(folio_try_get(folio)))
> + if (!folio_try_get(folio))
> + continue;
> + /*
> + * The folio may have been freed and its memory reused before
> + * we got the reference. Make sure it is still in the slot.
> + */
> + swp_tb = swap_table_get(ci, ci_off);
> + if (likely(swp_tb_is_folio(swp_tb) &&
> + swp_tb_to_folio(swp_tb) == folio))
> return folio;
> + folio_put(folio);
> }
>
> return NULL;
This return is now unreachable...
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-16 1:40 ` Andrew Morton
@ 2026-10-16 2:10 ` Jiayuan Chen
2026-10-16 2:22 ` Kairui Song
0 siblings, 1 reply; 7+ messages in thread
From: Jiayuan Chen @ 2026-10-16 2:10 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, Jiayuan Chen, Chris Li, Kairui Song, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
Hi Andrew,
On 10/16/26 9:40 AM, Andrew Morton wrote:
> On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
[...]
>
> So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
> takes the folio lock of someone else's page, before
> folio_matches_swap_entry() finds out the folio is not ours.
>
> Recheck the slot after folio_try_get() and retry if it no longer holds
> this folio.
> Thanks.
>
> As always, when fixing a bug please fully describe the worst-case
> userspace-visible runtime effects of that bug.
>
> I asked $LLM for this and was told
>
> : When the race occurs, a swap fault can acquire a reference to a
> : physical page that has been freed and reused for an unrelated purpose,
> : potentially by another process. The fault handler may then clear that
> : page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
> : and potentially attempt to remove it from an unrelated swap cache.
> :
> : These operations can interfere with unrelated workloads and may cause
> : unexpected stalls, impaired reclaim or writeback, and potentially
> : system hangs. The subsequent swap-entry validation normally prevents
I don't see a way to a system hang: the folio lock waiter is woken when
the real owner unlocks, so the worst case is a stall. I suggest
dropping "and potentially system hangs".
Writeback itself is not affected, only the PG_reclaim hint on where to
put the folio after writeback. So "impaired reclaim or writeback" ->
"impaired reclaim".
So I'd suggest:
: When the race occurs, a swap fault can acquire a reference to a
: physical page that has been freed and reused for an unrelated purpose,
: potentially by another process. The fault handler may then clear that
: page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
: and potentially attempt to remove it from an unrelated swap cache.
:
: These operations can interfere with unrelated workloads and may cause
: unexpected stalls and impaired reclaim. The subsequent swap-entry
: validation normally prevents the wrong folio from being mapped into
: the faulting process, and neither data corruption nor a kernel crash
: has been demonstrated.
> : the wrong folio from being mapped into the faulting process, and
> : neither data corruption nor a kernel crash has been demonstrated.
>
> Do you agree with this? OK with me adding this to your changelog?
>
>> Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
> Given the above, I suggest adding cc:stable.
>
> Do you and maintainers agree?
Agreed for me.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-16 2:06 ` Andrew Morton
@ 2026-10-16 2:19 ` Jiayuan Chen
0 siblings, 0 replies; 7+ messages in thread
From: Jiayuan Chen @ 2026-10-16 2:19 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, Jiayuan Chen, Chris Li, Kairui Song, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
Hi Andrew,
On 10/16/26 10:06 AM, Andrew Morton wrote:
> On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
>> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>>
>> swap_cache_get_folio() reads the folio from the swap table and takes a
>> reference with folio_try_get(), without any lock. If the folio is
>> removed from the swap cache and freed in between, and its memory gets
>> reused, folio_try_get() still succeeds and we return a page that has
>> nothing to do with the swap entry:
>>
>> CPU 0 CPU 1
>> do_swap_page()
>> swap_cache_get_folio()
>> swp_tb = swap_table_get()
>> folio = swp_tb_to_folio(swp_tb)
>> madvise(MADV_DONTNEED)
>> zap_nonpresent_ptes()
>> swap_put_entries_direct()
>> swap_put_entries_cluster()
>> __try_to_reclaim_swap()
>> swap_cache_del_folio()
>> folio_put() <- freed
>> page reused, e.g. as an anon
>> folio of another task
>> folio_try_get(folio) <- succeeds
>> return folio
>> swap_update_readahead() <- may clear PG_readahead on it
>> folio_lock_or_retry() <- locks it
>> folio_matches_swap_entry() fails, unlock and put
>>
>> filemap_get_entry() does the same lockless lookup for the page cache,
>> and after folio_try_get() it rechecks the xarray, because the folio may
>> have been freed and reused in between. swap_cache_get_folio() lost
>> that recheck when the swap cache moved to the swap table.
>>
>> So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
>> takes the folio lock of someone else's page, before
>> folio_matches_swap_entry() finds out the folio is not ours.
>>
>> Recheck the slot after folio_try_get() and retry if it no longer holds
>> this folio.
> LLM says to me:
>
> : The original folio could conceivably be freed, its physical page
> : reused, and the same PFN inserted into the same swap-table slot before
> : the second lookup. That would pass the check. However, the returned
> : reference would then belong to the newly inserted folio, and the later
> : swap-entry validation would still be required. I don't see that as
> : invalidating this fix.
I had the same worry at first, data corruption or something worse, but
after thinking it through I agree. Passing the check there is fine:
the folio we return is the one currently in the slot, which is what
the function promises, and callers still check folio_matches_swap_entry()
under the folio lock before using it. That's why I didn't mention it in
the changelog.
>> --- a/mm/swap_state.c
>> +++ b/mm/swap_state.c
>> @@ -92,17 +92,27 @@ void show_swap_cache_info(void)
>> */
>> struct folio *swap_cache_get_folio(swp_entry_t entry)
>> {
>> + struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
>> + unsigned int ci_off = swp_cluster_offset(entry);
>> unsigned long swp_tb;
>> struct folio *folio;
>>
>> for (;;) {
>> - swp_tb = swap_table_get(__swap_entry_to_cluster(entry),
>> - swp_cluster_offset(entry));
>> + swp_tb = swap_table_get(ci, ci_off);
>> if (!swp_tb_is_folio(swp_tb))
>> return NULL;
>> folio = swp_tb_to_folio(swp_tb);
>> - if (likely(folio_try_get(folio)))
>> + if (!folio_try_get(folio))
>> + continue;
>> + /*
>> + * The folio may have been freed and its memory reused before
>> + * we got the reference. Make sure it is still in the slot.
>> + */
>> + swp_tb = swap_table_get(ci, ci_off);
>> + if (likely(swp_tb_is_folio(swp_tb) &&
>> + swp_tb_to_folio(swp_tb) == folio))
>> return folio;
>> + folio_put(folio);
>> }
>>
>> return NULL;
> This return is now unreachable...
Right, and it never was reachable, the loop has no break. It should have
been dropped earlier...
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-16 2:10 ` Jiayuan Chen
@ 2026-10-16 2:22 ` Kairui Song
2026-10-16 3:00 ` Andrew Morton
0 siblings, 1 reply; 7+ messages in thread
From: Kairui Song @ 2026-10-16 2:22 UTC (permalink / raw)
To: Jiayuan Chen
Cc: Andrew Morton, linux-mm, Jiayuan Chen, Chris Li, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
On Fri, Oct 16, 2026 at 10:16 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
> Hi Andrew,
>
> On 10/16/26 9:40 AM, Andrew Morton wrote:
> > On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> >
> [...]
Hi all,
> >
> > So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
> > takes the folio lock of someone else's page, before
> > folio_matches_swap_entry() finds out the folio is not ours.
> >
> > Recheck the slot after folio_try_get() and retry if it no longer holds
> > this folio.
> > Thanks.
> >
> > As always, when fixing a bug please fully describe the worst-case
> > userspace-visible runtime effects of that bug.
> >
> > I asked $LLM for this and was told
> >
> > : When the race occurs, a swap fault can acquire a reference to a
> > : physical page that has been freed and reused for an unrelated purpose,
> > : potentially by another process. The fault handler may then clear that
> > : page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
> > : and potentially attempt to remove it from an unrelated swap cache.
> > :
> > : These operations can interfere with unrelated workloads and may cause
> > : unexpected stalls, impaired reclaim or writeback, and potentially
> > : system hangs. The subsequent swap-entry validation normally prevents
>
>
> I don't see a way to a system hang: the folio lock waiter is woken when
> the real owner unlocks, so the worst case is a stall. I suggest
> dropping "and potentially system hangs".
>
>
> Writeback itself is not affected, only the PG_reclaim hint on where to
> put the folio after writeback. So "impaired reclaim or writeback" ->
> "impaired reclaim".
>
> So I'd suggest:
>
> : When the race occurs, a swap fault can acquire a reference to a
> : physical page that has been freed and reused for an unrelated purpose,
> : potentially by another process. The fault handler may then clear that
> : page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
> : and potentially attempt to remove it from an unrelated swap cache.
> :
> : These operations can interfere with unrelated workloads and may cause
> : unexpected stalls and impaired reclaim. The subsequent swap-entry
> : validation normally prevents the wrong folio from being mapped into
It's documented that caller always double checks the folio so this
part is harmless from the aspect of data corruption or crash.
> >> Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
> > Given the above, I suggest adding cc:stable.
> >
> > Do you and maintainers agree?
>
> Agreed for me.
>
This is more like a optimization than a bug fix, but it's clean and
easy to follow, Cc'ing stable is fine to me.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
2026-10-16 2:22 ` Kairui Song
@ 2026-10-16 3:00 ` Andrew Morton
0 siblings, 0 replies; 7+ messages in thread
From: Andrew Morton @ 2026-10-16 3:00 UTC (permalink / raw)
To: Kairui Song
Cc: Jiayuan Chen, linux-mm, Jiayuan Chen, Chris Li, Kemeng Shi,
Nhat Pham, Baoquan He, Barry Song, Youngjun Park, linux-kernel
On Fri, 16 Oct 2026 10:22:20 +0800 Kairui Song <ryncsn@gmail.com> wrote:
> On Fri, Oct 16, 2026 at 10:16 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> >
> > Hi Andrew,
> >
> > On 10/16/26 9:40 AM, Andrew Morton wrote:
> > > On Thu, 15 Oct 2026 14:05:27 +0800 Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
> > >
> > [...]
>
> Hi all,
>
> > >
> > > So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
> > > takes the folio lock of someone else's page, before
> > > folio_matches_swap_entry() finds out the folio is not ours.
> > >
> > > Recheck the slot after folio_try_get() and retry if it no longer holds
> > > this folio.
> > > Thanks.
> > >
> > > As always, when fixing a bug please fully describe the worst-case
> > > userspace-visible runtime effects of that bug.
> > >
> > > I asked $LLM for this and was told
> > >
> > > : When the race occurs, a swap fault can acquire a reference to a
> > > : physical page that has been freed and reused for an unrelated purpose,
> > > : potentially by another process. The fault handler may then clear that
> > > : page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
> > > : and potentially attempt to remove it from an unrelated swap cache.
> > > :
> > > : These operations can interfere with unrelated workloads and may cause
> > > : unexpected stalls, impaired reclaim or writeback, and potentially
> > > : system hangs. The subsequent swap-entry validation normally prevents
> >
> >
> > I don't see a way to a system hang: the folio lock waiter is woken when
> > the real owner unlocks, so the worst case is a stall. I suggest
> > dropping "and potentially system hangs".
Yes, I think it's assuming the other-owned folio never gets unlocked.
> >
> > Writeback itself is not affected, only the PG_reclaim hint on where to
> > put the folio after writeback. So "impaired reclaim or writeback" ->
> > "impaired reclaim".
> >
> > So I'd suggest:
> >
> > : When the race occurs, a swap fault can acquire a reference to a
> > : physical page that has been freed and reused for an unrelated purpose,
> > : potentially by another process. The fault handler may then clear that
> > : page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
> > : and potentially attempt to remove it from an unrelated swap cache.
> > :
> > : These operations can interfere with unrelated workloads and may cause
> > : unexpected stalls and impaired reclaim. The subsequent swap-entry
> > : validation normally prevents the wrong folio from being mapped into
>
> It's documented that caller always double checks the folio so this
> part is harmless from the aspect of data corruption or crash.
OK, thanks, adjustment made.
> > >> Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
> > > Given the above, I suggest adding cc:stable.
> > >
> > > Do you and maintainers agree?
> >
> > Agreed for me.
> >
>
> This is more like a optimization than a bug fix, but it's clean and
> easy to follow, Cc'ing stable is fine to me.
Was that an ack ;)
Here's what I now have:
: swap_cache_get_folio() reads the folio from the swap table and takes a
: reference with folio_try_get(), without any lock. If the folio is removed
: from the swap cache and freed in between, and its memory gets reused,
: folio_try_get() still succeeds and we return a page that has nothing to do
: with the swap entry:
:
: CPU 0 CPU 1
: do_swap_page()
: swap_cache_get_folio()
: swp_tb = swap_table_get()
: folio = swp_tb_to_folio(swp_tb)
: madvise(MADV_DONTNEED)
: zap_nonpresent_ptes()
: swap_put_entries_direct()
: swap_put_entries_cluster()
: __try_to_reclaim_swap()
: swap_cache_del_folio()
: folio_put() <- freed
: page reused, e.g. as an anon
: folio of another task
: folio_try_get(folio) <- succeeds
: return folio
: swap_update_readahead() <- may clear PG_readahead on it
: folio_lock_or_retry() <- locks it
: folio_matches_swap_entry() fails, unlock and put
:
: filemap_get_entry() does the same lockless lookup for the page cache, and
: after folio_try_get() it rechecks the xarray, because the folio may have
: been freed and reused in between. swap_cache_get_folio() lost that
: recheck when the swap cache moved to the swap table.
:
: So CPU 0 writes to a folio it doesn't own: it may clear PG_readahead and
: takes the folio lock of someone else's page, before
: folio_matches_swap_entry() finds out the folio is not ours.
:
: When the race occurs, a swap fault can acquire a reference to a
: physical page that has been freed and reused for an unrelated purpose,
: potentially by another process. The fault handler may then clear that
: page's PG_readahead/PG_reclaim flag, wait on or acquire its folio lock,
: and potentially attempt to remove it from an unrelated swap cache.
:
: These operations can interfere with unrelated workloads and may cause
: unexpected stalls and impaired reclaim. The subsequent swap-entry
: validation normally prevents the wrong folio from being mapped into the
: faulting process.
:
: It's documented that caller always double checks the folio so this part
: is harmless from the aspect of data corruption or crash.
:
: Recheck the slot after folio_try_get() and retry if it no longer holds
: this folio.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-16 3:00 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 6:05 [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache Jiayuan Chen
2026-10-16 1:40 ` Andrew Morton
2026-10-16 2:10 ` Jiayuan Chen
2026-10-16 2:22 ` Kairui Song
2026-10-16 3:00 ` Andrew Morton
2026-10-16 2:06 ` Andrew Morton
2026-10-16 2:19 ` Jiayuan Chen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.