From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
To: stable@vger.kernel.org
Subject: [PATCH 6.6.y] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP self-merge
Date: Tue, 13 Oct 2026 17:48:37 +0100 [thread overview]
Message-ID: <20261013164837.2509912-1-ljs@kernel.org> (raw)
In-Reply-To: <2026101210-unicycle-configure-b7b7@gregkh>
The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap()
operations that keep the original VMA in place.
Historically this has led to a lot of bugs where non-obvious interactions
occur between existing mremap() operations and the original VMA.
Fix another of these - self-merge.
Self-merge occurs when a VMA is moved in front of or behind itself and the
attributes of the VMA permit such a merge.
Practically this can only happen for unfaulted anonymous VMAs due to the
page offset equality requirement for merge:
|------------|
| |
| v
|...........||-----------||...........|
| || unfaulted || |
|...........||-----------||...........|
^ |
| |
|------------|
This becomes problematic if the VMA is configured by the user to
mlock-on-fault, i.e. the VMA_LOCKED_BIT, VMA_LOCKONFAULT_BIT VMA flags are
set.
MREMAP_DONTUNMAP clears mlock flags for the source VMA and maintains them
for the destination VMA.
Self-merge makes this impossible (there is only one VMA) and incorrectly
clears the destination VMA's mlock flags.
This causes a leak in mm->locked_vm as clearing this flag does not
decrement the counter and the VMA no longer has VMA_LOCKED_BIT set so it
is not decremented on unmap.
Resolve this by simply disallowing a self-merge in this case - the source
and destination VMAs are kept distinct and then are able to have distinct
mlock() flags.
Update to make the now-redundant self-merge check a VM_WARN_ON_ONCE()
instead to guard against future regressions.
Fixes: e346b3813067 ("mm/mremap: add MREMAP_DONTUNMAP to mremap()")
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Pedro Falcato <pfalcato@suse.de>
Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
Tested-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
Cc: <stable@vger.kernel.org>
(cherry picked from commit 838bb60dc21553e32df90f2f3bac5b905376e7aa)
[ ljs - Converted to work with vma_merge(), tweaked commit msg. ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 2 +-
mm/mmap.c | 23 +++++++++++++++++++----
mm/mremap.c | 7 +++++--
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 09669ea23084..c365c33b9c3a 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3313,7 +3313,7 @@ extern int insert_vm_struct(struct mm_struct *, struct vm_area_struct *);
extern void unlink_file_vma(struct vm_area_struct *);
extern struct vm_area_struct *copy_vma(struct vm_area_struct **,
unsigned long addr, unsigned long len, pgoff_t pgoff,
- bool *need_rmap_locks);
+ bool *need_rmap_locks, bool keep_source);
extern void exit_mmap(struct mm_struct *);
static inline int check_data_rlimit(unsigned long rlim,
diff --git a/mm/mmap.c b/mm/mmap.c
index 8cf23a07ae50..374a6e02c299 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -3383,13 +3383,14 @@ int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
*/
struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
unsigned long addr, unsigned long len, pgoff_t pgoff,
- bool *need_rmap_locks)
+ bool *need_rmap_locks, bool keep_source)
{
struct vm_area_struct *vma = *vmap;
unsigned long vma_start = vma->vm_start;
struct mm_struct *mm = vma->vm_mm;
struct vm_area_struct *new_vma, *prev;
bool faulted_in_anon_vma = true;
+ bool can_self_merge = false;
VMA_ITERATOR(vmi, mm, addr);
/*
@@ -3399,15 +3400,29 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) {
pgoff = addr >> PAGE_SHIFT;
faulted_in_anon_vma = false;
+ can_self_merge = true;
}
new_vma = find_vma_prev(mm, addr, &prev);
if (new_vma && new_vma->vm_start < addr + len)
return NULL; /* should never get here */
- new_vma = vma_merge(&vmi, mm, prev, addr, addr + len, vma->vm_flags,
- vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
- vma->vm_userfaultfd_ctx, anon_vma_name(vma));
+ /*
+ * If the original VMA is kept (MREMAP_DONTUNMAP), the source and
+ * destination VMA must be treated distinctly.
+ *
+ * A merge violates this, so in this case disallow a self-merge. A
+ * self-merge can only occur if the new range is immediately adjacent to
+ * the source VMA, so simply do not attempt to merge in that case.
+ */
+ if (can_self_merge && keep_source &&
+ (addr == vma->vm_end || vma->vm_start == addr + len))
+ new_vma = NULL;
+ else
+ new_vma = vma_merge(&vmi, mm, prev, addr, addr + len,
+ vma->vm_flags, vma->anon_vma, vma->vm_file,
+ pgoff, vma_policy(vma),
+ vma->vm_userfaultfd_ctx, anon_vma_name(vma));
if (new_vma) {
/*
* Source vma may have been merged into new_vma
diff --git a/mm/mremap.c b/mm/mremap.c
index d458ef218a37..18cbc4b526df 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -645,7 +645,7 @@ static unsigned long move_vma(struct vm_area_struct *vma,
vma_start_write(vma);
new_pgoff = vma->vm_pgoff + ((old_addr - vma->vm_start) >> PAGE_SHIFT);
new_vma = copy_vma(&vma, new_addr, new_len, new_pgoff,
- &need_rmap_locks);
+ &need_rmap_locks, flags & MREMAP_DONTUNMAP);
if (!new_vma) {
if (vm_flags & VM_ACCOUNT)
vm_unacct_memory(to_account >> PAGE_SHIFT);
@@ -706,6 +706,9 @@ static unsigned long move_vma(struct vm_area_struct *vma,
untrack_pfn_clear(vma);
if (unlikely(!err && (flags & MREMAP_DONTUNMAP))) {
+ /* Self-merge is disallowed. */
+ VM_WARN_ON_ONCE(new_vma == vma);
+
/* We always clear VM_LOCKED[ONFAULT] on the old vma */
vm_flags_clear(vma, VM_LOCKED_MASK);
@@ -713,7 +716,7 @@ static unsigned long move_vma(struct vm_area_struct *vma,
* anon_vma links of the old vma is no longer needed after its page
* table has been moved.
*/
- if (new_vma != vma && vma->vm_start == old_addr &&
+ if (vma->vm_start == old_addr &&
vma->vm_end == (old_addr + old_len))
unlink_anon_vmas(vma);
--
2.56.0
next prev parent reply other threads:[~2026-10-13 16:48 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 11:45 FAILED: patch "[PATCH] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP" failed to apply to 6.6-stable tree gregkh
2026-10-13 16:48 ` Lorenzo Stoakes (ARM) [this message]
2026-10-14 16:53 ` [PATCH 6.6.y] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP self-merge Sasha Levin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013164837.2509912-1-ljs@kernel.org \
--to=ljs@kernel.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.