[RFC PATCH] mm/swap: fix swap table count encoding for empty slots

gaoguixing posted 1 patch an hour ago
mm/swapfile.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
[RFC PATCH] mm/swap: fix swap table count encoding for empty slots
Posted by gaoguixing an hour ago
While stress-testing a local adaptation of the swap table series, the
kernel reported unused swap offsets followed by a NULL pointer dereference
in __swap_cache_do_del_folio().

swap_table_count() returns a pure count.  However, when initializing a NULL
table entry, __swap_table_update_count() passes that count to
shadow_to_swp_tb(), whose second argument is the complete swap table flags
value and is encoded using SWP_TB_FLAGS_SHIFT.

The flags field includes SWP_TB_ZERO_FLAG below the count bits.  As a
result, passing a count of one sets the zero flag while leaving the decoded
count at zero.  A live swap slot can then appear unused to
__swap_cache_add_check().

Initialize the entry as an empty shadow without flags, then set the count
using __swp_tb_mk_count(), which encodes it with SWP_TB_COUNT_SHIFT while
preserving the entry type and zero flag.

The failure was observed in a QEMU x86_64 guest with about 8 GiB of RAM,
Linux 6.18.44 #7 and MGLRU enabled with 0x0007.  The workload used a 2 GiB
memory cgroup, wrote 2600 MiB of anonymous memory, read a 512 MiB hot file,
scanned a 4096 MiB cold file and then read the hot file again.  The kernel
reported:

  _swap_info_get: Unused swap offset entry 0005f948
  _swap_info_get: Unused swap offset entry 0005f94a
  BUG: kernel NULL pointer dereference, address: 0000000000000a50
  #PF: supervisor read access in kernel mode
  #PF: error_code(0x0000) - not-present page
  Oops: Oops: 0000 [#2] SMP NOPTI
  CPU: 1 UID: 0 PID: 4077 Comm: python3
  RIP: 0010:__swap_cache_do_del_folio+0xbb/0x1f0
  RAX: 0000000000000a50 RBX: ffff8eddd6308f40
  RCX: 0000000000000a50 RDX: 0000000000000001
  RSI: 000000000000014a RDI: 0000000000000000
  R12: 000000000000014a R13: 000000000000014b
  R14: 0000000000000a50 R15: fffffa8747b25580
  CR2: 0000000000000a50
  Relevant backtrace:
    __swap_cache_del_folio
    swap_cache_del_folio
    folio_free_swap
    free_swap_cache
    free_pages_and_swap_cache
    tlb_flush_mmu
    zap_pte_range
    zap_pmd_range.isra.0
    unmap_page_range
    unmap_single_vma.constprop.0
    unmap_vmas
    exit_mmap
    __mmput
    mmput
    exit_mm
    do_exit
    make_task_dead
    rewind_stack_and_make_dead
  note: python3[4077] exited with irqs disabled
  note: python3[4077] exited with preempt_count 1
  Fixing recursive fault but reboot is needed!

CR2 was 0xa50 and the swap table offset was 0x14a; 0x14a * 8 is 0xa50.
This is consistent with __swap_table_get() dereferencing table[ci_off]
while ci->table was NULL.  The unused-offset warnings show that swap slot
state was already inconsistent before the NULL dereference.

This patch is an RFC fixup for a pre-squash local adaptation.
__swap_table_update_count() is not present in the original v5 series, so
this diff is intended for review and fold-in rather than direct
application.

The squashed local commit 99b07072a9ca ("mm/mglru: introduce frequency
guided promotion (MGLRU-FG)") already contains this correction.

The broken intermediate commit was not published, while the public squashed
commit already contains the correction.  Therefore, no Fixes tag is
provided for this RFC.

The corrected local tree, which also contains a separate cache-pin rollback
fix, completed 15 out of 15 runs of the same pressure test without a BUG,
WARN, Oops or NULL-pointer signature.  Therefore this result validates the
combined corrected tree, not this patch in isolation.

Link: https://lore.kernel.org/20260517-swap-table-p4-v5-0-88ae43e064c7@tencent.com/
Link: https://gitea.cicd.getdeepin.org/ggx/kernel-rolling/commit/99b07072a9ca56420773bbdd052e894601c2b861
Signed-off-by: gaoguixing <gaoguixing@uniontech.com>
---
 mm/swapfile.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index d6c5c25755be..02fd5ad94185 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -163,16 +163,16 @@ static void __swap_table_update_count(struct swap_cluster_info *ci,
 				      unsigned char count)
 {
 	unsigned long swp_tb = __swap_table_get(ci, ci_off);
-	unsigned char flags = swap_table_count(count);
+	unsigned char tb_count = swap_table_count(count);
 
 	if (WARN_ON_ONCE(swp_tb_is_bad(swp_tb)))
 		return;
-	if (!flags && swp_tb_is_null(swp_tb))
+	if (!tb_count && swp_tb_is_null(swp_tb))
 		return;
-	if (flags && swp_tb_is_null(swp_tb))
-		swp_tb = shadow_to_swp_tb(NULL, flags);
-	else
-		swp_tb = __swp_tb_mk_count(swp_tb, flags);
+	if (tb_count && swp_tb_is_null(swp_tb))
+		swp_tb = shadow_to_swp_tb(NULL, 0);
+
+	swp_tb = __swp_tb_mk_count(swp_tb, tb_count);
 	__swap_table_set(ci, ci_off, swp_tb);
 }
 
-- 
2.50.1