[PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma

Pu Lehui posted 4 patches 8 months, 2 weeks ago
[PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Pu Lehui 8 months, 2 weeks ago
From: Pu Lehui <pulehui@huawei.com>

We encountered a BUG alert triggered by Syzkaller as follows:
   BUG: Bad rss-counter state mm:00000000b4a60fca type:MM_ANONPAGES val:1

And we can reproduce it with the following steps:
1. register uprobe on file at zero offset
2. mmap the file at zero offset:
   addr1 = mmap(NULL, 2 * 4096, PROT_NONE, MAP_PRIVATE, fd, 0);
3. mremap part of vma1 to new vma2:
   addr2 = mremap(addr1, 4096, 2 * 4096, MREMAP_MAYMOVE);
4. mremap back to orig addr1:
   mremap(addr2, 4096, 4096, MREMAP_MAYMOVE | MREMAP_FIXED, addr1);

In the step 3, the vma1 range [addr1, addr1 + 4096] will be remap to new
vma2 with range [addr2, addr2 + 8192], and remap uprobe anon page from
the vma1 to vma2, then unmap the vma1 range [addr1, addr1 + 4096]. In
tht step 4, the vma2 range [addr2, addr2 + 4096] will be remap back to
the addr range [addr1, addr1 + 4096]. Since the addr range
[addr1 + 4096, addr1 + 8192] still maps the file, it will take
vma_merge_new_range to expand the range, and then do uprobe_mmap in
vma_complete. Since the merged vma pgoff is also zero offset, it will
install uprobe anon page to the merged vma. However, the upcomming
move_page_tables step, which use set_pte_at to remap the vma2 uprobe pte
to the merged vma, will overwrite the newly uprobe pte in the merged
vma, and lead that pte to be orphan.

Since the uprobe pte will be remapped to the merged vma, we can
remove the unnecessary uprobe_mmap upon merged vma.

This problem was first find in linux-6.6.y and also exists in the
community syzkaller:
https://lore.kernel.org/all/000000000000ada39605a5e71711@google.com/T/

CC: stable@vger.kernel.org
Fixes: 2b1444983508 ("uprobes, mm, x86: Add the ability to install and remove uprobes breakpoints")
Suggested-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
 mm/vma.c | 20 +++++++++++++++++---
 mm/vma.h |  7 +++++++
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/mm/vma.c b/mm/vma.c
index 1c6595f282e5..b2d7c03d8aa4 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -169,6 +169,9 @@ static void init_multi_vma_prep(struct vma_prepare *vp,
 	vp->file = vma->vm_file;
 	if (vp->file)
 		vp->mapping = vma->vm_file->f_mapping;
+
+	if (vmg && vmg->skip_vma_uprobe)
+		vp->skip_vma_uprobe = true;
 }
 
 /*
@@ -358,10 +361,13 @@ static void vma_complete(struct vma_prepare *vp, struct vma_iterator *vmi,
 
 	if (vp->file) {
 		i_mmap_unlock_write(vp->mapping);
-		uprobe_mmap(vp->vma);
 
-		if (vp->adj_next)
-			uprobe_mmap(vp->adj_next);
+		if (!vp->skip_vma_uprobe) {
+			uprobe_mmap(vp->vma);
+
+			if (vp->adj_next)
+				uprobe_mmap(vp->adj_next);
+		}
 	}
 
 	if (vp->remove) {
@@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
 		faulted_in_anon_vma = false;
 	}
 
+	/*
+	 * If the VMA we are copying might contain a uprobe PTE, ensure
+	 * that we do not establish one upon merge. Otherwise, when mremap()
+	 * moves page tables, it will orphan the newly created PTE.
+	 */
+	if (vma->vm_file)
+		vmg.skip_vma_uprobe = true;
+
 	new_vma = find_vma_prev(mm, addr, &vmg.prev);
 	if (new_vma && new_vma->vm_start < addr + len)
 		return NULL;	/* should never get here */
diff --git a/mm/vma.h b/mm/vma.h
index 9a8af9be29a8..0db066e7a45d 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -19,6 +19,8 @@ struct vma_prepare {
 	struct vm_area_struct *insert;
 	struct vm_area_struct *remove;
 	struct vm_area_struct *remove2;
+
+	bool skip_vma_uprobe :1;
 };
 
 struct unlink_vma_file_batch {
@@ -120,6 +122,11 @@ struct vma_merge_struct {
 	 */
 	bool give_up_on_oom :1;
 
+	/*
+	 * If set, skip uprobe_mmap upon merged vma.
+	 */
+	bool skip_vma_uprobe :1;
+
 	/* Internal flags set during merge process: */
 
 	/*
-- 
2.34.1
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by David Hildenbrand 8 months, 2 weeks ago
>   
>   	if (vp->remove) {
> @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
>   		faulted_in_anon_vma = false;
>   	}
>   
> +	/*
> +	 * If the VMA we are copying might contain a uprobe PTE, ensure
> +	 * that we do not establish one upon merge. Otherwise, when mremap()
> +	 * moves page tables, it will orphan the newly created PTE.
> +	 */
> +	if (vma->vm_file)
> +		vmg.skip_vma_uprobe = true;
> +

Assuming we extend the VMA on the way (not merge), would we handle that 
properly?

Or is that not possible on this code path or already broken either way?

-- 
Cheers,

David / dhildenb
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Lorenzo Stoakes 8 months, 2 weeks ago
On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
> >   	if (vp->remove) {
> > @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
> >   		faulted_in_anon_vma = false;
> >   	}
> > +	/*
> > +	 * If the VMA we are copying might contain a uprobe PTE, ensure
> > +	 * that we do not establish one upon merge. Otherwise, when mremap()
> > +	 * moves page tables, it will orphan the newly created PTE.
> > +	 */
> > +	if (vma->vm_file)
> > +		vmg.skip_vma_uprobe = true;
> > +
>
> Assuming we extend the VMA on the way (not merge), would we handle that
> properly?
>
> Or is that not possible on this code path or already broken either way?

I'm not sure in what context you mean expand, vma_merge_new_range() calls
vma_expand() so we call an expand a merge here, and this flag will be
obeyed.

vma_merge_new_range() -> vma_expand() -> commit_merge() -> vma_complete()
will ensure expected behaviour.

>
> --
> Cheers,
>
> David / dhildenb
>
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by David Hildenbrand 8 months, 2 weeks ago
On 02.06.25 13:55, Lorenzo Stoakes wrote:
> On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
>>>    	if (vp->remove) {
>>> @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
>>>    		faulted_in_anon_vma = false;
>>>    	}
>>> +	/*
>>> +	 * If the VMA we are copying might contain a uprobe PTE, ensure
>>> +	 * that we do not establish one upon merge. Otherwise, when mremap()
>>> +	 * moves page tables, it will orphan the newly created PTE.
>>> +	 */
>>> +	if (vma->vm_file)
>>> +		vmg.skip_vma_uprobe = true;
>>> +
>>
>> Assuming we extend the VMA on the way (not merge), would we handle that
>> properly?
>>
>> Or is that not possible on this code path or already broken either way?
> 
> I'm not sure in what context you mean expand, vma_merge_new_range() calls
> vma_expand() so we call an expand a merge here, and this flag will be
> obeyed.

Essentially, an mremap() that grows an existing mapping while moving it.

Assume we have

[ VMA 0 ] [ VMA X]

And want to grow VMA 0 by 1 page.

We cannot grow in-place, so we'll have to copy VMA 0 to another VMA, and 
while at it, expand it by 1 page.

expand_vma()->move_vma()->copy_vma_and_data()->copy_vma()


But maybe I'm getting lost in the code. (e.g., expand_vma() vs. 
vma_expand() ... confusing :) )

-- 
Cheers,

David / dhildenb
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Lorenzo Stoakes 8 months, 2 weeks ago
On Mon, Jun 02, 2025 at 02:26:21PM +0200, David Hildenbrand wrote:
> On 02.06.25 13:55, Lorenzo Stoakes wrote:
> > On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
> > > >    	if (vp->remove) {
> > > > @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
> > > >    		faulted_in_anon_vma = false;
> > > >    	}
> > > > +	/*
> > > > +	 * If the VMA we are copying might contain a uprobe PTE, ensure
> > > > +	 * that we do not establish one upon merge. Otherwise, when mremap()
> > > > +	 * moves page tables, it will orphan the newly created PTE.
> > > > +	 */
> > > > +	if (vma->vm_file)
> > > > +		vmg.skip_vma_uprobe = true;
> > > > +
> > >
> > > Assuming we extend the VMA on the way (not merge), would we handle that
> > > properly?
> > >
> > > Or is that not possible on this code path or already broken either way?
> >
> > I'm not sure in what context you mean expand, vma_merge_new_range() calls
> > vma_expand() so we call an expand a merge here, and this flag will be
> > obeyed.
>
> Essentially, an mremap() that grows an existing mapping while moving it.
>
> Assume we have
>
> [ VMA 0 ] [ VMA X]
>
> And want to grow VMA 0 by 1 page.
>
> We cannot grow in-place, so we'll have to copy VMA 0 to another VMA, and
> while at it, expand it by 1 page.
>
> expand_vma()->move_vma()->copy_vma_and_data()->copy_vma()

OK so in that case you'd not have a merge at all, you'd have a new VMA and all
would be well and beautiful :) or I mean hopefully. Maybe?

>
>
> But maybe I'm getting lost in the code. (e.g., expand_vma() vs. vma_expand()
> ... confusing :) )

