* [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 @ 2026-03-27 7:46 ` antonius 2026-03-27 8:08 ` antonius ` (2 more replies) 0 siblings, 3 replies; 4+ messages in thread From: antonius @ 2026-03-27 7:46 UTC (permalink / raw) To: linux-mm Cc: lorenzo.stoakes, liam.howlett, jeffxu, akpm, linux-kernel, syzkaller-bugs [-- Attachment #1.1: Type: text/plain, Size: 7732 bytes --] Hello, I am reporting a reproducible WARNING in vma_modify() at mm/vma.c:830, triggered via the mseal(2) syscall on Linux 7.0.0-rc5. The bug was discovered using Syzkaller-based fuzzing. REPORTER -------- Antonius / Blue Dragon Security https://bluedragonsec.com https://github.com/bluedragonsecurity NOTE ON RELATIONSHIP TO KNOWN BUGS ----------------------------------- The VM_WARN_ON_VMG at mm/vma.c:830 inside vma_merge_existing_range() has been previously encountered via madvise()+OOM conditions (reported by syzbot+46423ed8fa1f1148c6e4 and Brad Spengler; addressed by Lorenzo's patch "mm: abort vma_modify() on merge out of memory failure"). This report describes a DISTINCT trigger via mseal(2) that: 1. Does NOT require fault injection or OOM pressure 2. Is 100% reproducible on every run (fires within 1 second) 3. Goes through a different call path: do_mseal() -> mseal_apply() rather than madvise_walk_vmas() 4. Is triggered by VM_SEALED flag state inconsistency across VMAs, not by a failed merge commit I could not find a prior LKML report or syzbot entry for this specific mseal(2) trigger. SUMMARY ------- File: mm/vma.c, line 830 Func: vma_merge_existing_range() Trigger: mseal() spanning two adjacent VMAs where the first has VM_SEALED set and the second does not Via: mseal(2) -> do_mseal() -> mseal_apply() -> vma_modify_flags() -> vma_modify() -> vma_merge_existing_range() -> VM_WARN_ON_VMG AFFECTED VERSIONS ----------------- Linux 7.0-rc3 -- confirmed (original fuzzing target) Linux 7.0-rc4 -- confirmed (mm/vma.c unchanged rc3->rc4) Linux 7.0-rc5 -- confirmed (mm/vma.c unchanged rc4->rc5) Linux 6.x -- NOT affected (mm/vma.c rewritten for 7.0) DMESG OUTPUT (Linux 7.0.0-rc5, trimmed) ---------------------------------------- [ 1680.275764] ------------[ cut here ]------------ [ 1680.275771] WARNING: mm/vma.c:830 at vma_modify+0x35b/0x2190 [ 1680.275808] CPU: 0 UID: 1000 PID: 1661 Comm: repro_mseal_vma [ 1680.275826] Tainted: [W]=WARN 7.0.0-rc5 #1 PREEMPT(lazy) [ 1680.275969] Call Trace: [ 1680.275975] <TASK> [ 1680.276030] vma_modify_flags+0x24c/0x3c0 [ 1680.276085] do_mseal+0x489/0x860 [ 1680.276136] __x64_sys_mseal+0x73/0xb0 [ 1680.276187] do_syscall_64+0x111/0x690 [ 1680.276207] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 1680.276394] ---[ end trace 0000000000000000 ]--- [ 1680.314910] vmg dumped because: VM_WARN_ON_VMG(middle && ((middle != prev && vmg->start != middle->vm_start) || vmg->end > middle->vm_end)) vmg state: vmi [21de6000, 21e83000) prev [21da6000-21de6000) flags: 0x400000000f8 (VM_SEALED set) middle [21de6000-21e83000) flags: 0xf8 (NOT sealed) vmg->start = 0x21da8000 vmg->end = 0x21e16000 ROOT CAUSE ---------- The bug is in vma_merge_existing_range() at mm/vma.c:830. Reproduction sequence: 1. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd1 2. mmap(0x21da8000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd1, 0) -> establishes VMA at [0x21da8000 .. 0x21e85000) 3. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd2 4. mmap(0x21da6000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd2, 0) -> remaps, leaving: VMA-A [0x21da6000 - 0x21de6000) pgoff=0 (fd2) VMA-B [0x21de6000 - 0x21e83000) pgoff=0x40 (fd2) VMA-C [0x21e83000 - 0x21e85000) (leftover) 5. mseal(mmap1_result, 0x3e000, 0) -> seals [0x21da8000 .. 0x21de5fff] -> VMA-A gets VM_SEALED (0x400000000000) set 6. mseal(mmap2_result, 0x70000, 0) -> targets [0x21da6000 .. 0x21e15fff] -> range spans VMA-A (sealed) and VMA-B (not sealed) In step 6, do_mseal() calls mseal_apply() per-VMA but ultimately calls vma_modify_flags() with the original full mseal start address (0x21da8000). When vma_merge_existing_range() processes VMA-B as "middle": vmg->start = 0x21da8000 (original mseal start) middle->vm_start = 0x21de6000 (VMA-B start) middle != prev (different VMA objects) -> vmg->start != middle->vm_start -> WARN_ON fires at line 830 The invariant violation occurs because the vmg->start passed to vma_modify_flags() is not clamped to the current VMA's start when the mseal range spans multiple VMAs with different VM_SEALED states. IMPACT ------ - Reachable from unprivileged userspace (UID 1000, no capabilities) - Only memfd_create(2), mmap(2), mseal(2) required - The WARN_ON indicates that vma_merge_existing_range() operates on an inconsistent vmg state; in production kernels with WARN compiled to no-op, this could result in VMA tree state inconsistency - mseal is a security primitive; invariant violations in its application logic are security-relevant SUGGESTED FIX DIRECTION ------------------------ In do_mseal() or mseal_apply() (mm/mseal.c), when iterating over VMAs in the mseal range, the vmg->start passed to vma_modify_flags() should be clamped to max(mseal_start, vma->vm_start) rather than using the original mseal() start address. This would prevent vma_merge_existing_range() from receiving a vmg->start that is inconsistent with vmg->middle when the mseal range spans multiple VMAs with different seal states. Alternatively, the WARN_ON in vma_merge_existing_range() may need to account for the mseal multi-VMA iteration pattern, though fixing the caller in do_mseal() seems more appropriate. REPRODUCER ---------- Compile: gcc -O2 -o repro repro_mseal_vma.c && ./repro Fires: Within 1 second, iteration 0, no fault injection, no root #define _GNU_SOURCE #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #ifndef __NR_memfd_create #define __NR_memfd_create 319 #endif #ifndef __NR_mseal #define __NR_mseal 462 #endif static void setup(void) { syscall(__NR_mmap, 0x1ffffffff000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); syscall(__NR_mmap, 0x200000000000UL, 0x1000000UL, 7UL, 0x32UL, -1, 0UL); syscall(__NR_mmap, 0x200001000000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); } static void trigger(void) { intptr_t fd1, fd2, m1, m2; memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); fd1 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); if (fd1 < 0) return; m1 = syscall(__NR_mmap, 0x21da8000UL, 0xdd000UL, 8UL, 0x11UL, (intptr_t)fd1, 0UL); memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); fd2 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); if (fd2 < 0) return; m2 = syscall(__NR_mmap, 0x21da6000UL, 0xdd000UL, 8UL, 0x11UL, (intptr_t)fd2, 0UL); syscall(__NR_mseal, (uint64_t)m1, 0x3e000UL, 0UL); syscall(__NR_mseal, (uint64_t)m2, 0x70000UL, 0UL); } int main(void) { setup(); for (int i = 0;; i++) { int pid = fork(); if (pid == 0) { trigger(); _exit(0); } int st; waitpid(pid, &st, 0); fprintf(stderr, "[iter %d]\n", i); } } VERIFICATION ------------ Kernel: Linux 7.0.0-rc5 #1 SMP PREEMPT_DYNAMIC x86_64 HW: QEMU Standard PC (i440FX + PIIX), BIOS 1.17.0-debian User: UID 1000 (no root required) Fires: Iteration 0, consistently, < 1 second mm/vma.c: Not patched in rc3->rc4 or rc4->rc5 --- Reported-by: Antonius <antonius@bluedragonsec.com> Please use this tag in the fix commit: Reported-by: Antonius <antonius@bluedragonsec.com> --- If this is a known issue or already fixed, please point me to the relevant commit. I was unable to find a matching LKML/syzbot entry for this specific mseal(2) trigger path. Thank you, Antonius Blue Dragon Security https://bluedragonsec.com https://github.com/bluedragonsecurity [-- Attachment #1.2: Type: text/html, Size: 9230 bytes --] [-- Attachment #2: repro_mseal_vma.c --] [-- Type: text/x-csrc, Size: 6231 bytes --] // 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 <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #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; } [-- Attachment #3: dmesg_linux_kernel_7_rc5.png --] [-- Type: image/png, Size: 234998 bytes --] ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 2026-03-27 7:46 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 antonius @ 2026-03-27 8:08 ` antonius 2026-03-27 8:59 ` Request received Yail 2026-03-27 9:11 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 Lorenzo Stoakes (Oracle) 2 siblings, 0 replies; 4+ messages in thread From: antonius @ 2026-03-27 8:08 UTC (permalink / raw) To: linux-mm [-- Attachment #1.1: Type: text/plain, Size: 8676 bytes --] Attaching additional confirmation on Linux 7.0.0-rc3 showing the same WARNING at mm/vma.c:830 with UID: 1000, confirming the bug is present throughout the entire 7.0 RC series (rc3 through rc5). Antonius / Blue Dragon Security On Fri, Mar 27, 2026 at 2:46 PM antonius <bluedragonsec2023@gmail.com> wrote: > Hello, > > I am reporting a reproducible WARNING in vma_modify() at mm/vma.c:830, > triggered via the mseal(2) syscall on Linux 7.0.0-rc5. The bug was > discovered using Syzkaller-based fuzzing. > > REPORTER > -------- > Antonius / Blue Dragon Security > https://bluedragonsec.com > https://github.com/bluedragonsecurity > > NOTE ON RELATIONSHIP TO KNOWN BUGS > ----------------------------------- > The VM_WARN_ON_VMG at mm/vma.c:830 inside vma_merge_existing_range() > has been previously encountered via madvise()+OOM conditions > (reported by syzbot+46423ed8fa1f1148c6e4 and Brad Spengler; addressed > by Lorenzo's patch "mm: abort vma_modify() on merge out of memory > failure"). > > This report describes a DISTINCT trigger via mseal(2) that: > 1. Does NOT require fault injection or OOM pressure > 2. Is 100% reproducible on every run (fires within 1 second) > 3. Goes through a different call path: do_mseal() -> mseal_apply() > rather than madvise_walk_vmas() > 4. Is triggered by VM_SEALED flag state inconsistency across VMAs, > not by a failed merge commit > > I could not find a prior LKML report or syzbot entry for this specific > mseal(2) trigger. > > SUMMARY > ------- > File: mm/vma.c, line 830 > Func: vma_merge_existing_range() > Trigger: mseal() spanning two adjacent VMAs where the first has > VM_SEALED set and the second does not > Via: mseal(2) -> do_mseal() -> mseal_apply() -> > vma_modify_flags() -> vma_modify() -> > vma_merge_existing_range() -> VM_WARN_ON_VMG > > AFFECTED VERSIONS > ----------------- > Linux 7.0-rc3 -- confirmed (original fuzzing target) > Linux 7.0-rc4 -- confirmed (mm/vma.c unchanged rc3->rc4) > Linux 7.0-rc5 -- confirmed (mm/vma.c unchanged rc4->rc5) > Linux 6.x -- NOT affected (mm/vma.c rewritten for 7.0) > > DMESG OUTPUT (Linux 7.0.0-rc5, trimmed) > ---------------------------------------- > > [ 1680.275764] ------------[ cut here ]------------ > [ 1680.275771] WARNING: mm/vma.c:830 at vma_modify+0x35b/0x2190 > [ 1680.275808] CPU: 0 UID: 1000 PID: 1661 Comm: repro_mseal_vma > [ 1680.275826] Tainted: [W]=WARN 7.0.0-rc5 #1 PREEMPT(lazy) > [ 1680.275969] Call Trace: > [ 1680.275975] <TASK> > [ 1680.276030] vma_modify_flags+0x24c/0x3c0 > [ 1680.276085] do_mseal+0x489/0x860 > [ 1680.276136] __x64_sys_mseal+0x73/0xb0 > [ 1680.276187] do_syscall_64+0x111/0x690 > [ 1680.276207] entry_SYSCALL_64_after_hwframe+0x77/0x7f > [ 1680.276394] ---[ end trace 0000000000000000 ]--- > > [ 1680.314910] vmg dumped because: > VM_WARN_ON_VMG(middle && > ((middle != prev && vmg->start != middle->vm_start) || > vmg->end > middle->vm_end)) > > vmg state: > vmi [21de6000, 21e83000) > prev [21da6000-21de6000) flags: 0x400000000f8 (VM_SEALED set) > middle [21de6000-21e83000) flags: 0xf8 (NOT sealed) > vmg->start = 0x21da8000 > vmg->end = 0x21e16000 > > ROOT CAUSE > ---------- > The bug is in vma_merge_existing_range() at mm/vma.c:830. > > Reproduction sequence: > > 1. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd1 > 2. mmap(0x21da8000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd1, 0) > -> establishes VMA at [0x21da8000 .. 0x21e85000) > > 3. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd2 > 4. mmap(0x21da6000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd2, 0) > -> remaps, leaving: > VMA-A [0x21da6000 - 0x21de6000) pgoff=0 (fd2) > VMA-B [0x21de6000 - 0x21e83000) pgoff=0x40 (fd2) > VMA-C [0x21e83000 - 0x21e85000) (leftover) > > 5. mseal(mmap1_result, 0x3e000, 0) > -> seals [0x21da8000 .. 0x21de5fff] > -> VMA-A gets VM_SEALED (0x400000000000) set > > 6. mseal(mmap2_result, 0x70000, 0) > -> targets [0x21da6000 .. 0x21e15fff] > -> range spans VMA-A (sealed) and VMA-B (not sealed) > > In step 6, do_mseal() calls mseal_apply() per-VMA but ultimately > calls vma_modify_flags() with the original full mseal start address > (0x21da8000). When vma_merge_existing_range() processes VMA-B as > "middle": > > vmg->start = 0x21da8000 (original mseal start) > middle->vm_start = 0x21de6000 (VMA-B start) > middle != prev (different VMA objects) > > -> vmg->start != middle->vm_start -> WARN_ON fires at line 830 > > The invariant violation occurs because the vmg->start passed to > vma_modify_flags() is not clamped to the current VMA's start when > the mseal range spans multiple VMAs with different VM_SEALED states. > > IMPACT > ------ > - Reachable from unprivileged userspace (UID 1000, no capabilities) > - Only memfd_create(2), mmap(2), mseal(2) required > - The WARN_ON indicates that vma_merge_existing_range() operates on > an inconsistent vmg state; in production kernels with WARN compiled > to no-op, this could result in VMA tree state inconsistency > - mseal is a security primitive; invariant violations in its > application logic are security-relevant > > SUGGESTED FIX DIRECTION > ------------------------ > In do_mseal() or mseal_apply() (mm/mseal.c), when iterating over > VMAs in the mseal range, the vmg->start passed to vma_modify_flags() > should be clamped to max(mseal_start, vma->vm_start) rather than > using the original mseal() start address. This would prevent > vma_merge_existing_range() from receiving a vmg->start that is > inconsistent with vmg->middle when the mseal range spans multiple > VMAs with different seal states. > > Alternatively, the WARN_ON in vma_merge_existing_range() may need > to account for the mseal multi-VMA iteration pattern, though fixing > the caller in do_mseal() seems more appropriate. > > REPRODUCER > ---------- > Compile: gcc -O2 -o repro repro_mseal_vma.c && ./repro > Fires: Within 1 second, iteration 0, no fault injection, no root > > #define _GNU_SOURCE > #include <stdint.h> > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > #include <sys/syscall.h> > #include <sys/wait.h> > #include <unistd.h> > > #ifndef __NR_memfd_create > #define __NR_memfd_create 319 > #endif > #ifndef __NR_mseal > #define __NR_mseal 462 > #endif > > static void setup(void) { > syscall(__NR_mmap, 0x1ffffffff000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); > syscall(__NR_mmap, 0x200000000000UL, 0x1000000UL, 7UL, 0x32UL, -1, > 0UL); > syscall(__NR_mmap, 0x200001000000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); > } > > static void trigger(void) { > intptr_t fd1, fd2, m1, m2; > memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); > fd1 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); > if (fd1 < 0) return; > m1 = syscall(__NR_mmap, 0x21da8000UL, 0xdd000UL, > 8UL, 0x11UL, (intptr_t)fd1, 0UL); > memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); > fd2 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); > if (fd2 < 0) return; > m2 = syscall(__NR_mmap, 0x21da6000UL, 0xdd000UL, > 8UL, 0x11UL, (intptr_t)fd2, 0UL); > syscall(__NR_mseal, (uint64_t)m1, 0x3e000UL, 0UL); > syscall(__NR_mseal, (uint64_t)m2, 0x70000UL, 0UL); > } > > int main(void) { > setup(); > for (int i = 0;; i++) { > int pid = fork(); > if (pid == 0) { trigger(); _exit(0); } > int st; waitpid(pid, &st, 0); > fprintf(stderr, "[iter %d]\n", i); > } > } > > VERIFICATION > ------------ > Kernel: Linux 7.0.0-rc5 #1 SMP PREEMPT_DYNAMIC x86_64 > HW: QEMU Standard PC (i440FX + PIIX), BIOS 1.17.0-debian > User: UID 1000 (no root required) > Fires: Iteration 0, consistently, < 1 second > mm/vma.c: Not patched in rc3->rc4 or rc4->rc5 > > --- > Reported-by: Antonius <antonius@bluedragonsec.com> > > Please use this tag in the fix commit: > Reported-by: Antonius <antonius@bluedragonsec.com> > > --- > If this is a known issue or already fixed, please point me to the > relevant commit. I was unable to find a matching LKML/syzbot entry > for this specific mseal(2) trigger path. > > Thank you, > Antonius > Blue Dragon Security > https://bluedragonsec.com > https://github.com/bluedragonsecurity > > [-- Attachment #1.2: Type: text/html, Size: 10079 bytes --] [-- Attachment #2: dmesg_linux_kernel_7_rc3.png --] [-- Type: image/png, Size: 327357 bytes --] ^ permalink raw reply [flat|nested] 4+ messages in thread
* Request received 2026-03-27 7:46 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 antonius 2026-03-27 8:08 ` antonius @ 2026-03-27 8:59 ` Yail 2026-03-27 9:11 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 Lorenzo Stoakes (Oracle) 2 siblings, 0 replies; 4+ messages in thread From: Yail @ 2026-03-27 8:59 UTC (permalink / raw) To: antonius Cc: Akpm, Jeffxu, Liam Howlett, Linux-kernel, Linux-mm, Lorenzo Stoakes [-- Attachment #1: Type: text/plain, Size: 314 bytes --] Your request (39) 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> [-- Attachment #2: Type: text/html, Size: 1839 bytes --] ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 2026-03-27 7:46 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 antonius 2026-03-27 8:08 ` antonius 2026-03-27 8:59 ` Request received Yail @ 2026-03-27 9:11 ` Lorenzo Stoakes (Oracle) 2 siblings, 0 replies; 4+ messages in thread From: Lorenzo Stoakes (Oracle) @ 2026-03-27 9:11 UTC (permalink / raw) To: antonius Cc: linux-mm, liam.howlett, jeffxu, akpm, linux-kernel, syzkaller-bugs -cc old email (please use ljs@kernel.org for correspondence) +cc linux-mm On Fri, Mar 27, 2026 at 02:46:32PM +0700, antonius wrote: > Hello, > > I am reporting a reproducible WARNING in vma_modify() at mm/vma.c:830, > triggered via the mseal(2) syscall on Linux 7.0.0-rc5. The bug was > discovered using Syzkaller-based fuzzing. Thanks very much! This is very much appreciated. So this is triggering: /* * If middle == prev, then we are offset into a VMA. Otherwise, if we are * not, we must span a portion of the VMA. */ VM_WARN_ON_VMG(middle && ((middle != prev && vmg->start != middle->vm_start) || vmg->end > middle->vm_end), vmg); And the repro is working for me thanks, it's refreshing to get a nice consistent repro :) Looks like the repro is creating two overlapping ranges: m2 0x21da6000 0x21e81000 |-------------------------|----| | vma2 |vma1| |-------------------------|----| <------------------------><----> 0xdd pages 2pgs (vma2 with fd2, vma1 with fd1) Then first mseal split things into: 0x21da6000 0x21de6000 0x21e81000 |--|-------|-----------------|----| |v2| vma2S | vma2' |vma1| |--|-------|-----------------|----| 0x21da8000 With vma2, vma2' unsealed, vma2S sealed (all with fd2) and vma1 with fd1. Then we try to mseal: 0x21da6000 0x21de6000 . 0x21e81000 |--|-------|--------------.--|----| |v2| vma2S | vma2' . |vma1| |--|-------|--------------.--|----| . 0x21da8000 . <------------------------>. 0x21e16000 In mseal_apply() with start=0x21da6000, end=0x21e16000 static int mseal_apply(struct mm_struct *mm, unsigned long start, unsigned long end) { ... unsigned long curr_start = start; ... for_each_vma_range(vmi, vma, end) { const unsigned long curr_end = MIN(vma->vm_end, end); if (!(vma->vm_flags & VM_SEALED)) { vm_flags_t vm_flags = vma->vm_flags | VM_SEALED; vma = vma_modify_flags(&vmi, prev, vma, curr_start, curr_end, &vm_flags); if (IS_ERR(vma)) return PTR_ERR(vma); vm_flags_set(vma, VM_SEALED); } prev = vma; curr_start = curr_end; } return 0; } Iteration 0: curr_start = 0x21da6000 curr_end = MIN(0x21da8000, 0x21e16000) = 0x21da8000 if (!(vma->vm_flags & VM_SEALED)) { Is true So: vma = vma_modify_flags(&vmi, prev, vma, curr_start, curr_end, &vm_flags); Results in a MERGE: 0x21da6000 0x21de6000 0x21e81000 |----------|-----------------|----| | vma2S | vma2' |vma1| |----------|-----------------|----| But... curr_start = curr_end = 0x21da8000 ^ | This is not updated!! prev = vma = the now-merged vma2S. Then iteration 1: curr_start = curr_end = 0x21da8000 Annnd we're in trouble because this is wrong. vma moves to next VMA (0x21de6000) And: const unsigned long curr_end = MIN(vma->vm_end, end); Results in curr_end = MIN(0x21de6000, 0x21e16000) = 0x21de6000 And prev = vma2S, vma/middle = vma2', next = vma1 So: 0x21da6000 0x21de6000 0x21e81000 |----------|-----------------|----| | vma2S | vma2' |vma1| |----------|-----------------|----| ^ ^ | | | curr_end curr_start = 0x21da8000 - OFFSET into vma2S. INCORRECT. So: VM_WARN_ON_VMG(middle && ((middle != prev && vmg->start != middle->vm_start) || vmg->end > middle->vm_end), vmg); Where vmg->start = 0x21da8000 which != middle->vm_start == 0x21da6000 so this triggers. It's essentially asserting we're not offset into the VMA, but here we are. I've sent a fix for this already - see https://lore.kernel.org/all/20260327090640.146308-1-ljs@kernel.org/ Cheers, Lorenzo > > REPORTER > -------- > Antonius / Blue Dragon Security > https://bluedragonsec.com > https://github.com/bluedragonsecurity > > NOTE ON RELATIONSHIP TO KNOWN BUGS > ----------------------------------- > The VM_WARN_ON_VMG at mm/vma.c:830 inside vma_merge_existing_range() > has been previously encountered via madvise()+OOM conditions > (reported by syzbot+46423ed8fa1f1148c6e4 and Brad Spengler; addressed > by Lorenzo's patch "mm: abort vma_modify() on merge out of memory > failure"). > > This report describes a DISTINCT trigger via mseal(2) that: > 1. Does NOT require fault injection or OOM pressure > 2. Is 100% reproducible on every run (fires within 1 second) > 3. Goes through a different call path: do_mseal() -> mseal_apply() > rather than madvise_walk_vmas() > 4. Is triggered by VM_SEALED flag state inconsistency across VMAs, > not by a failed merge commit > > I could not find a prior LKML report or syzbot entry for this specific > mseal(2) trigger. > > SUMMARY > ------- > File: mm/vma.c, line 830 > Func: vma_merge_existing_range() > Trigger: mseal() spanning two adjacent VMAs where the first has > VM_SEALED set and the second does not > Via: mseal(2) -> do_mseal() -> mseal_apply() -> > vma_modify_flags() -> vma_modify() -> > vma_merge_existing_range() -> VM_WARN_ON_VMG > > AFFECTED VERSIONS > ----------------- > Linux 7.0-rc3 -- confirmed (original fuzzing target) > Linux 7.0-rc4 -- confirmed (mm/vma.c unchanged rc3->rc4) > Linux 7.0-rc5 -- confirmed (mm/vma.c unchanged rc4->rc5) > Linux 6.x -- NOT affected (mm/vma.c rewritten for 7.0) This doesn't seem to be correct, this bug has been there since v6.17, doh... > > DMESG OUTPUT (Linux 7.0.0-rc5, trimmed) > ---------------------------------------- > > [ 1680.275764] ------------[ cut here ]------------ > [ 1680.275771] WARNING: mm/vma.c:830 at vma_modify+0x35b/0x2190 > [ 1680.275808] CPU: 0 UID: 1000 PID: 1661 Comm: repro_mseal_vma > [ 1680.275826] Tainted: [W]=WARN 7.0.0-rc5 #1 PREEMPT(lazy) > [ 1680.275969] Call Trace: > [ 1680.275975] <TASK> > [ 1680.276030] vma_modify_flags+0x24c/0x3c0 > [ 1680.276085] do_mseal+0x489/0x860 > [ 1680.276136] __x64_sys_mseal+0x73/0xb0 > [ 1680.276187] do_syscall_64+0x111/0x690 > [ 1680.276207] entry_SYSCALL_64_after_hwframe+0x77/0x7f > [ 1680.276394] ---[ end trace 0000000000000000 ]--- > > [ 1680.314910] vmg dumped because: > VM_WARN_ON_VMG(middle && > ((middle != prev && vmg->start != middle->vm_start) || > vmg->end > middle->vm_end)) > > vmg state: > vmi [21de6000, 21e83000) > prev [21da6000-21de6000) flags: 0x400000000f8 (VM_SEALED set) > middle [21de6000-21e83000) flags: 0xf8 (NOT sealed) > vmg->start = 0x21da8000 > vmg->end = 0x21e16000 > > ROOT CAUSE > ---------- > The bug is in vma_merge_existing_range() at mm/vma.c:830. > > Reproduction sequence: > > 1. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd1 > 2. mmap(0x21da8000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd1, 0) > -> establishes VMA at [0x21da8000 .. 0x21e85000) > > 3. memfd_create("syz-mseal", MFD_CLOEXEC) -> fd2 > 4. mmap(0x21da6000, 0xdd000, PROT_SEM, MAP_SHARED|MAP_FIXED, fd2, 0) > -> remaps, leaving: > VMA-A [0x21da6000 - 0x21de6000) pgoff=0 (fd2) > VMA-B [0x21de6000 - 0x21e83000) pgoff=0x40 (fd2) > VMA-C [0x21e83000 - 0x21e85000) (leftover) > > 5. mseal(mmap1_result, 0x3e000, 0) > -> seals [0x21da8000 .. 0x21de5fff] > -> VMA-A gets VM_SEALED (0x400000000000) set > > 6. mseal(mmap2_result, 0x70000, 0) > -> targets [0x21da6000 .. 0x21e15fff] > -> range spans VMA-A (sealed) and VMA-B (not sealed) > > In step 6, do_mseal() calls mseal_apply() per-VMA but ultimately > calls vma_modify_flags() with the original full mseal start address > (0x21da8000). When vma_merge_existing_range() processes VMA-B as > "middle": > > vmg->start = 0x21da8000 (original mseal start) > middle->vm_start = 0x21de6000 (VMA-B start) > middle != prev (different VMA objects) > > -> vmg->start != middle->vm_start -> WARN_ON fires at line 830 > > The invariant violation occurs because the vmg->start passed to > vma_modify_flags() is not clamped to the current VMA's start when > the mseal range spans multiple VMAs with different VM_SEALED states. > > IMPACT > ------ > - Reachable from unprivileged userspace (UID 1000, no capabilities) > - Only memfd_create(2), mmap(2), mseal(2) required > - The WARN_ON indicates that vma_merge_existing_range() operates on > an inconsistent vmg state; in production kernels with WARN compiled > to no-op, this could result in VMA tree state inconsistency > - mseal is a security primitive; invariant violations in its > application logic are security-relevant > > SUGGESTED FIX DIRECTION > ------------------------ > In do_mseal() or mseal_apply() (mm/mseal.c), when iterating over > VMAs in the mseal range, the vmg->start passed to vma_modify_flags() > should be clamped to max(mseal_start, vma->vm_start) rather than > using the original mseal() start address. This would prevent > vma_merge_existing_range() from receiving a vmg->start that is > inconsistent with vmg->middle when the mseal range spans multiple > VMAs with different seal states. > > Alternatively, the WARN_ON in vma_merge_existing_range() may need > to account for the mseal multi-VMA iteration pattern, though fixing > the caller in do_mseal() seems more appropriate. > > REPRODUCER > ---------- > Compile: gcc -O2 -o repro repro_mseal_vma.c && ./repro > Fires: Within 1 second, iteration 0, no fault injection, no root > > #define _GNU_SOURCE > #include <stdint.h> > #include <stdio.h> > #include <stdlib.h> > #include <string.h> > #include <sys/syscall.h> > #include <sys/wait.h> > #include <unistd.h> > > #ifndef __NR_memfd_create > #define __NR_memfd_create 319 > #endif > #ifndef __NR_mseal > #define __NR_mseal 462 > #endif > > static void setup(void) { > syscall(__NR_mmap, 0x1ffffffff000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); > syscall(__NR_mmap, 0x200000000000UL, 0x1000000UL, 7UL, 0x32UL, -1, 0UL); > syscall(__NR_mmap, 0x200001000000UL, 0x1000UL, 0UL, 0x32UL, -1, 0UL); > } > > static void trigger(void) { > intptr_t fd1, fd2, m1, m2; > memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); > fd1 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); > if (fd1 < 0) return; > m1 = syscall(__NR_mmap, 0x21da8000UL, 0xdd000UL, > 8UL, 0x11UL, (intptr_t)fd1, 0UL); > memcpy((void*)0x200000000100UL, "syz-mseal\0", 10); > fd2 = syscall(__NR_memfd_create, 0x200000000100UL, 1UL); > if (fd2 < 0) return; > m2 = syscall(__NR_mmap, 0x21da6000UL, 0xdd000UL, > 8UL, 0x11UL, (intptr_t)fd2, 0UL); > syscall(__NR_mseal, (uint64_t)m1, 0x3e000UL, 0UL); > syscall(__NR_mseal, (uint64_t)m2, 0x70000UL, 0UL); > } > > int main(void) { > setup(); > for (int i = 0;; i++) { > int pid = fork(); > if (pid == 0) { trigger(); _exit(0); } > int st; waitpid(pid, &st, 0); > fprintf(stderr, "[iter %d]\n", i); > } > } > > VERIFICATION > ------------ > Kernel: Linux 7.0.0-rc5 #1 SMP PREEMPT_DYNAMIC x86_64 > HW: QEMU Standard PC (i440FX + PIIX), BIOS 1.17.0-debian > User: UID 1000 (no root required) > Fires: Iteration 0, consistently, < 1 second > mm/vma.c: Not patched in rc3->rc4 or rc4->rc5 > > --- > Reported-by: Antonius <antonius@bluedragonsec.com> > > Please use this tag in the fix commit: > Reported-by: Antonius <antonius@bluedragonsec.com> > > --- > If this is a known issue or already fixed, please point me to the > relevant commit. I was unable to find a matching LKML/syzbot entry > for this specific mseal(2) trigger path. > > Thank you, > Antonius > Blue Dragon Security > https://bluedragonsec.com > https://github.com/bluedragonsecurity ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-03-27 9:11 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <DEX62KG7X9P@zendesk.com>
2026-03-27 7:46 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 antonius
2026-03-27 8:08 ` antonius
2026-03-27 8:59 ` Request received Yail
2026-03-27 9:11 ` [BUG] mm/vma.c:830 WARNING in vma_modify() via mseal(2) -- deterministic trigger without fault injection on Linux 7.0-rc5 Lorenzo Stoakes (Oracle)
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox