The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Youngjun Park <youngjun.park@lge.com>
To: Kemeng Shi <shikemeng@huaweicloud.com>
Cc: chrisl@kernel.org, kasong@tencent.com, nphamcs@gmail.com,
	baoquan.he@linux.dev, baohua@kernel.org, linux-mm@kvack.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH 1/4] mm, swap: Fix potential NULL dereference when trying a sleep table allocation
Date: Tue, 21 Jul 2026 11:15:40 +0900	[thread overview]
Message-ID: <al7WTNXanPVSELAH@yjaykim-PowerEdge-T330> (raw)
In-Reply-To: <0884d36c-06b3-4df1-8fd2-8abc9e6c97ee@huaweicloud.com>

On Tue, Jul 21, 2026 at 09:49:53AM +0800, Kemeng Shi wrote:
> 在 2026/7/20 17:11:54, Youngjun Park 写道:
> > On Mon, Jul 20, 2026 at 03:13:39PM +0800, Kemeng Shi wrote:
> > 
> > Hello Kemeng Shi,
> > 
> > Good catch!
> > 
> > it indeed looks like a possible race condition 
> > (though I haven't verified it at runtime either).
> > 
> >> The root cause of this issue is because multi-tables are updated in non
> >> atomic context. To be more specific, the issue could be triggerred as
> >> following:
> > 
> > Here the cluster is isolated (CLUSTER_FLAG_NONE).
> > 
> >> swap_alloc_fast         swap_cluster_populate()
> >>                          /* Try a sleep allocation */
> >>                          spin_unlock(&ci->lock);
> > 
> > And from here, the table becomes visible,
> > 
> >>                          swap_cluster_alloc_table()
> >>                           rcu_assign_pointer(ci->table, table);
> > 
> > All entry on this cluster is freed(e.g process dead) , 
> > so this might be the free cluster.
> > 
> >>  ci = swap_cluster_lock(si, offset)
> >>  cluster_is_usable(ci, order)
> > 
> > Since it's a normal cluster (CLUSTER_FLAG_NONE) and the table exists,
> > it passes here...
> > 
> >>   if (!cluster_table_is_alloced(ci)) // ok 
> >>  alloc_swap_scan_cluster()
> >>   cluster_scan_range()
> >>    __swap_table_get()
> >>
> >>                           /* free table when more table allocation fails */
> >>                           ci->memcg_table = kzalloc_obj(*ci->memcg_table,
> >>                                                         gfp);
> >>                           if (!ci->memcg_table)
> >>                            swap_cluster_free_table()
> > 
> > Nullified
> > 
> >>                             rcu_assign_pointer(ci->table, NULL);
> > 
> > Now it happens.
> > 
> >>     table = rcu_dereference_check(ci->table, lockdep_is_held(&ci->lock));
> >>      atomic_long_read(&table[off]); // NULL dereference
> >>
> >> Fix the issue by updating allocated tables in atomic context.
> >>
> >> Fixes: 2fe7a6f5024b8 ("mm/memcg, swap: store cgroup id in cluster table directly")
> >> Signed-off-by: Kemeng Shi <shikemeng@huaweicloud.com>
> >> ---
> >>  mm/swapfile.c | 21 ++++++++++++++++++++-
> >>  1 file changed, 20 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/mm/swapfile.c b/mm/swapfile.c
> >> index 615d90867111..d29062d9c3cd 100644
> >> --- a/mm/swapfile.c
> >> +++ b/mm/swapfile.c
> >> @@ -490,6 +490,20 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
> >>  	return 0;
> >>  }
> >>  
> >> +static void swap_cluster_copy_table(struct swap_cluster_info *d_ci,
> >> +				    struct swap_cluster_info *s_ci)
> >> +{
> >> +	rcu_assign_pointer(d_ci->table, rcu_access_pointer(s_ci->table));
> >> +
> >> +#ifdef CONFIG_MEMCG
> >> +	d_ci->memcg_table = s_ci->memcg_table;
> >> +#endif
> >> +
> >> +#if !SWAP_TABLE_HAS_ZEROFLAG
> >> +	d_ci->zero_bitmap = s_ci->zero_bitmap;
> >> +#endif
> >> +}
> >> +
> >>  /*
> >>   * Sanity check to ensure nothing leaked, and the specified range is empty.
> >>   * One special case is that bad slots can't be freed, so check the number of
> >> @@ -527,6 +541,7 @@ static struct swap_cluster_info *
> >>  swap_cluster_populate(struct swap_info_struct *si,
> >>  			 struct swap_cluster_info *ci)
> >>  {
> >> +	struct swap_cluster_info tmp_ci;
> >>  	int ret;
> > 
> Hello, Youngjun
> 
> Thanks for feedback.> IMHO, How about resolving everything inside swap_cluster_alloc_table() instead?
> > 
> > We could consider the following two things.
> > 
> > - Assign the table at the very end inside swap_cluster_alloc_table().
> > - Handle the table freeing properly if subsequent allocations fail.
> > 
> > Rather than allocating a temp_ci (which adds special handling
> > for this case), wouldn't it be better to maintain the original intention
> > of the swap_cluster_alloc_table() function?
> > 
> > Although this is not a hot path and table allocation will usually succeed with
> > the ATOMIC allocator, this approach would also save the memcpy overhead
> > and stack memory usage.
> Yes, it will be better to handle the issue inside the swap_cluster_alloc_table()
> and I also consider this before. However, I didn't find a way to properly handle
> the lock as swap_cluster_alloc_table() will be used for both ATOMIC allocation
> and sleep allocation. So what about introduce a new helper like:
> swap_cluster_alloc_table_unlocked()
> {
> 	table = kmem_cache_zalloc()
> #ifdef CONFIG_MEMCG
> 	memcg_table = kzalloc_obj()
> #endif
> #if !SWAP_TABLE_HAS_ZEROFLAG
> 	zero_bitmap = bitmap_zalloc()
> #endif
> 
> 	spin_lock(&ci->lock);
> 	/* update all tables in atomic */
> 	spin_unlock(&ci->lock);
> }
> In this way, there are some repeated code, so it is also a little ugly...
> I will appreciate if you have any better idea!