Yeah I think Liam or somebody else called me out for this :P I mean it's
accurate naming in mremap.c but that's kinda in the context of the mremap.

For VMA merging vma_expand() is used generally for a new VMA, since you're
always expanding into the gap, but because we all did terrible things in past
lives also called by relocate_vma_down() which is a kinda-hack for initial stack
relocation on initial process setup.

It maybe needs renaming... But expand kinda accurately describes what's going on
just semi-overloaded vs. mremap() now :>)

VMA merge code now at least readable enough that you can pick up on the various
oddnesses clearly :P

>
> --
> Cheers,
>
> David / dhildenb
>
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by David Hildenbrand 8 months, 1 week ago
On 02.06.25 15:26, Lorenzo Stoakes wrote:
> On Mon, Jun 02, 2025 at 02:26:21PM +0200, David Hildenbrand wrote:
>> On 02.06.25 13:55, Lorenzo Stoakes wrote:
>>> On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
>>>>>     	if (vp->remove) {
>>>>> @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
>>>>>     		faulted_in_anon_vma = false;
>>>>>     	}
>>>>> +	/*
>>>>> +	 * If the VMA we are copying might contain a uprobe PTE, ensure
>>>>> +	 * that we do not establish one upon merge. Otherwise, when mremap()
>>>>> +	 * moves page tables, it will orphan the newly created PTE.
>>>>> +	 */
>>>>> +	if (vma->vm_file)
>>>>> +		vmg.skip_vma_uprobe = true;
>>>>> +
>>>>
>>>> Assuming we extend the VMA on the way (not merge), would we handle that
>>>> properly?
>>>>
>>>> Or is that not possible on this code path or already broken either way?
>>>
>>> I'm not sure in what context you mean expand, vma_merge_new_range() calls
>>> vma_expand() so we call an expand a merge here, and this flag will be
>>> obeyed.
>>
>> Essentially, an mremap() that grows an existing mapping while moving it.
>>
>> Assume we have
>>
>> [ VMA 0 ] [ VMA X]
>>
>> And want to grow VMA 0 by 1 page.
>>
>> We cannot grow in-place, so we'll have to copy VMA 0 to another VMA, and
>> while at it, expand it by 1 page.
>>
>> expand_vma()->move_vma()->copy_vma_and_data()->copy_vma()
> 
> OK so in that case you'd not have a merge at all, you'd have a new VMA and all
> would be well and beautiful :) or I mean hopefully. Maybe?

