From: Jiayuan Chen <jiayuan.chen@linux.dev>
To: linux-mm@kvack.org, Kairui Song <ryncsn@gmail.com>,
Nhat Pham <nphamcs@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>
Cc: Jiayuan Chen <jiayuan.chen@shopee.com>,
Andrew Morton <akpm@linux-foundation.org>,
Chris Li <chrisl@kernel.org>, Kairui Song <kasong@tencent.com>,
Kemeng Shi <shikemeng@huaweicloud.com>,
Nhat Pham <nphamcs@gmail.com>, Baoquan He <baoquan.he@linux.dev>,
Barry Song <baohua@kernel.org>,
Youngjun Park <youngjun.park@lge.com>,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] mm/swap: fix swap_cache_get_folio() returning a folio that left the swap cache
Date: Fri, 30 Oct 2026 18:11:51 +0800 [thread overview]
Message-ID: <23a3fa60-9489-48ff-ab0e-38a865d6bde7@linux.dev> (raw)
In-Reply-To: <20261015060528.302634-1-jiayuan.chen@linux.dev>
On 10/15/26 2:05 PM, Jiayuan Chen 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.
>
> 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;
I spent some more time on this and I'm not happy with any of the
options I have. Advice welcome.
1. Trigger the worker where the zap gives up (this patch). On the
swap entry side that works, __try_to_reclaim_swap() has the si.
On the present PTE side it doesn't: free_swap_cache() lost the
trylock, so it can't read folio->swap to find the si, as Kairui
pointed out. And that trylock-and-give-up is not in one place,
free_swap_cache(), do_wp_page() and __uprobe_write() all have
their own copy. Chasing every one of them doesn't feel right.
2. Clean up at memcg offline. Doesn't cover the root cgroup or a
cgroup that stays alive, and on mm-new the folios are reparented
at offline anyway, so there is nothing left to find there.
3. A swapon flag, say --clean-cache, that enables the cleanup.
Pro: covers everything, and the cleanup can live in its own
function without touching the existing paths. Con: we know to
turn it on because we hit this, others won't. Needs util-linux
too.
4. A per-device worker that walks the device on its own, say 1024
clusters per second, backing off when a pass finds nothing.
Pro: same as 3, and nothing to turn on. Con: a worker that wakes
up on its own for this, I expect objections.
5. A sysctl (or a drop_caches bit) to kick the cleanup by hand.
Pro: same as 4, all in the worker. Con: same as 3, someone has
to know to poke it.
4 is the one I'd pick if nobody objects. Thoughts?
next prev parent reply other threads:[~2026-10-30 10:12 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
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-20 18:07 ` Kairui Song
2026-10-16 2:06 ` Andrew Morton
2026-10-16 2:19 ` Jiayuan Chen
2026-10-30 10:11 ` Jiayuan Chen [this message]
2026-10-30 10:17 ` Jiayuan Chen
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=23a3fa60-9489-48ff-ab0e-38a865d6bde7@linux.dev \
--to=jiayuan.chen@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baoquan.he@linux.dev \
--cc=chrisl@kernel.org \
--cc=jiayuan.chen@shopee.com \
--cc=kasong@tencent.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=ryncsn@gmail.com \
--cc=shikemeng@huaweicloud.com \
--cc=youngjun.park@lge.com \
/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 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.