Hello!

I don't quite understand why we need to introduce a new helper function.
Is it because the table assignment must be done strictly inside the lock?
In successful cases, wouldn't it be fine to assign the table without holding
the lock? (Please let me know if I am missing something here.)

If we just add error handling to free the allocated table inside
swap_cluster_alloc_table(), swap_cluster_free_table() will properly free
memcg_table and zero_bitmap as long as ci->table is not assigned yet.
I don't think this will cause any issues.

My intention is something like this:

-       rcu_assign_pointer(ci->table, table);     /* Remove this */
...

#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) {
			+ free table
                        swap_cluster_free_table(ci);
                        return -ENOMEM;
                }
        }
#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) {
                + free table
                swap_cluster_free_table(ci);
                return -ENOMEM;
        }
#endif

...
        /* Assign here! */
        rcu_assign_pointer(ci->table, table);

	return
}
If it becomes visible at the very end, wouldn't it be safe from any issues?

Youngjun

  reply	other threads:[~2026-07-21  2:15 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-20  7:13 [PATCH 0/4] mm, swap: some random fixes and cleanups Kemeng Shi
2026-07-20  7:13 ` [PATCH 1/4] mm, swap: Fix potential NULL dereference when trying a sleep table allocation Kemeng Shi
2026-07-20  9:11   ` Youngjun Park
2026-07-21  1:49     ` Kemeng Shi
2026-07-21  2:15       ` Youngjun Park [this message]
2026-07-21  6:12         ` Kemeng Shi
2026-07-20  9:11   ` Kairui Song
2026-07-21  2:21     ` Kemeng Shi
2026-07-20  7:13 ` [PATCH 2/4] mm, swap: Move setup_swap_clusters_info() after SWP_SOLIDSTATE initialization Kemeng Shi
2026-07-20 15:54   ` Luiz Capitulino
2026-07-21  2:19     ` Kemeng Shi
2026-07-20  7:13 ` [PATCH 3/4] mm, swap: return early from swap_extend_table_try_free() on first non-zero entry Kemeng Shi
2026-07-21  4:07   ` Youngjun Park
2026-07-20  7:13 ` [PATCH 4/4] mm, swap: Remove unneeded swap_extend_table_try_free() in swap_dup_entries_cluster() Kemeng Shi
2026-07-21  7:58   ` Youngjun Park
2026-08-20 11:46     ` Kemeng Shi

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=al7WTNXanPVSELAH@yjaykim-PowerEdge-T330 \
    --to=youngjun.park@lge.com \
    --cc=baohua@kernel.org \
    --cc=baoquan.he@linux.dev \
    --cc=chrisl@kernel.org \
    --cc=kasong@tencent.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=nphamcs@gmail.com \
    --cc=shikemeng@huaweicloud.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