I'm really not sure. :)

Could there be some very odd cases like

[VMA 0 ][ VMA 1 ][ VMA X]

and when we mremap() [ VMA 1 ] to grow, we would place it before [VMA 0 
], and just by pure lick end up merging with that if the ranges match?

We're in the corner cases now, ... so this might not be relevant. But I 
hope we can clean up that uprobe mmap call later ...

> 
>>
>>
>> But maybe I'm getting lost in the code. (e.g., expand_vma() vs. vma_expand()
>> ... confusing :) )
> 
> Yeah I think Liam or somebody else called me out for this :P I mean it's
> accurate naming in mremap.c but that's kinda in the context of the mremap.
> 
> For VMA merging vma_expand() is used generally for a new VMA, since you're
> always expanding into the gap, but because we all did terrible things in past
> lives also called by relocate_vma_down() which is a kinda-hack for initial stack
> relocation on initial process setup.
> 
> It maybe needs renaming... But expand kinda accurately describes what's going on
> just semi-overloaded vs. mremap() now :>)
> 
> VMA merge code now at least readable enough that you can pick up on the various
> oddnesses clearly :P

:)

-- 
Cheers,

David / dhildenb
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Lorenzo Stoakes 8 months, 1 week ago
On Mon, Jun 02, 2025 at 06:28:58PM +0200, David Hildenbrand wrote:
> On 02.06.25 15:26, Lorenzo Stoakes wrote:
> > On Mon, Jun 02, 2025 at 02:26:21PM +0200, David Hildenbrand wrote:
> > > On 02.06.25 13:55, Lorenzo Stoakes wrote:
> > > > On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
> > > > > >     	if (vp->remove) {
> > > > > > @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
> > > > > >     		faulted_in_anon_vma = false;
> > > > > >     	}
> > > > > > +	/*
> > > > > > +	 * If the VMA we are copying might contain a uprobe PTE, ensure
> > > > > > +	 * that we do not establish one upon merge. Otherwise, when mremap()
> > > > > > +	 * moves page tables, it will orphan the newly created PTE.
> > > > > > +	 */
> > > > > > +	if (vma->vm_file)
> > > > > > +		vmg.skip_vma_uprobe = true;
> > > > > > +
> > > > >
> > > > > Assuming we extend the VMA on the way (not merge), would we handle that
> > > > > properly?
> > > > >
> > > > > Or is that not possible on this code path or already broken either way?
> > > >
> > > > I'm not sure in what context you mean expand, vma_merge_new_range() calls
> > > > vma_expand() so we call an expand a merge here, and this flag will be
> > > > obeyed.
> > >
> > > Essentially, an mremap() that grows an existing mapping while moving it.
> > >
> > > Assume we have
> > >
> > > [ VMA 0 ] [ VMA X]
> > >
> > > And want to grow VMA 0 by 1 page.
> > >
> > > We cannot grow in-place, so we'll have to copy VMA 0 to another VMA, and
> > > while at it, expand it by 1 page.
> > >
> > > expand_vma()->move_vma()->copy_vma_and_data()->copy_vma()
> >
> > OK so in that case you'd not have a merge at all, you'd have a new VMA and all
> > would be well and beautiful :) or I mean hopefully. Maybe?
>
> I'm really not sure. :)
>
> Could there be some very odd cases like
>
> [VMA 0 ][ VMA 1 ][ VMA X]
>
> and when we mremap() [ VMA 1 ] to grow, we would place it before [VMA 0 ],
> and just by pure lick end up merging with that if the ranges match?

