Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Nhat Pham" <nphamcs@gmail.com>,
	linux-mm@kvack.org, akpm@linux-foundation.org,
	axelrasmussen@google.com, baohua@kernel.org,
	baolin.wang@linux.alibaba.com, baoquan.he@linux.dev,
	brauner@kernel.org, chrisl@kernel.org, david@kernel.org,
	hannes@cmpxchg.org, kasong@tencent.com, ljs@kernel.org,
	mhocko@kernel.org, qi.zheng@linux.dev, shakeel.butt@linux.dev,
	shikemeng@huaweicloud.com, weixugc@google.com,
	willy@infradead.org, youngjun.park@lge.com, yuanchu@google.com
Cc: ryncsn@gmail.com
Subject: Re: [PATCH 2/2] mm/swap: publish cluster tables after reacquiring allocator locks
Date: Sat, 07 Nov 2026 16:04:58 +0000	[thread overview]
Message-ID: <sashiko-outbox-203018@kernel.org> (raw)
In-Reply-To: <20261106160432.2552507-3-nphamcs@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] mm/swap: NULL deref in swap_cluster_assert_empty() on stale
  reclaimer race
- [Medium] mm/swap: ABA race on cluster state check in
  swap_cluster_alloc_table()
--

commit 22e220b18bd05df45a828a3170169b92c0c14f76
Author: Nhat Pham <nphamcs@gmail.com>

mm/swap: publish cluster tables after reacquiring allocator locks

This commit keeps newly allocated swap cluster tables private until
swap_cluster_alloc_table() reacquires the allocator and cluster locks, and
abandons installation if a stale reclaimer returned the cluster while the
locks were dropped.

Link: https://lore.kernel.org/all/sashiko-outbox-181084@kernel.org/
Link: https://lore.kernel.org/all/sashiko-outbox-181086@kernel.org/

> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 189f04d8838f3..162a73a9e6638 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c

[ ... ]

