WARNING in deferred_split_folio
Subsystems (3)
syzbot
syzbotHello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48fa R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline] migrate_folios_move mm/migrate.c:1740 [inline] migrate_pages_batch+0x319f/0x4c40 mm/migrate.c:1996 migrate_pages_sync mm/migrate.c:2026 [inline] migrate_pages+0x1c74/0x2a10 mm/migrate.c:2135 do_mbind mm/mempolicy.c:1614 [inline] kernel_mbind mm/mempolicy.c:1757 [inline] __do_sys_mbind mm/mempolicy.c:1831 [inline] __se_sys_mbind+0xe89/0x10f0 mm/mempolicy.c:1827 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f32e239c819 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f32e32a7028 EFLAGS: 00000246 ORIG_RAX: 00000000000000ed RAX: ffffffffffffffda RBX: 00007f32e2616090 RCX: 00007f32e239c819 RDX: 0000000000000000 RSI: 0000000000800000 RDI: 0000200000001000 RBP: 00007f32e2432c91 R08: 0000000000000000 R09: 0000000000000002 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f32e2616128 R14: 00007f32e2616090 R15: 00007ffe30c61cc8 </TASK>
---
This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at [email protected].
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
Request received
Your request (139) has been received and is being reviewed by our support staff.
To add additional comments, reply to this email.
This email is a service from Yail. Delivered by Zendesk
<https://www.zendesk.com/support/?utm_campaign=text&utm_content=Yail&utm_medium=poweredbyzendesk&utm_source=email-notification>
syzbot
syzbotForwarded: [PATCH] mm/migrate: fix stale partially_mapped arg to deferred_split_folio()
In reply to syzbotFor archival purposes, forwarding an incoming command email to [email protected], [email protected].
For archival purposes, forwarding an incoming command email to [email protected], [email protected].
***
Subject: [PATCH] mm/migrate: fix stale partially_mapped arg to deferred_split_folio()
Author: [email protected]
#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
In migrate_folio_move(), src_partially_mapped is sampled from the source folio before move_to_new_folio() is called:
if (folio_order(src) > 1 &&
!data_race(list_empty(&src->_deferred_list))) {
src_deferred_split = true;
src_partially_mapped = folio_test_partially_mapped(src);
}A concurrent thread can unmap pages from the source folio between this read and the actual migration, making the sampled value stale.
After move_to_new_folio() succeeds, __folio_migrate_mapping() has already copied all folio flags including PG_partially_mapped from src to dst. Passing the stale src_partially_mapped=false to deferred_split_folio(dst) while dst already has PG_partially_mapped=true triggers the invariant check in deferred_split_folio():
VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio)
at mm/huge_memory.c:4371, because the argument contradicts the flag already set on the folio.
Fix this by removing the src_partially_mapped variable entirely and reading PG_partially_mapped directly from dst after move_to_new_folio() completes, where it is authoritative and race-free.
--- mm/migrate.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
mm/migrate.c +5 -5
@@ -1361,7 +1361,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,int old_page_state =0 ;struct anon_vma *anon_vma =NULL ;bool src_deferred_split = false; -bool src_partially_mapped = false;struct list_head *prev;__migrate_folio_extract (dst, &old_page_state, &anon_vma);
@@ -1378,7 +1377,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (folio_order (src) >1 && !data_race (list_empty (&src->_deferred_list))) { src_deferred_split = true; - src_partially_mapped =folio_test_partially_mapped (src); } rc =move_to_new_folio (dst, src, mode);
@@ -1404,11 +1402,13 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,/* * Requeue the destination folio on the deferred split queue if * the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). +* __folio_migrate_mapping(). Read PG_partially_mapped directly from +* dst: move_to_new_folio() copies all flags from src to dst, so dst +* now holds the correct authoritative state. The pre-migration value +* sampled from src is racy and must not be used. */ if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); +deferred_split_folio (dst,folio_test_partially_mapped (dst)); out_unlock_both:folio_unlock (dst);
Signature · 1 line
2.43.0
Lance Yang
reviewer+Cc Usama
Quoted 50 lines · syzbot
Hello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xzIMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by [email protected]free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48fa
Quoted 8 lines
R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline]
Looks like a race introduced by commit[1] ("mm: migrate: requeue destination folio on deferred split queue").
Between folio migration (mbind) and rmap removal (exit_mmap), I guess :)
migrate_folio_move() snapshots src_partially_mapped from src before migration:
if (folio_order(src) > 1 &&
!data_race(list_empty(&src->_deferred_list))) {
src_deferred_split = true;
src_partially_mapped = folio_test_partially_mapped(src);
}Then move_to_new_folio() eventually unqueues src in __folio_migrate_mapping():
folio_unqueue_deferred_split(src);
After that, migration restores mappings to dst:
if (old_page_state & PAGE_WAS_MAPPED) remove_migration_ptes(src, dst, 0);
At that point, dst is already visible again. A concurrent unmap path from another sharer can then remove some of those mappings and reach deferred_split_folio(dst, true), which sets PG_partially_mapped on dst.
Migration then resumes and does:
if (src_deferred_split) deferred_split_folio(dst, src_partially_mapped);
If the earlier snapshot from src was false, this becomes deferred_split_folio(dst, false), but dst may already have been marked partially mapped by the concurrent rmap-removal path, so the WARN in deferred_split_folio() fires:
if (partially_mapped) {
...
} else {
/* partially mapped folios cannot become non-partially mapped */
VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio);
}[1] https://lore.kernel.org/all/[email protected]/
Quoted 48 lines
migrate_folios_move mm/migrate.c:1740 [inline] migrate_pages_batch+0x319f/0x4c40 mm/migrate.c:1996 migrate_pages_sync mm/migrate.c:2026 [inline] migrate_pages+0x1c74/0x2a10 mm/migrate.c:2135 do_mbind mm/mempolicy.c:1614 [inline] kernel_mbind mm/mempolicy.c:1757 [inline] __do_sys_mbind mm/mempolicy.c:1831 [inline] __se_sys_mbind+0xe89/0x10f0 mm/mempolicy.c:1827 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f32e239c819 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f32e32a7028 EFLAGS: 00000246 ORIG_RAX: 00000000000000ed RAX: ffffffffffffffda RBX: 00007f32e2616090 RCX: 00007f32e239c819 RDX: 0000000000000000 RSI: 0000000000800000 RDI: 0000200000001000 RBP: 00007f32e2432c91 R08: 0000000000000000 R09: 0000000000000002 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f32e2616128 R14: 00007f32e2616090 R15: 00007ffe30c61cc8 </TASK>---
This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at [email protected].syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-titleIf you want syzbot to run the reproducer, reply with:
#syz test:git://repo/address.gitbranch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-reportIf you want to undo deduplication, reply with:
#syz undup
Lance Yang
reviewerQuoted 102 lines · Lance Yang
+Cc Usama
Quoted 50 lines · syzbot
Hello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xzIMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by [email protected]free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48faQuoted 8 lines
R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline]Looks like a race introduced by commit[1] ("mm: migrate: requeue destination folio on deferred split queue").
Between folio migration (mbind) and rmap removal (
exit_mmap), I guess :)
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration:if (folio_order(src) > 1 && !data_race(list_empty(&src->_deferred_list))) { src_deferred_split = true; src_partially_mapped = folio_test_partially_mapped(src); }Then
move_to_new_folio()eventually unqueues src in__folio_migrate_mapping():folio_unqueue_deferred_split(src);After that, migration restores mappings to dst:
if (old_page_state & PAGE_WAS_MAPPED) remove_migration_ptes(src, dst, 0);At that point, dst is already visible again. A concurrent unmap path from another sharer can then remove some of those mappings and reach
deferred_split_folio(dst, true), which setsPG_partially_mappedon dst.Migration then resumes and does:
if (src_deferred_split) deferred_split_folio(dst, src_partially_mapped);If the earlier snapshot from src was false, this becomes
deferred_split_folio(dst, false), but dst may already have been marked partially mapped by the concurrent rmap-removal path, so the WARN indeferred_split_folio()fires:
Quoted 9 lines
if (partially_mapped) {
...
} else {
/* partially mapped folios cannot become non-partially mapped */
VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio);
}Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once remove_migration_ptes(src, dst, 0) makes dst visible again, this race looks hard to avoid.
So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning in deferred_split_folio()
From: Lance Yang <[email protected]>
migrate_folio_move() snapshots src_partially_mapped from src before migration and later requeues dst after remove_migration_ptes(src, dst, 0).
Once dst is visible again, a competing rmap-removal path can legally set PG_partially_mapped before the migration path reaches deferred_split_folio(dst, src_partially_mapped).
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once remove_migration_ptes(src, dst, 0) makes dst visible again, this race looks hard to avoid.
So just drop the WARN in the !partially_mapped path and preserve an already-set PG_partially_mapped bit.
--- mm/huge_memory.c | 3 --- 1 file changed, 3 deletions(-)
mm/huge_memory.c +0 -3
@@ -4433,9 +4433,6 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)mod_mthp_stat (folio_order (folio),MTHP_STAT_NR_ANON_PARTIALLY_MAPPED ,1 ); } - }else { -/* partially mapped folios cannot become non-partially mapped */ -VM_WARN_ON_FOLIO (folio_test_partially_mapped (folio), folio); }if (list_empty (&folio->_deferred_list)) {struct mem_cgroup *memcg;
---
Thanks,
Lance
David Hildenbrand (Arm)
maintainerQuoted 141 lines · Lance Yang
Quoted 102 lines · Lance Yang
+Cc Usama
Quoted 50 lines · syzbot
Hello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xzIMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by [email protected]free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48faQuoted 8 lines
R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline]Looks like a race introduced by commit[1] ("mm: migrate: requeue destination folio on deferred split queue").
Between folio migration (mbind) and rmap removal (
exit_mmap), I guess :)
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration:if (folio_order(src) > 1 && !data_race(list_empty(&src->_deferred_list))) { src_deferred_split = true; src_partially_mapped = folio_test_partially_mapped(src); }Then
move_to_new_folio()eventually unqueues src in__folio_migrate_mapping():folio_unqueue_deferred_split(src);After that, migration restores mappings to dst:
if (old_page_state & PAGE_WAS_MAPPED) remove_migration_ptes(src, dst, 0);At that point, dst is already visible again. A concurrent unmap path from another sharer can then remove some of those mappings and reach
deferred_split_folio(dst, true), which setsPG_partially_mappedon dst.Migration then resumes and does:
if (src_deferred_split) deferred_split_folio(dst, src_partially_mapped);If the earlier snapshot from src was false, this becomes
deferred_split_folio(dst, false), but dst may already have been marked partially mapped by the concurrent rmap-removal path, so the WARN indeferred_split_folio()fires:Quoted 9 lines
if (partially_mapped) { ... } else { /* partially mapped folios cannot become non-partially mapped */ VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); }Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning indeferred_split_folio()From: Lance Yang <[email protected]>
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration and later requeues dst afterremove_migration_ptes(src, dst, 0).Once dst is visible again, a competing rmap-removal path can legally set
PG_partially_mappedbefore the migration path reachesdeferred_split_folio(dst, src_partially_mapped).Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.
Quoted 7 lines
So just drop the WARN in the !partially_mapped path and preserve an already-set
PG_partially_mappedbit.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Signed-off-by Lance Yang <[email protected]>
A fix just appeared:
https://lore.kernel.org/r/[email protected]
Have to think about this :)
Signature · 3 lines
Cheers, David
David Hildenbrand (Arm)
maintainerQuoted 157 lines · Lance Yang
Quoted 102 lines · Lance Yang
+Cc Usama
Quoted 50 lines · syzbot
Hello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xzIMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by [email protected]free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48faQuoted 8 lines
R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline]Looks like a race introduced by commit[1] ("mm: migrate: requeue destination folio on deferred split queue").
Between folio migration (mbind) and rmap removal (
exit_mmap), I guess :)
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration:if (folio_order(src) > 1 && !data_race(list_empty(&src->_deferred_list))) { src_deferred_split = true; src_partially_mapped = folio_test_partially_mapped(src); }Then
move_to_new_folio()eventually unqueues src in__folio_migrate_mapping():folio_unqueue_deferred_split(src);After that, migration restores mappings to dst:
if (old_page_state & PAGE_WAS_MAPPED) remove_migration_ptes(src, dst, 0);At that point, dst is already visible again. A concurrent unmap path from another sharer can then remove some of those mappings and reach
deferred_split_folio(dst, true), which setsPG_partially_mappedon dst.Migration then resumes and does:
if (src_deferred_split) deferred_split_folio(dst, src_partially_mapped);If the earlier snapshot from src was false, this becomes
deferred_split_folio(dst, false), but dst may already have been marked partially mapped by the concurrent rmap-removal path, so the WARN indeferred_split_folio()fires:Quoted 9 lines
if (partially_mapped) { ... } else { /* partially mapped folios cannot become non-partially mapped */ VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); }Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning indeferred_split_folio()From: Lance Yang <[email protected]>
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration and later requeues dst afterremove_migration_ptes(src, dst, 0).Once dst is visible again, a competing rmap-removal path can legally set
PG_partially_mappedbefore the migration path reachesdeferred_split_folio(dst, src_partially_mapped).Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So just drop the WARN in the !partially_mapped path and preserve an already-set
PG_partially_mappedbit.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Signed-off-by Lance Yang <[email protected]>--- mm/huge_memory.c | 3 --- 1 file changed, 3 deletions(-)mm/huge_memory.c +0
-0
mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4436
Quoted 7 lines
mm/huge_memory.c +0
-3@@ -4433,9 +4433,6 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)mod_mthp_stat (folio_order (folio),MTHP_STAT_NR_ANON_PARTIALLY_MAPPED ,1 ); } - }else { -/* partially mapped folios cannot become non-partially mapped */ -VM_WARN_ON_FOLIO (folio_test_partially_mapped (folio), folio); }
Can't we simply move the setting before restoring migration ptes?
mm/migrate.c +9 -10
@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will
@@ -1400,16 +1409,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); - -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); - out_unlock_both:folio_unlock (dst);folio_set_owner_migrate_reason (dst, reason);
Signature · 3 lines
Cheers, David
Lance Yang
reviewer+Cc Deepanshu
Quoted 160 lines · David Hildenbrand (Arm)
Quoted 159 lines · Lance Yang
Quoted 102 lines · Lance Yang
+Cc Usama
Quoted 50 lines · syzbot
Hello,
syzbot found the following issue on:
HEAD commit: cf7c3c02fdd0 Add linux-next specific files for 20260330 git tree: linux-next console output: https://syzkaller.appspot.com/x/log.txt?x=154ee46a580000 kernel config: https://syzkaller.appspot.com/x/.config?x=3944d875fa9bfb67 dashboard link: https://syzkaller.appspot.com/bug?extid=a7067a757858ac8eb085 compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=12c846ba580000Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/053d3b49a360/disk-cf7c3c02.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/faabb37d41d0/vmlinux-cf7c3c02.xz
kernel image: https://storage.googleapis.com/syzbot-assets/8d47fe92aaa8/bzImage-cf7c3c02.xzIMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by [email protected]free_pages_and_swap_cache+0x2b9/0x490 mm/swap_state.c:401 __tlb_batch_free_encoded_pages mm/mmu_gather.c:138 [inline] tlb_batch_pages_flush mm/mmu_gather.c:151 [inline] tlb_flush_mmu_free mm/mmu_gather.c:417 [inline] tlb_flush_mmu+0x6d3/0xa30 mm/mmu_gather.c:424 tlb_finish_mmu+0xf9/0x230 mm/mmu_gather.c:549 exit_mmap+0x498/0x9e0 mm/mmap.c:1313 __mmput+0x118/0x430 kernel/fork.c:1177 exit_mm+0x18e/0x250 kernel/exit.c:581 do_exit+0x6a2/0x22c0 kernel/exit.c:962 do_group_exit+0x21b/0x2d0 kernel/exit.c:1116 __do_sys_exit_group kernel/exit.c:1127 [inline] __se_sys_exit_group kernel/exit.c:1125 [inline] __x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1125 x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f ------------[ cut here ]------------ 1 WARNING: mm/huge_memory.c:4371 at deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371, CPU#1: syz.3.1110/10500 Modules linked in: CPU: 1 UID: 0 PID: 10500 Comm: syz.3.1110 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026 RIP: 0010:deferred_split_folio+0x974/0xaa0 mm/huge_memory.c:4371 Code: 31 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d e9 c2 67 8d 09 cc e8 8c 73 93 ff 48 89 df 48 c7 c6 20 5b fc 8b e8 dd 2b f5 fe 90 <0f> 0b 90 e9 d4 fe ff ff e8 9f 7a 8a 09 e8 6a 73 93 ff 48 89 df 48 RSP: 0018:ffffc900047ef540 EFLAGS: 00010046 RAX: 1c05fb65cfaab100 RBX: ffffea0001840000 RCX: 0000000080000001 RDX: 0000000000000002 RSI: ffffffff8e4da1c7 RDI: ffff88807d6f9e80 RBP: ffffc900047ef610 R08: ffff8880b87247d3 R09: 1ffff110170e48faQuoted 8 lines
R10: dffffc0000000000 R11: ffffed10170e48fb R12: ffffea0001840040 R13: 0000000000000000 R14: 0000000000010000 R15: 1ffff920008fdeb0 FS: 00007f32e32a76c0(0000) GS:ffff8881250e8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5825757930 CR3: 0000000034ad8000 CR4: 00000000003526f0 Call Trace: <TASK> migrate_folio_move mm/migrate.c:1411 [inline]Looks like a race introduced by commit[1] ("mm: migrate: requeue destination folio on deferred split queue").
Between folio migration (mbind) and rmap removal (
exit_mmap), I guess :)
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration:if (folio_order(src) > 1 && !data_race(list_empty(&src->_deferred_list))) { src_deferred_split = true; src_partially_mapped = folio_test_partially_mapped(src); }Then
move_to_new_folio()eventually unqueues src in__folio_migrate_mapping():folio_unqueue_deferred_split(src);After that, migration restores mappings to dst:
if (old_page_state & PAGE_WAS_MAPPED) remove_migration_ptes(src, dst, 0);At that point, dst is already visible again. A concurrent unmap path from another sharer can then remove some of those mappings and reach
deferred_split_folio(dst, true), which setsPG_partially_mappedon dst.Migration then resumes and does:
if (src_deferred_split) deferred_split_folio(dst, src_partially_mapped);If the earlier snapshot from src was false, this becomes
deferred_split_folio(dst, false), but dst may already have been marked partially mapped by the concurrent rmap-removal path, so the WARN indeferred_split_folio()fires:Quoted 9 lines
if (partially_mapped) { ... } else { /* partially mapped folios cannot become non-partially mapped */ VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); }Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning indeferred_split_folio()From: Lance Yang <[email protected]>
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration and later requeues dst afterremove_migration_ptes(src, dst, 0).Once dst is visible again, a competing rmap-removal path can legally set
PG_partially_mappedbefore the migration path reachesdeferred_split_folio(dst, src_partially_mapped).Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So just drop the WARN in the !partially_mapped path and preserve an already-set
PG_partially_mappedbit.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Signed-off-by Lance Yang <[email protected]>--- mm/huge_memory.c | 3 --- 1 file changed, 3 deletions(-)mm/huge_memory.c +0
-0@@ -4433,9 +4433,6 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)mod_mthp_stat (folio_order (folio),MTHP_STAT_NR_ANON_PARTIALLY_MAPPED ,1 );
Quoted 7 lines
mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4436
Quoted 5 lines
mm/huge_memory.c +0
-3} - }else { -/* partially mapped folios cannot become non-partially mapped */ -VM_WARN_ON_FOLIO (folio_test_partially_mapped (folio), folio); }Can't we simply move the setting before restoring migration ptes?
Afraid not, it closes the remove_migration_ptes() -> deferred_split_folio() race, but opens a new one with the shrinker, IIUC
Once dst is on the deferred split queue, deferred_split_scan() can pick it up immediately. The shrinker unconditionally dequeues every folio it visits:
list_del_init(&folio->_deferred_list); /* always */
Then for a non-partially-mapped folio, if folio_trylock() fails (dst is still locked by migration), it falls through to:
next:
if (did_split || !folio_test_partially_mapped(folio)) continue; /* not requeued, dst silently lost */
so it is *not* requeued.
That seems to recreate the original issue commit[1] was fixing: letting underused THPs silently fall off the deferred split queue again ...
Hopefully, I didn't miss something important :)
[1] https://lore.kernel.org/all/[email protected]/
Quoted 39 lines
mm/migrate.c +9
-10@@ -1385,6 +1385,15 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (rc) goto out; +/* +* Requeue the destination folio on the deferred split queue if +* the source was on the queue. The source is unqueued in +* __folio_migrate_mapping(), so we recorded the state from +* before move_to_new_folio(). +*/ +if (src_deferred_split) +deferred_split_folio (dst, src_partially_mapped); +/* * When successful, push dst to LRU immediately: so that if it * turns out to be an mlocked page, remove_migration_ptes() will @@ -1400,16 +1409,6 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private,if (old_page_state &PAGE_WAS_MAPPED )remove_migration_ptes (src, dst,0 ); - -/* -* Requeue the destination folio on the deferred split queue if -* the source was on the queue. The source is unqueued in -* __folio_migrate_mapping(), so we recorded the state from -* before move_to_new_folio(). -*/ -if (src_deferred_split) -deferred_split_folio (dst, src_partially_mapped); -
out_unlock_both:folio_unlock(dst); folio_set_owner_migrate_reason(dst, reason);
David Hildenbrand (Arm)
maintainerQuoted 70 lines · Lance Yang
+Cc Deepanshu
Quoted 49 lines · David Hildenbrand (Arm)
Quoted 48 lines · Lance Yang
Quoted 2 lines
from another sharer can then remove some of those mappings and reach
Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning indeferred_split_folio()From: Lance Yang <[email protected]>
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration and later requeues dst afterremove_migration_ptes(src, dst, 0).Once dst is visible again, a competing rmap-removal path can legally set
PG_partially_mappedbefore the migration path reachesdeferred_split_folio(dst, src_partially_mapped).Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So just drop the WARN in the !partially_mapped path and preserve an already-set
PG_partially_mappedbit.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Signed-off-by Lance Yang <[email protected]>--- mm/huge_memory.c | 3 --- 1 file changed, 3 deletions(-)mm/huge_memory.c +0
-0@@ -4433,9 +4433,6 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)mod_mthp_stat (folio_order (folio),MTHP_STAT_NR_ANON_PARTIALLY_MAPPED ,1 );Quoted 7 lines
mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4436
Quoted 5 lines
mm/huge_memory.c +0
-3} - }else { -/* partially mapped folios cannot become non-partially mapped */ -VM_WARN_ON_FOLIO (folio_test_partially_mapped (folio), folio); }Can't we simply move the setting before restoring migration ptes?
Afraid not, it closes the
remove_migration_ptes()->deferred_split_folio()race, but opens a new one with the shrinker, IIUCOnce dst is on the deferred split queue,
deferred_split_scan()can pick it up immediately. The shrinker unconditionally dequeues every folio it visits:list_del_init(&folio->_deferred_list); /* always */
Quoted 8 lines
Then for a non-partially-mapped folio, if
folio_trylock()fails (dst is still locked by migration), it falls through to:next:
if (did_split || !folio_test_partially_mapped(folio)) continue; /* not requeued, dst silently lost */so it is *not* requeued.
How is that different to the shrinker just trying to lock the folio before we unlock it and failing? The race already exists?
To sort out that race a trylock must not result in the folio getting discarded.
mm/huge_memory.c +2 -1
@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue:if (!split_folio (folio)) { did_split = true;if (underused)
@@ -4569,6 +4569,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, next:if (did_split || !folio_test_partially_mapped (folio))continue ; +requeue:/* * Only add back to the queue if folio is partially mapped. * If thp_underused returns false, or if split_folio fails
Signature · 3 lines
Cheers, David
Lance Yang
reviewerQuoted 74 lines · David Hildenbrand (Arm)
Quoted 73 lines · Lance Yang
+Cc Deepanshu
Quoted 49 lines · David Hildenbrand (Arm)
Quoted 48 lines · Lance Yang
Quoted 2 lines
from another sharer can then remove some of those mappings and reach
Perhaps the WARN is simply too strict there :)
Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So maybe the simplest fix is just to drop the WARN in the !partially_mapped path:
---8<---
Subject: [PATCH 1/1] mm/thp: avoid false warning indeferred_split_folio()From: Lance Yang <[email protected]>
migrate_folio_move()snapshotssrc_partially_mappedfrom src before migration and later requeues dst afterremove_migration_ptes(src, dst, 0).Once dst is visible again, a competing rmap-removal path can legally set
PG_partially_mappedbefore the migration path reachesdeferred_split_folio(dst, src_partially_mapped).Migration already holds the folio lock on dst, while the competing rmap-removal path runs under the page-table lock. So once
remove_migration_ptes(src, dst, 0)makes dst visible again, this race looks hard to avoid.So just drop the WARN in the !partially_mapped path and preserve an already-set
PG_partially_mappedbit.Fixes 8a8ca142a488 ("mm: migrate: requeue destination folio on deferred split queue")Reported-by [email protected]Signed-off-by Lance Yang <[email protected]>--- mm/huge_memory.c | 3 --- 1 file changed, 3 deletions(-)mm/huge_memory.c +0
-0@@ -4433,9 +4433,6 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)mod_mthp_stat (folio_order (folio),MTHP_STAT_NR_ANON_PARTIALLY_MAPPED ,1 );Quoted 7 lines
mm/huge_memory.c void deferred_split_folio(struct folio *folio, bool partially_mapped) · line 4436
Quoted 5 lines
mm/huge_memory.c +0
-3} - }else { -/* partially mapped folios cannot become non-partially mapped */ -VM_WARN_ON_FOLIO (folio_test_partially_mapped (folio), folio); }Can't we simply move the setting before restoring migration ptes?
Afraid not, it closes the
remove_migration_ptes()->deferred_split_folio()race, but opens a new one with the shrinker, IIUCOnce dst is on the deferred split queue,
deferred_split_scan()can pick it up immediately. The shrinker unconditionally dequeues every folio it visits:list_del_init(&folio->_deferred_list); /* always */Then for a non-partially-mapped folio, if
folio_trylock()fails (dst is still locked by migration), it falls through to:
Quoted 8 lines
Quoted 5 lines
next:
if (did_split || !folio_test_partially_mapped(folio)) continue; /* not requeued, dst silently lost */so it is *not* requeued.
How is that different to the shrinker just trying to lock the folio before we unlock it and failing? The race already exists?
Ouch, you're right, I was wrong - the trylock drop is a pre-existing issue, not caused by the reorder ;)
Quoted 3 lines
To sort out that race a trylock must not result in the folio getting discarded.
Nice, LGTM!
Given that the "trylock -> drop" behavior seems to exist already today, do you think it's worth fixing that together with the reorder?
Quoted 23 lines
mm/huge_memory.c +1
-1@@ -4558,7 +4558,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, goto next; }if (!folio_trylock (folio)) - goto next; + goto requeue:if (!split_folio (folio)) { did_split = true;if (underused)@@ -4569,6 +4569,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink,next:
if (did_split || !folio_test_partially_mapped(folio)) continue;+requeue:
/*
* Only add back to the queue if folio is partially mapped.
* Ifthp_underusedreturns false, or ifsplit_foliofails
David Hildenbrand (Arm)
maintainerQuoted 32 lines · Lance Yang
Quoted 17 lines · David Hildenbrand (Arm)
Quoted 16 lines · Lance Yang
+Cc Deepanshu
Afraid not, it closes the
remove_migration_ptes()->deferred_split_folio()race, but opens a new one with the shrinker, IIUCOnce dst is on the deferred split queue,
deferred_split_scan()can pick it up immediately. The shrinker unconditionally dequeues every folio it visits:list_del_init(&folio->_deferred_list); /* always */Then for a non-partially-mapped folio, if
folio_trylock()fails (dst is still locked by migration), it falls through to:Quoted 8 lines
Quoted 5 lines
next:
if (did_split || !folio_test_partially_mapped(folio)) continue; /* not requeued, dst silently lost */so it is *not* requeued.
How is that different to the shrinker just trying to lock the folio before we unlock it and failing? The race already exists?
Ouch, you're right, I was wrong - the trylock drop is a pre-existing issue, not caused by the reorder ;)
Quoted 1 line
Quoted 7 lines
Quoted 2 lines
To sort out that race a trylock must not result in the folio getting discarded.
Nice, LGTM!
Given that the "trylock -> drop" behavior seems to exist already today, do you think it's worth fixing that together with the reorder?
I'd do it in a single shot if possible.
Can you craft something? (cc stable etc)
Signature · 3 lines
Cheers, David
Lance Yang
reviewerQuoted 36 lines · David Hildenbrand (Arm)
Quoted 35 lines · Lance Yang
Quoted 17 lines · David Hildenbrand (Arm)
Quoted 16 lines · Lance Yang
+Cc Deepanshu
Afraid not, it closes the
remove_migration_ptes()->deferred_split_folio()race, but opens a new one with the shrinker, IIUCOnce dst is on the deferred split queue,
deferred_split_scan()can pick it up immediately. The shrinker unconditionally dequeues every folio it visits:list_del_init(&folio->_deferred_list); /* always */Then for a non-partially-mapped folio, if
folio_trylock()fails (dst is still locked by migration), it falls through to:Quoted 8 lines
Quoted 5 lines
next:
if (did_split || !folio_test_partially_mapped(folio)) continue; /* not requeued, dst silently lost */so it is *not* requeued.
How is that different to the shrinker just trying to lock the folio before we unlock it and failing? The race already exists?
Ouch, you're right, I was wrong - the trylock drop is a pre-existing issue, not caused by the reorder ;)
Quoted 3 lines
To sort out that race a trylock must not result in the folio getting discarded.
Quoted 6 lines
Quoted 4 lines
Nice, LGTM!
Given that the "trylock -> drop" behavior seems to exist already today, do you think it's worth fixing that together with the reorder?
I'd do it in a single shot if possible.
ACK.
Quoted 1 line
Can you craft something? (cc stable etc)
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
David Hildenbrand (Arm)
maintainerQuoted 16 lines · Lance Yang
Quoted 7 lines · David Hildenbrand (Arm)
Quoted 6 lines · Lance Yang
Ouch, you're right, I was wrong - the trylock drop is a pre-existing issue, not caused by the reorder ;)
Quoted 6 lines
Quoted 4 lines
Nice, LGTM!
Given that the "trylock -> drop" behavior seems to exist already today, do you think it's worth fixing that together with the reorder?
I'd do it in a single shot if possible.
Quoted 7 lines
ACK.
Quoted 1 line
Can you craft something? (cc stable etc)
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
It's tricky. The original commit will be backported to stable kernels, so we want also the fix to be backported to the same stable kernels.
The commit id in mm-stable is "stable" now.
Signature · 3 lines
Cheers, David
Lance Yang
reviewerQuoted 21 lines · David Hildenbrand (Arm)
Quoted 20 lines · Lance Yang
Quoted 7 lines · David Hildenbrand (Arm)
Quoted 6 lines · Lance Yang
Ouch, you're right, I was wrong - the trylock drop is a pre-existing issue, not caused by the reorder ;)
Quoted 6 lines
Quoted 4 lines
Nice, LGTM!
Given that the "trylock -> drop" behavior seems to exist already today, do you think it's worth fixing that together with the reorder?
I'd do it in a single shot if possible.
ACK.
Quoted 1 line
Can you craft something? (cc stable etc)
Quoted 8 lines
Quoted 3 lines
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
It's tricky. The original commit will be backported to stable kernels, so we want also the fix to be backported to the same stable kernels.
The commit id in mm-stable is "stable" now.
Emm... Not a big deal, I guess. We can always submit a stable backport once that fix gets merged into stable :D
David Hildenbrand (Arm)
maintainerQuoted 13 lines · Lance Yang
Quoted 10 lines · David Hildenbrand (Arm)
Quoted 8 lines · Lance Yang
ACK.
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
Quoted 7 lines
Quoted 4 lines
It's tricky. The original commit will be backported to stable kernels, so we want also the fix to be backported to the same stable kernels.
The commit id in mm-stable is "stable" now.
Emm... Not a big deal, I guess. We can always submit a stable backport once that fix gets merged into stable :D
What's the problem with tagging the commit right way as Cc: stable?
Signature · 3 lines
Cheers, David
Lance Yang
reviewerQuoted 17 lines · David Hildenbrand (Arm)
Quoted 16 lines · Lance Yang
Quoted 13 lines · David Hildenbrand (Arm)
Quoted 8 lines · Lance Yang
ACK.
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
It's tricky. The original commit will be backported to stable kernels, so we want also the fix to be backported to the same stable kernels.
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
The commit id in mm-stable is "stable" now.
Emm... Not a big deal, I guess. We can always submit a stable backport once that fix gets merged into stable :D
What's the problem with tagging the commit right way as Cc: stable?
Sure, that makes sense. I'll add "Cc: stable" to the fix as well, so it can follow 8a8ca142a488 into stable ;)
Lance Yang
reviewerQuoted 22 lines · Lance Yang
Quoted 19 lines · David Hildenbrand (Arm)
Quoted 18 lines · Lance Yang
Quoted 14 lines · David Hildenbrand (Arm)
Quoted 8 lines · Lance Yang
ACK.
certainly, will do! But commit[1] ("mm: migrate: requeue destination folio on deferred split queue") is only in mm-stable now, not yet upstream/stable ...
It's tricky. The original commit will be backported to stable kernels, so we want also the fix to be backported to the same stable kernels.
The commit id in mm-stable is "stable" now.
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
Emm... Not a big deal, I guess. We can always submit a stable backport once that fix gets merged into stable :D
What's the problem with tagging the commit right way as Cc: stable?
Sure, that makes sense. I'll add "Cc: stable" to the fix as well, so it can follow
8a8ca142a488into stable ;)
8a8ca142a488 refers to commit "mm: migrate: requeue destination folio on deferred split queue".