// SPDX-License-Identifier: GPL-2.0 /* * Reproducer: WARNING in vma_modify() at mm/vma.c:830 * * Trigger: mseal(2) spanning two adjacent VMAs where the first * has been partially sealed (VM_SEALED set), the second * has not. vma_merge_existing_range() fires WARN_ON because * vmg->start != middle->vm_start with middle != prev. * * Affected: Linux 7.0-rc3, 7.0-rc4, 7.0-rc5 (confirmed) * mm/vma.c untouched in rc3->rc4 and rc4->rc5 patches. * Not present in Linux 6.x (mm/vma.c rewritten for 7.0). * * Note: The same WARN at mm/vma.c:830 is known to trigger via * madvise()+OOM (syzbot+46423ed8fa1f1148c6e4). This * reproducer demonstrates a DISTINCT trigger via mseal(2) * that requires NO fault injection and fires deterministically. * * Reporter: Antonius / Blue Dragon Security * https://bluedragonsec.com * https://github.com/bluedragonsecurity * * Compile: gcc -O2 -o repro_mseal_vma repro_mseal_vma.c * Run: ./repro_mseal_vma * Verify: dmesg | grep 'WARNING.*vma\.c:830' * (fires within iteration 0, < 1 second, no root needed) * * Call path: * mseal(2) * -> do_mseal() [mm/mseal.c] * -> mseal_apply() * -> vma_modify_flags() [mm/vma.c] * -> vma_modify() * -> vma_merge_existing_range() * -> VM_WARN_ON_VMG at line 830 <-- fires here * * Condition that triggers WARN: * VM_WARN_ON_VMG(middle && * ((middle != prev && vmg->start != middle->vm_start) || * vmg->end > middle->vm_end)) * * vmg->start = 0x21da8000 (from first mseal context) * middle->vm_start = 0x21de6000 (VMA-B, not sealed) * -> vmg->start != middle->vm_start -> WARN fires */ #define _GNU_SOURCE #include #include #include #include #include #include #include #ifndef __NR_memfd_create #define __NR_memfd_create 319 #endif #ifndef __NR_mseal #define __NR_mseal 462 #endif /* --------------------------------------------------------------- * Fixed workspace layout (syzbot-style) * These three mmaps establish a predictable address space so that * the trigger addresses 0x21daXXXX fall within mapped memory. * --------------------------------------------------------------- */ static void setup_workspace(void) { syscall(__NR_mmap, (uint64_t)0x1ffffffff000UL, (uint64_t)0x1000UL, (uint64_t)0UL, (uint64_t)0x32UL, /* MAP_FIXED|MAP_ANON|MAP_PRIVATE */ (intptr_t)-1, (uint64_t)0UL); syscall(__NR_mmap, (uint64_t)0x200000000000UL, (uint64_t)0x1000000UL, (uint64_t)7UL, /* PROT_READ|WRITE|EXEC */ (uint64_t)0x32UL, (intptr_t)-1, (uint64_t)0UL); syscall(__NR_mmap, (uint64_t)0x200001000000UL, (uint64_t)0x1000UL, (uint64_t)0UL, (uint64_t)0x32UL, (intptr_t)-1, (uint64_t)0UL); } /* --------------------------------------------------------------- * Core trigger. * * After the two mmaps + first mseal, memory layout is: * * [0x21da6000 - 0x21de5fff] VMA-A (fd2, MAP_SHARED|MAP_FIXED) * ^-- first mseal() sets VM_SEALED here * [0x21de6000 - 0x21e82fff] VMA-B (fd2, MAP_SHARED|MAP_FIXED) * ^-- NOT sealed when second mseal fires * [0x21e83000 - 0x21e84fff] VMA-C (leftover) * * Second mseal(mmap2_result, 0x70000) targets [0x21da6000-0x21e15fff], * spanning VMA-A (sealed) into VMA-B (not sealed). * * Inside do_mseal() -> mseal_apply() -> vma_modify_flags(): * The call passes the original full mseal start (0x21da8000 from the * first mseal context) as vmg->start. When vma_merge_existing_range() * is invoked for VMA-B (middle=[0x21de6000..]): * * vmg->start (0x21da8000) != middle->vm_start (0x21de6000) * AND middle != prev * -> VM_WARN_ON_VMG fires at mm/vma.c:830 * --------------------------------------------------------------- */ static void trigger(void) { intptr_t fd1, fd2, m1, m2; /* workspace string for memfd names */ memcpy((void *)0x200000000100UL, "syz-mseal\0", 10); /* fd1: first memfd, mapped at 0x21da8000 */ fd1 = syscall(__NR_memfd_create, (uint64_t)0x200000000100UL, (uint64_t)1UL); if (fd1 < 0) return; m1 = syscall(__NR_mmap, (uint64_t)0x21da8000UL, (uint64_t)0xdd000UL, (uint64_t)8UL, /* PROT_SEM */ (uint64_t)0x11UL, /* MAP_SHARED | MAP_FIXED */ (intptr_t)fd1, (uint64_t)0UL); /* fd2: second memfd, mapped at 0x21da6000 (overlaps m1 at start) */ memcpy((void *)0x200000000100UL, "syz-mseal\0", 10); fd2 = syscall(__NR_memfd_create, (uint64_t)0x200000000100UL, (uint64_t)1UL); if (fd2 < 0) return; m2 = syscall(__NR_mmap, (uint64_t)0x21da6000UL, (uint64_t)0xdd000UL, (uint64_t)8UL, (uint64_t)0x11UL, (intptr_t)fd2, (uint64_t)0UL); /* * Step 1: Partial seal on m1 range. * Seals [0x21da8000 .. 0x21de5fff] -- a subset of VMA-A. * Sets VM_SEALED (0x400000000000) on VMA-A. */ syscall(__NR_mseal, (uint64_t)m1, (uint64_t)0x3e000UL, (uint64_t)0UL); /* * Step 2: Seal spanning VMA-A (sealed) + VMA-B (not sealed). * Range [0x21da6000 .. 0x21e15fff]. * -> vma_merge_existing_range() WARN fires. */ syscall(__NR_mseal, (uint64_t)m2, (uint64_t)0x70000UL, (uint64_t)0UL); } int main(void) { fprintf(stderr, "============================================\n" "repro_mseal_vma -- mm/vma.c:830 reproducer\n" "Reporter: Antonius / Blue Dragon Security\n" " https://bluedragonsec.com\n" " https://github.com/bluedragonsecurity" "============================================\n" "Monitor: dmesg | grep 'WARNING.*vma\\.c:830'\n\n"); setup_workspace(); for (int iter = 0;; iter++) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return 1; } if (pid == 0) { trigger(); _exit(0); } int st; waitpid(pid, &st, 0); fprintf(stderr, "[iter %d]\n", iter); if (iter % 5 == 0) system("dmesg 2>/dev/null | grep -c 'WARNING.*vma\\.c:830' " "| xargs -I{} sh -c " "'[ {} -gt 0 ] && " "echo \"[+] WARNING triggered {} times total\"'"); } return 0; }