When we invoke copy_vma() we pass vrm->new_addr and vrm->new_len so this would
trigger a merge and the correct uprobe handling.

Since we just don't trigger the breakpoint install in this situation, we'd
correctly move over the breakpoint to the right position, and overwrite anything
we expanded into.

I do want to do a mremap doc actually to cover all the weird cases, because
there's some weird stuff in there and it's worth covering off stuff for users
and stuff for kernel people :)

>
> We're in the corner cases now, ... so this might not be relevant. But I hope
> we can clean up that uprobe mmap call later ...

Yeah with this initial fix in we can obviously revisit as needed!

>
> >
> > >
> > >
> > > But maybe I'm getting lost in the code. (e.g., expand_vma() vs. vma_expand()
> > > ... confusing :) )
> >
> > Yeah I think Liam or somebody else called me out for this :P I mean it's
> > accurate naming in mremap.c but that's kinda in the context of the mremap.
> >
> > For VMA merging vma_expand() is used generally for a new VMA, since you're
> > always expanding into the gap, but because we all did terrible things in past
> > lives also called by relocate_vma_down() which is a kinda-hack for initial stack
> > relocation on initial process setup.
> >
> > It maybe needs renaming... But expand kinda accurately describes what's going on
> > just semi-overloaded vs. mremap() now :>)
> >
> > VMA merge code now at least readable enough that you can pick up on the various
> > oddnesses clearly :P
>
> :)
>
> --
> Cheers,
>
> David / dhildenb
>

