mm/swapfile.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
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
© 2016 - 2026 Red Hat, Inc.