mm/get_user_pages_fast fixes, cleanups
Subsystems (9)
v2 cover mm/get_user_pages_fast fixes, cleanups
Turns out get_user_pages_fast and __get_user_pages_fast return different values on error when given a single page: __get_user_pages_fast returns
0. get_user_pages_fast returns either 0 or an error.
Callers of get_user_pages_fast expect an error so fix it up to return an error consistently.
Stress the difference between get_user_pages_fast and __get_user_pages_fast to make sure callers aren't confused.
Changes from v1:
limit code changes to get_user_pages_fast. document __get_user_pages_fast
fix a bug in caller
Michael S. Tsirkin (3):
mm/gup_benchmark: handle gup failures
gup: return -EFAULT on access_ok failure
mm/gup: document return value
arch/mips/mm/gup.c | 2 ++ arch/s390/mm/gup.c | 2 ++ arch/sh/mm/gup.c | 2 ++ arch/sparc/mm/gup.c | 4 ++++ mm/gup.c | 9 +++++++-- mm/gup_benchmark.c | 5 +++-- mm/util.c | 6 ++++-- 7 files changed, 24 insertions(+), 6 deletions(-)
Signature · 1 line
MST
PATCH v2 1/3 mm/gup_benchmark: handle gup failures
__gup_benchmark_ioctl does not handle the case where get_user_pages_fast fails:
- a negative return code will cause a buffer overrun
- returning with partial success will cause use of uninitialized memory.
--- mm/gup_benchmark.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
mm/gup_benchmark.c +3 -2
@@ -23,7 +23,7 @@ static int __gup_benchmark_ioctl(unsigned int cmd,struct page **pages; nr_pages = gup->size /PAGE_SIZE ; - pages =kvmalloc (sizeof (void *) * nr_pages,GFP_KERNEL ); + pages =kvzalloc (sizeof (void *) * nr_pages,GFP_KERNEL );if (!pages)return -ENOMEM ;
@@ -41,7 +41,8 @@ static int __gup_benchmark_ioctl(unsigned int cmd, } nr =get_user_pages_fast (addr, nr, gup->flags &1 , pages + i); - i += nr; +if (nr >0 ) + i += nr; } end_time =ktime_get ();
Signature · 1 line
MST
Re: [PATCH v2 1/3] mm/gup_benchmark: handle gup failures
mm/gup_benchmark.c static int __gup_benchmark_ioctl(unsigned int cmd, · line 45
Quoted 5 lines · Michael S. Tsirkin
nr = get_user_pages_fast(addr, nr, gup->flags & 1, pages + i);mm/gup_benchmark.c +2
-1- i += nr; +if (nr >0 ) + i += nr;
Can we just make this robust while at it, and just make it
if (nr <= 0)
break;instead? Then it doesn't care about zero vs negative error, and wouldn't get stuck in an endless loop if it got zero.
Linus
Re: [PATCH v2 1/3] mm/gup_benchmark: handle gup failures
Quoted 9 lines · Linus Torvalds
mm/gup_benchmark.c static int __gup_benchmark_ioctl(unsigned int cmd, · line 45
Quoted 5 lines · Michael S. Tsirkin
nr = get_user_pages_fast(addr, nr, gup->flags & 1, pages + i);mm/gup_benchmark.c +2
-1- i += nr; +if (nr >0 ) + i += nr;Can we just make this robust while at it, and just make it
Quoted 7 lines
if (nr <= 0) break;instead? Then it doesn't care about zero vs negative error, and wouldn't get stuck in an endless loop if it got zero.
Linus
I don't mind though it alredy breaks out on the next cycle:
if (nr != gup->nr_pages_per_call)
break;the only issue is i getting corrupted when nr < 0;
Andrew Morton
maintainerRe: [PATCH v2 1/3] mm/gup_benchmark: handle gup failures
Quoted 16 lines · On Sun, 8 Apr 2018 06:12:13 +0300 "Michael S. Tsirkin" <[email protected]> wrote:
Quoted 15 lines · Linus Torvalds
Review
Quoted 5 lines · Michael S. Tsirkin
nr = get_user_pages_fast(addr, nr, gup->flags & 1, pages + i);(quoted diff) +2
-1- i += nr; + if (nr > 0) + i += nr;Can we just make this robust while at it, and just make it
if (nr <= 0) break;instead? Then it doesn't care about zero vs negative error, and wouldn't get stuck in an endless loop if it got zero.
Quoted 9 lines
Quoted 1 line
Linus
I don't mind though it alredy breaks out on the next cycle:
if (nr != gup->nr_pages_per_call) break;the only issue is i getting corrupted when nr < 0;
It does help readability to have the thing bail out as soon as we see something go bad. This?
mm/gup_benchmark.c renamed from mm/gup_benchmark.c~mm-gup_benchmark-handle-gup-failures-fix +3 -2
@@ -41,8 +41,9 @@ static int __gup_benchmark_ioctl(unsigne } nr =get_user_pages_fast (addr, nr, gup->flags &1 , pages + i); -if (nr >0 ) - i += nr; +if (nr <=0 ) +break ; + i += nr; } end_time =ktime_get ();
_
PATCH v2 2/3 gup: return -EFAULT on access_ok failure
In reply to Michael S. Tsirkinget_user_pages_fast is supposed to be a faster drop-in equivalent of get_user_pages. As such, callers expect it to return a negative return code when passed an invalid address, and never expect it to return 0 when passed a positive number of pages, since
its documentation says:
* Returns number of pages pinned. This may be fewer than the number
* requested. If nr_pages is 0 or negative, returns 0. If no pages
* were pinned, returns -errno.
When get_user_pages_fast fall back on get_user_pages this is exactly what happens. Unfortunately the implementation is inconsistent: it returns 0 if passed a kernel address, confusing callers: for example, the following is pretty common but does not appear to do the right thing with a kernel address:
ret = get_user_pages_fast(addr, 1, writeable, &page);
if (ret < 0)
return ret;Change get_user_pages_fast to return -EFAULT when supplied a kernel address to make it match expectations.
All caller have been audited for consistency with the documented semantics.
Lightly tested.
--- mm/gup.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/gup.c +4 -1
@@ -1806,9 +1806,12 @@ int get_user_pages_fast(unsigned long start, int nr_pages, int write, len = (unsigned long ) nr_pages <<PAGE_SHIFT ; end = start + len; +if (nr_pages <=0 ) +return 0 ; +if (unlikely (!access_ok (write ?VERIFY_WRITE :VERIFY_READ , (void __user *)start, len))) -return 0 ; +return -EFAULT ;if (gup_fast_permitted (start, nr_pages, write)) {local_irq_disable ();
Signature · 1 line
MST
PATCH v2 3/3 mm/gup: document return value
In reply to Michael S. Tsirkin__get_user_pages_fast handles errors differently fromget_user_pages_fast: the former always returns the number of pages pinned, the later might return a negative error code.
--- arch/mips/mm/gup.c | 2 ++ arch/s390/mm/gup.c | 2 ++ arch/sh/mm/gup.c | 2 ++ arch/sparc/mm/gup.c | 4 ++++ mm/gup.c | 4 +++- mm/util.c | 6 ++++-- 6 files changed, 17 insertions(+), 3 deletions(-)
arch/mips/mm/gup.c +2 -0
@@ -178,6 +178,8 @@ static int gup_pud_range(pgd_t pgd, unsigned long addr, unsigned long end,/* * Like get_user_pages_fast() except its IRQ-safe in that it won't fall * back to the regular GUP. +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. */ int __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages)
arch/s390/mm/gup.c +2 -0
@@ -220,6 +220,8 @@ static inline int gup_p4d_range(pgd_t *pgdp, pgd_t pgd, unsigned long addr,/* * Like get_user_pages_fast() except its IRQ-safe in that it won't fall * back to the regular GUP. +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. */ int __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages)
arch/sh/mm/gup.c +2 -0
@@ -160,6 +160,8 @@ static int gup_pud_range(pgd_t pgd, unsigned long addr, unsigned long end,/* * Like get_user_pages_fast() except its IRQ-safe in that it won't fall * back to the regular GUP. +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. */ int __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages)
arch/sparc/mm/gup.c +4 -0
@@ -192,6 +192,10 @@ static int gup_pud_range(pgd_t pgd, unsigned long addr, unsigned long end,return 1 ; } +/* +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. +*/ int __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages) {
mm/gup.c +3 -1
@@ -1740,7 +1740,9 @@ bool gup_fast_permitted(unsigned long start, int nr_pages, int write)/* * Like get_user_pages_fast() except it's IRQ-safe in that it won't fall back to -* the regular GUP. It will only return non-negative values. +* the regular GUP. +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. */ int __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages)
mm/util.c +4 -2
@@ -297,8 +297,10 @@ void arch_pick_mmap_layout(struct mm_struct *mm)/* * Like get_user_pages_fast() except its IRQ-safe in that it won't fall * back to the regular GUP. -* If the architecture not support this function, simply return with no -* page pinned +* Note a difference with get_user_pages_fast: this always returns the +* number of pages pinned, 0 if no pages were pinned. +* If the architecture does not support this function, simply return with no +* pages pinned. */ int __weak __get_user_pages_fast (unsigned long start,int nr_pages,int write,struct page **pages)
Signature · 1 line
MST
Andrew Morton
maintainerQuoted 10 lines · On Fri, 6 Apr 2018 00:03:48 +0300 "Michael S. Tsirkin" <[email protected]> wrote:
Turns out
get_user_pages_fastand__get_user_pages_fastreturn different values on error when given a single page:__get_user_pages_fastreturns
0.get_user_pages_fastreturns either 0 or an error.Callers of
get_user_pages_fastexpect an error so fix it up to return an error consistently.Stress the difference between
get_user_pages_fastand__get_user_pages_fastto make sure callers aren't confused.
A term which is missing from all these changelogs is "vhost" :(
This patchset fixes a user-affecting bug, does it not? If so, please fully describe that bug so that we can decide which kernel version(s) need the patchset.
And yes, this return value asymmetry is sad. Did you scope out what would be needed to fix up the callers so we can avoid this?
Quoted 6 lines · Andrew Morton
Quoted 4 lines · On Fri, 6 Apr 2018 00:03:48 +0300 "Michael S. Tsirkin" <[email protected]> wrote:
Turns out
get_user_pages_fastand__get_user_pages_fastreturn different values on error when given a single page:__get_user_pages_fastreturns
0.get_user_pages_fastreturns either 0 or an error.
Quoted 8 lines
Quoted 6 lines
Callers of
get_user_pages_fastexpect an error so fix it up to return an error consistently.Stress the difference between
get_user_pages_fastand__get_user_pages_fastto make sure callers aren't confused.A term which is missing from all these changelogs is "vhost" :(
vhost has a BUG_ON for unexpected handling so it catches the bug, but it's not the only site affected.
Quoted 3 lines
This patchset fixes a user-affecting bug, does it not? If so, please fully describe that bug so that we can decide which kernel version(s) need the patchset.
OK, I'll try to write up something.
Quoted 2 lines
And yes, this return value asymmetry is sad. Did you scope out what would be needed to fix up the callers so we can avoid this?
Yes - there is a very small number of callers for __get_user_pages_fast so it's easy to teach them all to treat any value <=0 as 0. There seems to be some opposition to changing the API,
I'd like to look into this after the bugfix patchset is merged.
Signature · 1 line
MST