Cheers, Lorenzo
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by David Hildenbrand 8 months, 1 week ago
On 02.06.25 19:01, Lorenzo Stoakes wrote:
> On Mon, Jun 02, 2025 at 06:28:58PM +0200, David Hildenbrand wrote:
>> On 02.06.25 15:26, Lorenzo Stoakes wrote:
>>> On Mon, Jun 02, 2025 at 02:26:21PM +0200, David Hildenbrand wrote:
>>>> On 02.06.25 13:55, Lorenzo Stoakes wrote:
>>>>> On Fri, May 30, 2025 at 08:51:14PM +0200, David Hildenbrand wrote:
>>>>>>>      	if (vp->remove) {
>>>>>>> @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
>>>>>>>      		faulted_in_anon_vma = false;
>>>>>>>      	}
>>>>>>> +	/*
>>>>>>> +	 * If the VMA we are copying might contain a uprobe PTE, ensure
>>>>>>> +	 * that we do not establish one upon merge. Otherwise, when mremap()
>>>>>>> +	 * moves page tables, it will orphan the newly created PTE.
>>>>>>> +	 */
>>>>>>> +	if (vma->vm_file)
>>>>>>> +		vmg.skip_vma_uprobe = true;
>>>>>>> +
>>>>>>
>>>>>> Assuming we extend the VMA on the way (not merge), would we handle that
>>>>>> properly?
>>>>>>
>>>>>> Or is that not possible on this code path or already broken either way?
>>>>>
>>>>> I'm not sure in what context you mean expand, vma_merge_new_range() calls
>>>>> vma_expand() so we call an expand a merge here, and this flag will be
>>>>> obeyed.
>>>>
>>>> Essentially, an mremap() that grows an existing mapping while moving it.
>>>>
>>>> Assume we have
>>>>
>>>> [ VMA 0 ] [ VMA X]
>>>>
>>>> And want to grow VMA 0 by 1 page.
>>>>
>>>> We cannot grow in-place, so we'll have to copy VMA 0 to another VMA, and
>>>> while at it, expand it by 1 page.
>>>>
>>>> expand_vma()->move_vma()->copy_vma_and_data()->copy_vma()
>>>
>>> OK so in that case you'd not have a merge at all, you'd have a new VMA and all
>>> would be well and beautiful :) or I mean hopefully. Maybe?
>>
>> I'm really not sure. :)
>>
>> Could there be some very odd cases like
>>
>> [VMA 0 ][ VMA 1 ][ VMA X]
>>
>> and when we mremap() [ VMA 1 ] to grow, we would place it before [VMA 0 ],
>> and just by pure lick end up merging with that if the ranges match?
> 
> When we invoke copy_vma() we pass vrm->new_addr and vrm->new_len so this would
> trigger a merge and the correct uprobe handling.
> 
> Since we just don't trigger the breakpoint install in this situation, we'd
> correctly move over the breakpoint to the right position, and overwrite anything
> we expanded into.
> 
> I do want to do a mremap doc actually to cover all the weird cases, because
> there's some weird stuff in there and it's worth covering off stuff for users
> and stuff for kernel people :)
> 
>>
>> We're in the corner cases now, ... so this might not be relevant. But I hope
>> we can clean up that uprobe mmap call later ...
> 
> Yeah with this initial fix in we can obviously revisit as needed!

As Andrew was asking off-list:

Acked-by: David Hildenbrand <david@redhat.com>


:)

-- 
Cheers,

David / dhildenb
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Andrew Morton 8 months, 1 week ago
On Tue, 3 Jun 2025 14:16:37 +0200 David Hildenbrand <david@redhat.com> wrote:

> >> We're in the corner cases now, ... so this might not be relevant. But I hope
> >> we can clean up that uprobe mmap call later ...
> > 
> > Yeah with this initial fix in we can obviously revisit as needed!
> 
> As Andrew was asking off-list:
> 
> Acked-by: David Hildenbrand <david@redhat.com>

OK, thanks.  I'll aim to send this series in to Linus during this merge
window.
Re: [PATCH v1 1/4] mm: Fix uprobe pte be overwritten when expanding vma
Posted by Lorenzo Stoakes 8 months, 2 weeks ago
On Thu, May 29, 2025 at 03:56:47PM +0000, Pu Lehui wrote:
> From: Pu Lehui <pulehui@huawei.com>
>
> We encountered a BUG alert triggered by Syzkaller as follows:
>    BUG: Bad rss-counter state mm:00000000b4a60fca type:MM_ANONPAGES val:1
>
> And we can reproduce it with the following steps:
> 1. register uprobe on file at zero offset
> 2. mmap the file at zero offset:
>    addr1 = mmap(NULL, 2 * 4096, PROT_NONE, MAP_PRIVATE, fd, 0);
> 3. mremap part of vma1 to new vma2:
>    addr2 = mremap(addr1, 4096, 2 * 4096, MREMAP_MAYMOVE);
> 4. mremap back to orig addr1:
>    mremap(addr2, 4096, 4096, MREMAP_MAYMOVE | MREMAP_FIXED, addr1);
>
> In the step 3, the vma1 range [addr1, addr1 + 4096] will be remap to new
> vma2 with range [addr2, addr2 + 8192], and remap uprobe anon page from
> the vma1 to vma2, then unmap the vma1 range [addr1, addr1 + 4096]. In
> tht step 4, the vma2 range [addr2, addr2 + 4096] will be remap back to
> the addr range [addr1, addr1 + 4096]. Since the addr range
> [addr1 + 4096, addr1 + 8192] still maps the file, it will take
> vma_merge_new_range to expand the range, and then do uprobe_mmap in
> vma_complete. Since the merged vma pgoff is also zero offset, it will
> install uprobe anon page to the merged vma. However, the upcomming
> move_page_tables step, which use set_pte_at to remap the vma2 uprobe pte
> to the merged vma, will overwrite the newly uprobe pte in the merged
> vma, and lead that pte to be orphan.
>
> Since the uprobe pte will be remapped to the merged vma, we can
> remove the unnecessary uprobe_mmap upon merged vma.
>
> This problem was first find in linux-6.6.y and also exists in the
> community syzkaller:
> https://lore.kernel.org/all/000000000000ada39605a5e71711@google.com/T/
>
> CC: stable@vger.kernel.org
> Fixes: 2b1444983508 ("uprobes, mm, x86: Add the ability to install and remove uprobes breakpoints")
> Suggested-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>

Reviewed-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>

> ---
>  mm/vma.c | 20 +++++++++++++++++---
>  mm/vma.h |  7 +++++++
>  2 files changed, 24 insertions(+), 3 deletions(-)
>
> diff --git a/mm/vma.c b/mm/vma.c
> index 1c6595f282e5..b2d7c03d8aa4 100644
> --- a/mm/vma.c
> +++ b/mm/vma.c
> @@ -169,6 +169,9 @@ static void init_multi_vma_prep(struct vma_prepare *vp,
>  	vp->file = vma->vm_file;
>  	if (vp->file)
>  		vp->mapping = vma->vm_file->f_mapping;
> +
> +	if (vmg && vmg->skip_vma_uprobe)
> +		vp->skip_vma_uprobe = true;
>  }
>
>  /*
> @@ -358,10 +361,13 @@ static void vma_complete(struct vma_prepare *vp, struct vma_iterator *vmi,
>
>  	if (vp->file) {
>  		i_mmap_unlock_write(vp->mapping);
> -		uprobe_mmap(vp->vma);
>
> -		if (vp->adj_next)
> -			uprobe_mmap(vp->adj_next);
> +		if (!vp->skip_vma_uprobe) {
> +			uprobe_mmap(vp->vma);
> +
> +			if (vp->adj_next)
> +				uprobe_mmap(vp->adj_next);
> +		}
>  	}
>
>  	if (vp->remove) {
> @@ -1823,6 +1829,14 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
>  		faulted_in_anon_vma = false;
>  	}
>
> +	/*
> +	 * If the VMA we are copying might contain a uprobe PTE, ensure
> +	 * that we do not establish one upon merge. Otherwise, when mremap()
> +	 * moves page tables, it will orphan the newly created PTE.
> +	 */
> +	if (vma->vm_file)
> +		vmg.skip_vma_uprobe = true;
> +
>  	new_vma = find_vma_prev(mm, addr, &vmg.prev);
>  	if (new_vma && new_vma->vm_start < addr + len)
>  		return NULL;	/* should never get here */
> diff --git a/mm/vma.h b/mm/vma.h
> index 9a8af9be29a8..0db066e7a45d 100644
> --- a/mm/vma.h
> +++ b/mm/vma.h
> @@ -19,6 +19,8 @@ struct vma_prepare {
>  	struct vm_area_struct *insert;
>  	struct vm_area_struct *remove;
>  	struct vm_area_struct *remove2;
> +
> +	bool skip_vma_uprobe :1;
>  };
>
>  struct unlink_vma_file_batch {
> @@ -120,6 +122,11 @@ struct vma_merge_struct {
>  	 */
>  	bool give_up_on_oom :1;
>
> +	/*
> +	 * If set, skip uprobe_mmap upon merged vma.
> +	 */
> +	bool skip_vma_uprobe :1;
> +
>  	/* Internal flags set during merge process: */
>
>  	/*
> --
> 2.34.1
>