* [SECURITY] mm: mremap() merge silently disarms a userfaultfd VMA
@ 2026-11-10 7:31 n3rd
2026-11-10 9:03 ` Mike Rapoport
0 siblings, 1 reply; 2+ messages in thread
From: n3rd @ 2026-11-10 7:31 UTC (permalink / raw)
To: linux-mm, linux-kernel; +Cc: rppt, peterx, ljs, akpm, security
[-- Attachment #1: Type: text/plain, Size: 5516 bytes --]
Hello,
I am reporting a security bug in the Linux kernel.
1. Affected kernel versions
Linux v7.3-rc7 (mainline, built and tested as 7.3.0-rc7 with KASAN) is affected and the issue was reproduced in two fresh QEMU boots plus a control boot on it. The same code shape is present in v7.3-rc6 and in v7.2.9: mremap_userfaultfd_prep() and the copy_vma() pgoff reset are textually identical, and no commit between v7.3-rc6 and v7.3-rc7 touches the relevant functions. Only v7.3-rc7 was rebuilt and executed here; older releases were not tested and the issue was not bisected.
2. Problem description
copy_vma_and_data() in mm/mremap.c calls copy_vma() and then calls mremap_userfaultfd_prep() on the VMA that copy_vma() returned. copy_vma() can return a pre-existing neighbouring VMA that vma_merge_copied_range() expanded to absorb the copied range: is_mergeable_vma() in mm/vma.c accepts the merge when the neighbour and the source carry the same userfaultfd context, and for an unfaulted anonymous source copy_vma() rewrites the page offset to the destination address so the range is adjacent to that neighbour. mremap_userfaultfd_prep() in mm/userfaultfd.c then runs on the merged VMA, and when the uffd was created without UFFD_FEATURE_EVENT_REMAP it takes the else branch and calls userfaultfd_reset_ctx(). That sets vm_userfaultfd_ctx to NULL and clears VM_UFFD_WP and VM_UFFD_MISSING for the whole merged VMA, not just for the copied range. The victim VMA was registered long before and has nothing to do with this mremap, so a plain unprivileged mremap() silently destroys its registration: uffd-wp write interception stops working and the monitoring process receives zero events. In the test, a write to a page of the victim whose PTE still carried the uffd-wp bit completed immediately with no pagefault event at all, while the same uffd still enforced a separate control VMA in the same process, and UFFDIO_WRITEPROTECT on the victim returned ENOENT, which shows the registration is gone. Running the identical mremap with UFFD_FEATURE_EVENT_REMAP requested does not disarm the VMA, which isolates the else branch as the cause. No memory corruption is involved and no KASAN report, warning or oops is produced.
3. Reproducer
A small static C program opens one userfaultfd with UFFD_USER_MODE_ONLY and features=0, maps three anonymous VMAs B, C and D, registers all three in WP mode, arms uffd-wp on B, and then calls mremap(D, 0x1000, 0x1000, MREMAP_FIXED|MREMAP_MAYMOVE|MREMAP_DONTUNMAP, hole_adjacent_to_B). It writes to a still write-protected page of B and polls the uffd, then repeats with a control VMA and with UFFD_FEATURE_EVENT_REMAP. The program prints machine-checkable verdict lines and ran with euid 1000. The complete reproducer is available on request; I am not posting it publicly, and I can send the sources and the full console logs privately to the maintainers.
4. Conditions
Local unprivileged user: mremap(2), userfaultfd(2) and UFFDIO_* are ordinary system calls and no capability is needed. The uffd is created with UFFD_USER_MODE_ONLY, which userfaultfd_syscall_allowed() accepts even when vm.unprivileged_userfaultfd is 0, and every fault involved is a user-mode fault; in the test the process dropped to euid 1000 before creating the uffd. The victim VMA and the mremap source VMA must share one uffd context, which is the normal shape for an in-process monitor, for CRIU, or for a post-copy migration helper that watches several regions with a single userfaultfd. The monitor must not have requested UFFD_FEATURE_EVENT_REMAP, which is off by default. Kernel configuration needs CONFIG_USERFAULTFD=y and CONFIG_HAVE_ARCH_USERFAULTFD_WP=y, both standard on x86_64. Tested on x86_64 with QEMU TCG and two vCPUs.
5. Suspected location
mm/mremap.c: copy_vma_and_data(); mm/vma.c: copy_vma(), vma_merge_copied_range(), is_mergeable_vma(); mm/userfaultfd.c: mremap_userfaultfd_prep(), userfaultfd_reset_ctx()
6. Suggested fix
mremap_userfaultfd_prep() must only drop the context of the range that was actually copied, not of a pre-existing VMA that absorbed it. Either have copy_vma() report whether it merged into a neighbour and, in that case, restrict or skip the reset, or refuse the merge in vma_merge_copied_range() when the neighbour is uffd-armed and the caller does not have UFFD_FEATURE_EVENT_REMAP. No upstream patch exists as of v7.3-rc7 and no patch is included in this report; one can be provided on request.
7. Mitigation
A monitoring process can request UFFD_FEATURE_EVENT_REMAP so that the if branch is taken, and can periodically re-arm its ranges with UFFDIO_WRITEPROTECT, which starts returning ENOENT once the registration has been lost and therefore detects the failure. There is no way to make the kernel refuse the merge from userspace, and mseal() does not help because the victim VMA is the merge target, not the mremap source.
8. Disclosure
Public. This report goes to the public mm list and LKML because it was prepared with the assistance of an AI coding assistant, and no embargo is requested. The reproducer is not posted publicly; it is available on request.
9. AI-assistance note
This report was prepared with the help of an AI coding assistant, which pointed at the cross-feature interaction between the 2018 mremap_userfaultfd_prep() reset branch and the same-context merge permission, and which helped build the test kernel and draft the text. Per the kernel security process I treat the issue as public and I am not posting the reproducer publicly.
[-- Attachment #2: Type: text/html, Size: 21346 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [SECURITY] mm: mremap() merge silently disarms a userfaultfd VMA
2026-11-10 7:31 [SECURITY] mm: mremap() merge silently disarms a userfaultfd VMA n3rd
@ 2026-11-10 9:03 ` Mike Rapoport
0 siblings, 0 replies; 2+ messages in thread
From: Mike Rapoport @ 2026-11-10 9:03 UTC (permalink / raw)
To: n3rd; +Cc: linux-mm, linux-kernel, peterx, ljs, akpm, security
Hi,
On Tue, Nov 10, 2026 at 03:31:00PM +0800, n3rd wrote:
> Hello,
> I am reporting a security bug in the Linux kernel.
> 1. Affected kernel versions
> Linux v7.3-rc7 (mainline, built and tested as 7.3.0-rc7 with KASAN) is
> affected and the issue was reproduced in two fresh QEMU boots plus a
> control boot on it.
...
> 6. Suggested fix
> mremap_userfaultfd_prep() must only drop the context of the range that
> was actually copied, not of a pre-existing VMA that absorbed it. Either
> have copy_vma() report whether it merged into a neighbour and, in that
> case, restrict or skip the reset, or refuse the merge in
> vma_merge_copied_range() when the neighbour is uffd-armed and the
> caller does not have UFFD_FEATURE_EVENT_REMAP. No upstream patch exists
> as of v7.3-rc7 and no patch is included in this report; one can be
> provided on request.
This was already reported [1] and fixed by commit 68cb098eadda ("mm/vma:
avoid broken uffd merge on mremap") that is in v7.4-rc1 and is tagged
for stable.
[1] https://lore.kernel.org/all/20261020110748.959870-1-shawnq1@163.com/
Please check the latest upstream before sending reports.
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-10 9:03 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 7:31 [SECURITY] mm: mremap() merge silently disarms a userfaultfd VMA n3rd
2026-11-10 9:03 ` Mike Rapoport
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox