[PATCH v3 1/4] mm, swap: Fix potential NULL dereference when trying a sleep table allocation
From: Kemeng Shi
Date: Mon Sep 07 2026 - 05:21:53 EST
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:
swap_alloc_fast swap_cluster_populate()
/* Try a sleep allocation */
spin_unlock(&ci->lock);
swap_cluster_alloc_table()
rcu_assign_pointer(ci->table, table);
ci = swap_cluster_lock(si, offset)
cluster_is_usable(ci, order)
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()
rcu_assign_pointer(ci->table, NULL);
table = rcu_dereference_check(ci->table, lockdep_is_held(&ci->lock));
atomic_long_read(&table[off]); // NULL dereference
Since memory order guarantee between ci->table, as well as between
ci->table and ci->zero_bitmap, fix the issue by making tables
visible at the end of swap_cluster_populate().
Current memory order guarantee is as following:
On write side:
rcu_assign_pointer(ci->table, table) will offer release to ensure
zero_bitmap and memcg_table visible before ci->table.
On read side:
folio_alloc_swap
swap_alloc_fast/swap_alloc_slow
/* ci->table: protected by cluster lock */
swap_cluster_lock
cluster_is_usable
...
__swap_table_set
...
swap_cluster_unlock
mem_cgroup_try_charge_swap
...
/* memcg_table: protected by cluster lock */
swap_cluster_get_and_lock
__swap_cgroup_set
swap_cluster_unlock
swap_writeout
swap_zeromap_folio_set
/* zero_bitmap: protected by cluster lock */
swap_cluster_get_and_lock
__swap_table_set_zero
swap_cluster_unlock
Fixes: b197d41462c2 ("mm/memcg, swap: store cgroup id in cluster table directly")
Signed-off-by: Kemeng Shi <shikemeng@xxxxxxxxxxxxxxx>
---
mm/swapfile.c | 30 ++++++++++++++++++++----------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 53bf01d5f7f1..45b6154f85ad 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -418,6 +418,17 @@ static void swap_cluster_free_table_folio_rcu_cb(struct rcu_head *head)
folio_put(folio);
}
+static void swap_cluster_free_count_table(struct swap_table *table)
+{
+ if (!SWP_TABLE_USE_PAGE) {
+ kmem_cache_free(swap_table_cachep, table);
+ return;
+ }
+
+ call_rcu(&(folio_page(virt_to_folio(table), 0)->rcu_head),
+ swap_cluster_free_table_folio_rcu_cb);
+}
+
static void swap_cluster_free_table(struct swap_cluster_info *ci)
{
struct swap_table *table;
@@ -437,13 +448,7 @@ static void swap_cluster_free_table(struct swap_cluster_info *ci)
return;
rcu_assign_pointer(ci->table, NULL);
- if (!SWP_TABLE_USE_PAGE) {
- kmem_cache_free(swap_table_cachep, table);
- return;
- }
-
- call_rcu(&(folio_page(virt_to_folio(table), 0)->rcu_head),
- swap_cluster_free_table_folio_rcu_cb);
+ swap_cluster_free_count_table(table);
}
static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
@@ -466,14 +471,12 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
if (!table)
return -ENOMEM;
- rcu_assign_pointer(ci->table, table);
-
#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_table(ci);
+ swap_cluster_free_count_table(table);
return -ENOMEM;
}
}
@@ -484,9 +487,16 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
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;
}
#endif
+
+ /*
+ * Make tables visible to cluster_is_usable() after everything is
+ * ready.
+ */
+ rcu_assign_pointer(ci->table, table);
return 0;
}
--
2.36.1