summaryrefslogtreecommitdiff
path: root/mm
diff options
context:
space:
mode:
authorKemeng Shi <shikemeng@huaweicloud.com>2026-09-07 17:13:53 +0800
committerAndrew Morton <akpm@linux-foundation.org>2026-09-13 21:32:38 -0700
commitba85601f6bcf0629860c342b7dbbfa4e5733be0c (patch)
tree6a1d691f6f234eb42b9a26aebe4f8a2ed0757d16 /mm
parentcb51a30a647ea45a3f53987a743b248fb3954cc4 (diff)
downloadlinux-next-ba85601f6bcf0629860c342b7dbbfa4e5733be0c.tar.gz
linux-next-ba85601f6bcf0629860c342b7dbbfa4e5733be0c.zip
mm, swap: fix potential NULL dereference when trying a sleep table allocation
Patch series "mm, swap: some random fixes and cleanups", v3. This series contains some random fixes and cleanups. More details can be found in respective patches. This patch (of 4): 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 Link: https://lore.kernel.org/20260907091356.53026-1-shikemeng@huaweicloud.com Link: https://lore.kernel.org/20260907091356.53026-2-shikemeng@huaweicloud.com Fixes: b197d41462c2 ("mm/memcg, swap: store cgroup id in cluster table directly") Signed-off-by: Kemeng Shi <shikemeng@huaweicloud.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Cc: Baoquan He <baoquan.he@linux.dev> Cc: Barry Song <baohua@kernel.org> Cc: Chris Li <chrisl@kernel.org> Cc: Nhat Pham <nphamcs@gmail.com> Cc: Kairui Song <kasong@tencent.com> Cc: Luiz Capitulino <luizcap@redhat.com> Cc: Youngjun Park <youngjun.park@lge.com>
Diffstat (limited to 'mm')
-rw-r--r--mm/swapfile.c30
1 files changed, 20 insertions, 10 deletions
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 2c263563b70e..8df8b2c2e5b4 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -416,6 +416,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;
@@ -435,13 +446,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)
@@ -464,14 +469,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;
}
}
@@ -482,9 +485,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;
}