FAILED: patch "[PATCH] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP" failed to apply to 7.2-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 7.2-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.2.y
git checkout FETCH_HEAD
git cherry-pick -x 838bb60dc21553e32df90f2f3bac5b905376e7aa
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026101206-jawed-thriving-05c3@gregkh' --subject-prefix 'PATCH 7.2.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 838bb60dc21553e32df90f2f3bac5b905376e7aa Mon Sep 17 00:00:00 2001
From: "Lorenzo Stoakes (ARM)" <[email protected]>
Date: Wed, 30 Sep 2026 19:47:09 +0100
Subject: [PATCH] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP
self-merge
Patch series "mm/mremap: fix two issues with MREMAP_DONTUNMAP", v2.
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.
Commit 397432cab17b ("mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP") fixed an accidentally introduced bug around mm->locked_vm accounting, but this wasn't the only issue.
And thus history repeats itself, as it turns out that mm->locked_vm accounting is broken by MREMAP_DONTUNMAP yet again by two further cases, and has been broken ever since the feature was introduced.
Both relate to the fact that VMA_LOCKED_BIT is cleared on the source VMA (it has to be as all page tables are moved):
1. If an unfaulted VMA_LOCKONFAULT_BIT anonymous VMA self-merges it clears the VMA_LOCKED_BIT flag and permanently leaks mm->locked_vm pages.
2. If a partial mremap() is performed on a locked VMA there is a leak equal to the number of pages not copied.
(Both for MREMAP_DONTUNMAP operations only)
Both issues can be fixed by treating the source range as distinct from the destination range, which is the definition of what MREMAP_DONTUNMAP does so is appropriate.
In case 1, simply disallow the self-merge, keeping adjacent source and destination VMAs distinct.
In case 2, split the source range ahead of time if the VMA is mlock()'d, so accounting is always correct.
Both changes were tested locally and confirmed to fix the issues.
For the purposes of a backport, the fixes are kept distinct, a follow-up series can add self-tests.
This patch (of 2):
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 dontunmap_complete() to make the now-redundant self-merge check a VM_WARN_ON_ONCE() instead to guard against future regressions.
Also update the VMA userland tests to reflect the change.
mm/mremap.c +6 -2
@@ -1275,7 +1275,8 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm,PAGETABLE_MOVE (pmc,NULL ,NULL , vrm->addr, vrm->new_addr, vrm->old_len); new_vma =copy_vma (&vma, vrm->new_addr, vrm->new_len, new_pgoff, - new_anon_pgoff, &pmc.need_rmap_locks); + new_anon_pgoff, &pmc.need_rmap_locks, + vrm->flags &MREMAP_DONTUNMAP );if (!new_vma) {vrm_uncharge (vrm); *new_vma_ptr =NULL ;
@@ -1335,6 +1336,9 @@ static void dontunmap_complete(struct vma_remap_struct *vrm,unsigned long old_start = vma->vm_start;unsigned long old_end = vma->vm_end; +/* Self-merge is disallowed. */ +VM_WARN_ON_ONCE (new_vma == vma); +/* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */ vma_clear_flags_mask (vma,VMA_LOCKED_MASK );
@@ -1342,7 +1346,7 @@ static void dontunmap_complete(struct vma_remap_struct *vrm,* anon_vma links of the old vma is no longer needed after its page * table has been moved. */ -if (new_vma != vma && start == old_start && end == old_end) { +if (start == old_start && end == old_end) {const pgoff_t pgoff_unfaulted = vma->vm_start >>PAGE_SHIFT ;unlink_anon_vmas (vma);
mm/vma.c +16 -1
@@ -1943,7 +1943,7 @@ static int vma_link(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, -pgoff_t anon_pgoff,bool *need_rmap_locks) +pgoff_t anon_pgoff,bool *need_rmap_locks,bool keep_source) {struct vm_area_struct *vma = *vmap;unsigned long old_vma_start = vma->vm_start;
@@ -1981,6 +1981,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, vmg.pgoff = pgoff; vmg.anon_pgoff = anon_pgoff; vmg.next =vma_iter_next_rewind (&vmi,NULL ); + +/* +* 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. +*/ +if (can_self_merge && keep_source) { +if (vmg.prev == vma) + vmg.prev =NULL ; +if (vmg.next == vma) + vmg.next =NULL ; + can_self_merge = false; + } + new_vma =vma_merge_copied_range (&vmg);if (new_vma) {
mm/vma.h +1 -1
@@ -535,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb,struct vm_area_struct *copy_vma (struct vm_area_struct **vmap,unsigned long addr,unsigned long len,pgoff_t pgoff, -pgoff_t anon_pgoff,bool *need_rmap_locks); +pgoff_t anon_pgoff,bool *need_rmap_locks,bool keep_source);struct anon_vma *find_mergeable_anon_vma (struct vm_area_struct *vma);
tools/testing/vma/tests/vma.c +5 -5
@@ -40,7 +40,7 @@ static bool test_copy_vma(void) vma =alloc_and_link_vma (&mm,0x1000 ,0x2000 ,1 , vma_flags);vma_set_anonymous (vma); vma_orig = vma; - vma_new =copy_vma (&vma,0x2000 ,0x1000 ,1 ,1 , &need_locks); + vma_new =copy_vma (&vma,0x2000 ,0x1000 ,1 ,1 , &need_locks, false);ASSERT_EQ (vma_new, vma_orig);ASSERT_EQ (vma, vma_orig);ASSERT_EQ (vma_new->vm_start,0x1000 );
@@ -53,7 +53,7 @@ static bool test_copy_vma(void) vma =alloc_and_link_vma (&mm,0x2000 ,0x3000 ,2 , vma_flags);vma_set_anonymous (vma); vma_orig = vma; - vma_new =copy_vma (&vma,0x1000 ,0x1000 ,2 ,2 , &need_locks); + vma_new =copy_vma (&vma,0x1000 ,0x1000 ,2 ,2 , &need_locks, false);ASSERT_EQ (vma_new, vma_orig);ASSERT_EQ (vma, vma_orig);ASSERT_EQ (vma_new->vm_start,0x1000 );
@@ -71,7 +71,7 @@ static bool test_copy_vma(void) vma =alloc_and_link_vma (&mm,0x3000 ,0x4000 ,3 , vma_flags);vma_set_anonymous (vma); vma_orig = vma; - vma_new =copy_vma (&vma,0x2000 ,0x1000 ,3 ,3 , &need_locks); + vma_new =copy_vma (&vma,0x2000 ,0x1000 ,3 ,3 , &need_locks, false);ASSERT_NE (vma_new, vma_orig);ASSERT_EQ (vma_new, vma);ASSERT_EQ (vma_new->vm_start,0x1000 );
@@ -82,7 +82,7 @@ static bool test_copy_vma(void)/* Move backwards and do not merge. */ vma =alloc_and_link_vma (&mm,0x3000 ,0x5000 ,3 , vma_flags); - vma_new =copy_vma (&vma,0 ,0x2000 ,0 ,3 , &need_locks); + vma_new =copy_vma (&vma,0 ,0x2000 ,0 ,3 , &need_locks, false);ASSERT_NE (vma_new, vma);ASSERT_EQ (vma_new->vm_start,0 );ASSERT_EQ (vma_new->vm_end,0x2000 );
@@ -95,7 +95,7 @@ static bool test_copy_vma(void) vma =alloc_and_link_vma (&mm,0 ,0x2000 ,0 , vma_flags); vma_next =alloc_and_link_vma (&mm,0x6000 ,0x8000 ,6 , vma_flags); - vma_new =copy_vma (&vma,0x4000 ,0x2000 ,4 ,4 , &need_locks); + vma_new =copy_vma (&vma,0x4000 ,0x2000 ,4 ,4 , &need_locks, false);vma_assert_attached (vma_new);ASSERT_EQ (vma_new, vma_next);
Sasha Levin
maintainerPATCH 1/1 mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP self-merge
From: "Lorenzo Stoakes (ARM)" <[email protected]>
[ Upstream commit 838bb60dc21553e32df90f2f3bac5b905376e7aa ]
Patch series "mm/mremap: fix two issues with MREMAP_DONTUNMAP", v2.
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.
Commit 397432cab17b ("mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP") fixed an accidentally introduced bug around mm->locked_vm accounting, but this wasn't the only issue.
And thus history repeats itself, as it turns out that mm->locked_vm accounting is broken by MREMAP_DONTUNMAP yet again by two further cases, and has been broken ever since the feature was introduced.
Both relate to the fact that VMA_LOCKED_BIT is cleared on the source VMA (it has to be as all page tables are moved):
1. If an unfaulted VMA_LOCKONFAULT_BIT anonymous VMA self-merges it clears the VMA_LOCKED_BIT flag and permanently leaks mm->locked_vm pages.
2. If a partial mremap() is performed on a locked VMA there is a leak equal to the number of pages not copied.
(Both for MREMAP_DONTUNMAP operations only)
Both issues can be fixed by treating the source range as distinct from the destination range, which is the definition of what MREMAP_DONTUNMAP does so is appropriate.
In case 1, simply disallow the self-merge, keeping adjacent source and destination VMAs distinct.
In case 2, split the source range ahead of time if the VMA is mlock()'d, so accounting is always correct.
Both changes were tested locally and confirmed to fix the issues.
For the purposes of a backport, the fixes are kept distinct, a follow-up series can add self-tests.
This patch (of 2):
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 dontunmap_complete() to make the now-redundant self-merge check a VM_WARN_ON_ONCE() instead to guard against future regressions.
Also update the VMA userland tests to reflect the change.
--- mm/mremap.c | 8 ++++++-- mm/vma.c | 17 ++++++++++++++++- mm/vma.h | 2 +- tools/testing/vma/tests/vma.c | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-)
mm/mremap.c +6 -2
@@ -1295,7 +1295,8 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm,PAGETABLE_MOVE (pmc,NULL ,NULL , vrm->addr, vrm->new_addr, vrm->old_len); new_vma =copy_vma (&vma, vrm->new_addr, vrm->new_len, new_pgoff, - &pmc.need_rmap_locks); + &pmc.need_rmap_locks, + vrm->flags &MREMAP_DONTUNMAP );if (!new_vma) {vrm_uncharge (vrm); *new_vma_ptr =NULL ;
@@ -1355,6 +1356,9 @@ static void dontunmap_complete(struct vma_remap_struct *vrm,unsigned long old_start = vma->vm_start;unsigned long old_end = vma->vm_end; +/* 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 );
@@ -1362,7 +1366,7 @@ static void dontunmap_complete(struct vma_remap_struct *vrm,* anon_vma links of the old vma is no longer needed after its page * table has been moved. */ -if (new_vma != vma && start == old_start && end == old_end) { +if (start == old_start && end == old_end) {const pgoff_t pgoff_unfaulted = vma->vm_start >>PAGE_SHIFT ;unlink_anon_vmas (vma);
mm/vma.c +16 -1
@@ -1863,7 +1863,7 @@ static int vma_link(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;
@@ -1896,6 +1896,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, vmg.pgoff = pgoff; vmg.next =vma_iter_next_rewind (&vmi,NULL ); + +/* +* 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. +*/ +if (!faulted_in_anon_vma && keep_source) { +if (vmg.prev == vma) + vmg.prev =NULL ; +if (vmg.next == vma) + vmg.next =NULL ; + faulted_in_anon_vma = true; + } + new_vma =vma_merge_copied_range (&vmg);if (new_vma) {
mm/vma.h +1 -1
@@ -449,7 +449,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb,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 anon_vma *find_mergeable_anon_vma (struct vm_area_struct *vma);
tools/testing/vma/tests/vma.c +2 -2
@@ -38,7 +38,7 @@ static bool test_copy_vma(void)/* Move backwards and do not merge. */ vma =alloc_and_link_vma (&mm,0x3000 ,0x5000 ,3 , vma_flags); - vma_new =copy_vma (&vma,0 ,0x2000 ,0 , &need_locks); + vma_new =copy_vma (&vma,0 ,0x2000 ,0 , &need_locks, false);ASSERT_NE (vma_new, vma);ASSERT_EQ (vma_new->vm_start,0 );ASSERT_EQ (vma_new->vm_end,0x2000 );
@@ -51,7 +51,7 @@ static bool test_copy_vma(void) vma =alloc_and_link_vma (&mm,0 ,0x2000 ,0 , vma_flags); vma_next =alloc_and_link_vma (&mm,0x6000 ,0x8000 ,6 , vma_flags); - vma_new =copy_vma (&vma,0x4000 ,0x2000 ,4 , &need_locks); + vma_new =copy_vma (&vma,0x4000 ,0x2000 ,4 , &need_locks, false);vma_assert_attached (vma_new);ASSERT_EQ (vma_new, vma_next);
Signature · 1 line
2.53.0