> @@ -467,27 +475,49 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
>  		table = kmem_cache_zalloc(swap_table_cachep, gfp);
>  	}
>  	if (!table)
> -		return -ENOMEM;
> +		goto lock;
>  
>  #ifdef CONFIG_MEMCG
>  	if (!mem_cgroup_disabled()) {
> -		VM_WARN_ON_ONCE(ci->memcg_table);
> -		ci->memcg_table = kzalloc_obj(*ci->memcg_table, gfp);
> -		if (!ci->memcg_table) {
> -			swap_cluster_free_count_table(table);
> -			return -ENOMEM;
> -		}
> +		memcg_table = kzalloc_obj(*ci->memcg_table, gfp);
> +		if (!memcg_table)
> +			goto lock;
>  	}
>  #endif
>  
>  #if !SWAP_TABLE_HAS_ZEROFLAG
> -	VM_WARN_ON_ONCE(ci->zero_bitmap);
> -	ci->zero_bitmap = bitmap_zalloc(SWAPFILE_CLUSTER, gfp);
> -	if (!ci->zero_bitmap) {
> -		swap_cluster_free_table(ci);
> -		swap_cluster_free_count_table(table);
> -		return -ENOMEM;
> +	zero_bitmap = bitmap_zalloc(SWAPFILE_CLUSTER, gfp);
> +	if (!zero_bitmap)
> +		goto lock;
> +#endif
> +	ret = 0;
> +
> +lock:
> +	if (!locked) {
> +		if (si) {
> +			local_lock(&percpu_swap_cluster.lock);
> +			if (!(si->flags & SWP_SOLIDSTATE))
> +				spin_lock(&si->global_cluster_lock);
> +		}
> +		spin_lock(&ci->lock);
>  	}
> +	lockdep_assert_held(&ci->lock);
> +
> +	/* A stale reclaimer may have returned the cluster to allocation. */
> +	if (ci->flags != CLUSTER_FLAG_NONE || cluster_table_is_alloced(ci))
> +		ret = -EAGAIN;

[Severity: Medium]
Can a stale reclaimer trigger a NULL pointer dereference in
swap_cluster_assert_empty() while ci->lock is dropped here?

Because ci->table now remains NULL across the sleeping allocation in
swap_cluster_populate(), a stale reclaimer that dropped ci->lock before the
cluster was freed and re-isolated can reacquire ci->lock while ci->count is 0,
ci->flags is CLUSTER_FLAG_NONE, and ci->table is NULL:

CPU0                              CPU1
                                  cluster_reclaim_range()
                                    spin_unlock(&ci->lock)
                                    /* ci is freed and table is freed */
isolate_lock_cluster()
  ci->flags = CLUSTER_FLAG_NONE
swap_cluster_populate()
  /* atomic alloc fails */
  spin_unlock(&ci->lock)
  swap_cluster_alloc_table()
                                    spin_lock(&ci->lock)
                                  ...
                                  relocate_cluster()
                                    free_cluster()
                                      __free_cluster()
                                        swap_cluster_assert_empty()
                                          __swap_table_get()
                                            <-- NULL pointer dereference

In relocate_cluster(), !ci->count and ci->flags != CLUSTER_FLAG_FREE are both
true, so it calls free_cluster() -> __free_cluster() without checking
cluster_table_is_alloced(ci).  With CONFIG_DEBUG_VM enabled,
swap_cluster_assert_empty() calls __swap_table_get(), which dereferences the
NULL ci->table.  With SWP_PAGE_DISCARD enabled, free_cluster() also schedules
a redundant discard before swap_do_scheduled_discard() calls __free_cluster()
on the tableless cluster.

Should relocate_cluster() avoid calling free_cluster() when
!cluster_table_is_alloced(ci)?

[ ... ]

> @@ -571,13 +612,9 @@ swap_cluster_populate(struct swap_info_struct *si,
>  	 * could happen with ignoring the percpu cluster is fragmentation,
>  	 * which is acceptable since this fallback and race is rare.
>  	 */
> -	local_lock(&percpu_swap_cluster.lock);
> -	if (!(si->flags & SWP_SOLIDSTATE))
> -		spin_lock(&si->global_cluster_lock);
> -	spin_lock(&ci->lock);
> -
>  	if (ret) {
> -		move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
> +		if (ret != -EAGAIN)
> +			move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
>  		spin_unlock(&ci->lock);
>  		return NULL;
>  	}

[Severity: Medium]
Can an ABA race on ci->flags and ci->table cause a failed allocator to put a
cluster back onto si->free_clusters while a second allocator still has it
isolated?

When CONFIG_DEBUG_VM and SWP_PAGE_DISCARD are disabled, a stale reclaimer
calling relocate_cluster() -> free_cluster() -> __free_cluster() moves the
tableless cluster back to si->free_clusters.  A second allocator can then
re-isolate it and also drop ci->lock for a sleeping table allocation:

CPU0                       CPU1                       CPU2
swap_cluster_populate()
  spin_unlock(&ci->lock)
  swap_cluster_alloc_table()
    /* alloc fails */
                           relocate_cluster()
                             ...
                             __free_cluster()
                               ci->flags =
                                 CLUSTER_FLAG_FREE
                                                      isolate_lock_cluster()
                                                        ci->flags =
                                                          CLUSTER_FLAG_NONE
                                                      swap_cluster_populate()
                                                        spin_unlock(&ci->lock)
    spin_lock(&ci->lock)
    /* flags == NONE,
     * table == NULL */
    return -ENOMEM
  move_cluster(..., FREE)  <-- puts ci on free_clusters while CPU2 owns it

When CPU0 reacquires ci->lock, ci->flags is back to CLUSTER_FLAG_NONE and
cluster_table_is_alloced(ci) is still false, so swap_cluster_alloc_table()
returns -ENOMEM instead of -EAGAIN and CPU0 moves the cluster back onto
si->free_clusters while CPU2 is still sleeping in swap_cluster_alloc_table().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106160432.2552507-1-nphamcs@gmail.com?part=2


      parent reply	other threads:[~2026-11-07 16:05 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06 16:04 [PATCH 0/2] mm: fix swapcache writeout and cluster table publication Nhat Pham
2026-11-06 16:04 ` [PATCH 1/2] mm: vmscan: allow writeout of swapcache folios without anon mappings Nhat Pham
2026-11-06 16:04 ` [PATCH 2/2] mm/swap: publish cluster tables after reacquiring allocator locks Nhat Pham
2026-11-06 19:59   ` Nhat Pham
2026-11-07 18:02     ` Kairui Song
2026-11-07 16:04   ` sashiko-bot [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=sashiko-outbox-203018@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=brauner@kernel.org \
    --cc=chrisl@kernel.org \
    --cc=david@kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=kasong@tencent.com \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@kernel.org \
    --cc=nphamcs@gmail.com \
    --cc=qi.zheng@linux.dev \
    --cc=ryncsn@gmail.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=shikemeng@huaweicloud.com \
    --cc=weixugc@google.com \
    --cc=willy@infradead.org \
    --cc=youngjun.park@lge.com \
    --cc=yuanchu@google.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox