2019-08-23 23:39:21

by Ralph Campbell

[permalink] [raw]
Subject: [PATCH 2/2] mm/hmm: hmm_range_fault() infinite loop

Normally, callers to handle_mm_fault() are supposed to check the
vma->vm_flags first. hmm_range_fault() checks for VM_READ but doesn't
check for VM_WRITE if the caller requests a page to be faulted in
with write permission (via the hmm_range.pfns[] value).
If the vma is write protected, this can result in an infinite loop:
hmm_range_fault()
walk_page_range()
...
hmm_vma_walk_hole()
hmm_vma_walk_hole_()
hmm_vma_do_fault()
handle_mm_fault(FAULT_FLAG_WRITE)
/* returns VM_FAULT_WRITE */
/* returns -EBUSY */
/* returns -EBUSY */
/* returns -EBUSY */
/* loops on -EBUSY and range->valid */
Prevent this by checking for vma->vm_flags & VM_WRITE before calling
handle_mm_fault().

Signed-off-by: Ralph Campbell <[email protected]>
---
mm/hmm.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/mm/hmm.c b/mm/hmm.c
index 29371485fe94..4882b83aeccb 100644
--- a/mm/hmm.c
+++ b/mm/hmm.c
@@ -292,6 +292,9 @@ static int hmm_vma_walk_hole_(unsigned long addr, unsigned long end,
hmm_vma_walk->last = addr;
i = (addr - range->start) >> PAGE_SHIFT;

+ if (write_fault && walk->vma && !(walk->vma->vm_flags & VM_WRITE))
+ return -EPERM;
+
for (; addr < end; addr += PAGE_SIZE, i++) {
pfns[i] = range->values[HMM_PFN_NONE];
if (fault || write_fault) {
--
2.20.1


2019-08-24 22:41:08

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH 2/2] mm/hmm: hmm_range_fault() infinite loop

On Fri, Aug 23, 2019 at 03:17:53PM -0700, Ralph Campbell wrote:
> Normally, callers to handle_mm_fault() are supposed to check the
> vma->vm_flags first. hmm_range_fault() checks for VM_READ but doesn't
> check for VM_WRITE if the caller requests a page to be faulted in
> with write permission (via the hmm_range.pfns[] value).
> If the vma is write protected, this can result in an infinite loop:
> hmm_range_fault()
> walk_page_range()
> ...
> hmm_vma_walk_hole()
> hmm_vma_walk_hole_()
> hmm_vma_do_fault()
> handle_mm_fault(FAULT_FLAG_WRITE)
> /* returns VM_FAULT_WRITE */
> /* returns -EBUSY */
> /* returns -EBUSY */
> /* returns -EBUSY */
> /* loops on -EBUSY and range->valid */
> Prevent this by checking for vma->vm_flags & VM_WRITE before calling
> handle_mm_fault().
>
> Signed-off-by: Ralph Campbell <[email protected]>

Looks good,

Reviewed-by: Christoph Hellwig <[email protected]>

2019-08-27 18:43:37

by Jason Gunthorpe

[permalink] [raw]
Subject: Re: [PATCH 2/2] mm/hmm: hmm_range_fault() infinite loop

On Fri, Aug 23, 2019 at 03:17:53PM -0700, Ralph Campbell wrote:

> Signed-off-by: Ralph Campbell <[email protected]>
> mm/hmm.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/mm/hmm.c b/mm/hmm.c
> index 29371485fe94..4882b83aeccb 100644
> +++ b/mm/hmm.c
> @@ -292,6 +292,9 @@ static int hmm_vma_walk_hole_(unsigned long addr, unsigned long end,
> hmm_vma_walk->last = addr;
> i = (addr - range->start) >> PAGE_SHIFT;
>
> + if (write_fault && walk->vma && !(walk->vma->vm_flags & VM_WRITE))
> + return -EPERM;

Can walk->vma be NULL here? hmm_vma_do_fault() touches it
unconditionally.

Jason

2019-08-27 20:17:25

by Ralph Campbell

[permalink] [raw]
Subject: Re: [PATCH 2/2] mm/hmm: hmm_range_fault() infinite loop


On 8/27/19 11:41 AM, Jason Gunthorpe wrote:
> On Fri, Aug 23, 2019 at 03:17:53PM -0700, Ralph Campbell wrote:
>
>> Signed-off-by: Ralph Campbell <[email protected]>
>> mm/hmm.c | 3 +++
>> 1 file changed, 3 insertions(+)
>>
>> diff --git a/mm/hmm.c b/mm/hmm.c
>> index 29371485fe94..4882b83aeccb 100644
>> +++ b/mm/hmm.c
>> @@ -292,6 +292,9 @@ static int hmm_vma_walk_hole_(unsigned long addr, unsigned long end,
>> hmm_vma_walk->last = addr;
>> i = (addr - range->start) >> PAGE_SHIFT;
>>
>> + if (write_fault && walk->vma && !(walk->vma->vm_flags & VM_WRITE))
>> + return -EPERM;
>
> Can walk->vma be NULL here? hmm_vma_do_fault() touches it
> unconditionally.
>
> Jason
>
walk->vma can be NULL. hmm_vma_do_fault() no longer touches it
unconditionally, that is what the preceding patch fixes.
I suppose I could change hmm_vma_walk_hole_() to check for NULL
and fill in the pfns[] array, I just chose to handle it in
hmm_vma_do_fault().

2019-08-27 22:25:22

by Jason Gunthorpe

[permalink] [raw]
Subject: Re: [PATCH 2/2] mm/hmm: hmm_range_fault() infinite loop

On Tue, Aug 27, 2019 at 01:16:13PM -0700, Ralph Campbell wrote:
>
> On 8/27/19 11:41 AM, Jason Gunthorpe wrote:
> > On Fri, Aug 23, 2019 at 03:17:53PM -0700, Ralph Campbell wrote:
> >
> > > Signed-off-by: Ralph Campbell <[email protected]>
> > > mm/hmm.c | 3 +++
> > > 1 file changed, 3 insertions(+)
> > >
> > > diff --git a/mm/hmm.c b/mm/hmm.c
> > > index 29371485fe94..4882b83aeccb 100644
> > > +++ b/mm/hmm.c
> > > @@ -292,6 +292,9 @@ static int hmm_vma_walk_hole_(unsigned long addr, unsigned long end,
> > > hmm_vma_walk->last = addr;
> > > i = (addr - range->start) >> PAGE_SHIFT;
> > > + if (write_fault && walk->vma && !(walk->vma->vm_flags & VM_WRITE))
> > > + return -EPERM;
> >
> > Can walk->vma be NULL here? hmm_vma_do_fault() touches it
> > unconditionally.
> >
> > Jason
> >
> walk->vma can be NULL. hmm_vma_do_fault() no longer touches it
> unconditionally, that is what the preceding patch fixes.
> I suppose I could change hmm_vma_walk_hole_() to check for NULL
> and fill in the pfns[] array, I just chose to handle it in
> hmm_vma_do_fault().

Okay, yes maybe long term it would be clearer to do the vma null check
closer to the start of the callback, but this is a good enough bug fix

Reviewed-by: Jason Gunthorpe <[email protected]>

Jason