diff options
| author | Kemeng Shi <shikemeng@huaweicloud.com> | 2026-09-07 17:13:53 +0800 |
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2026-09-13 21:32:38 -0700 |
| commit | ba85601f6bcf0629860c342b7dbbfa4e5733be0c (patch) | |
| tree | 6a1d691f6f234eb42b9a26aebe4f8a2ed0757d16 /mm | |
| parent | cb51a30a647ea45a3f53987a743b248fb3954cc4 (diff) | |
| download | linux-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.c | 30 |
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; } |
