| From 1c8f4ab999574b48ee3d48d532872048c39acd93 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 29 Nov 2019 10:03:49 +0100 |
| Subject: mm, gup: add missing refcount overflow checks on x86 and s390 |
| |
| From: Vlastimil Babka <vbabka@suse.cz> |
| |
| The mainline commit 8fde12ca79af ("mm: prevent get_user_pages() from |
| overflowing page refcount") was backported to 4.9.y stable as commit |
| 2ed768cfd895. The backport however missed that in 4.9, there are several |
| arch-specific gup.c versions with fast gup implementations, so these do not |
| prevent refcount overflow. |
| |
| This is partially fixed for x86 in stable-only commit d73af79742e7 ("x86, mm, |
| gup: prevent get_page() race with munmap in paravirt guest"). This stable-only |
| commit adds missing parts to x86 version, as well as s390 version, both taken |
| from the SUSE SLES/openSUSE 4.12-based kernels. |
| |
| The remaining architectures with own gup.c are sparc, mips, sh. It's unlikely |
| the known overflow scenario based on FUSE, which needs 140GB of RAM, is a |
| problem for those architectures, and I don't feel confident enough to patch |
| them. |
| |
| Signed-off-by: Vlastimil Babka <vbabka@suse.cz> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/s390/mm/gup.c | 9 ++++++--- |
| arch/x86/mm/gup.c | 9 ++++++++- |
| 2 files changed, 14 insertions(+), 4 deletions(-) |
| |
| --- a/arch/s390/mm/gup.c |
| +++ b/arch/s390/mm/gup.c |
| @@ -38,7 +38,8 @@ static inline int gup_pte_range(pmd_t *p |
| VM_BUG_ON(!pfn_valid(pte_pfn(pte))); |
| page = pte_page(pte); |
| head = compound_head(page); |
| - if (!page_cache_get_speculative(head)) |
| + if (WARN_ON_ONCE(page_ref_count(head) < 0) |
| + || !page_cache_get_speculative(head)) |
| return 0; |
| if (unlikely(pte_val(pte) != pte_val(*ptep))) { |
| put_page(head); |
| @@ -76,7 +77,8 @@ static inline int gup_huge_pmd(pmd_t *pm |
| refs++; |
| } while (addr += PAGE_SIZE, addr != end); |
| |
| - if (!page_cache_add_speculative(head, refs)) { |
| + if (WARN_ON_ONCE(page_ref_count(head) < 0) |
| + || !page_cache_add_speculative(head, refs)) { |
| *nr -= refs; |
| return 0; |
| } |
| @@ -150,7 +152,8 @@ static int gup_huge_pud(pud_t *pudp, pud |
| refs++; |
| } while (addr += PAGE_SIZE, addr != end); |
| |
| - if (!page_cache_add_speculative(head, refs)) { |
| + if (WARN_ON_ONCE(page_ref_count(head) < 0) |
| + || !page_cache_add_speculative(head, refs)) { |
| *nr -= refs; |
| return 0; |
| } |
| --- a/arch/x86/mm/gup.c |
| +++ b/arch/x86/mm/gup.c |
| @@ -202,9 +202,12 @@ static int __gup_device_huge_pmd(pmd_t p |
| undo_dev_pagemap(nr, nr_start, pages); |
| return 0; |
| } |
| + if (unlikely(!try_get_page(page))) { |
| + put_dev_pagemap(pgmap); |
| + return 0; |
| + } |
| SetPageReferenced(page); |
| pages[*nr] = page; |
| - get_page(page); |
| put_dev_pagemap(pgmap); |
| (*nr)++; |
| pfn++; |
| @@ -230,6 +233,8 @@ static noinline int gup_huge_pmd(pmd_t p |
| |
| refs = 0; |
| head = pmd_page(pmd); |
| + if (WARN_ON_ONCE(page_ref_count(head) <= 0)) |
| + return 0; |
| page = head + ((addr & ~PMD_MASK) >> PAGE_SHIFT); |
| do { |
| VM_BUG_ON_PAGE(compound_head(page) != head, page); |
| @@ -289,6 +294,8 @@ static noinline int gup_huge_pud(pud_t p |
| |
| refs = 0; |
| head = pud_page(pud); |
| + if (WARN_ON_ONCE(page_ref_count(head) <= 0)) |
| + return 0; |
| page = head + ((addr & ~PUD_MASK) >> PAGE_SHIFT); |
| do { |
| VM_BUG_ON_PAGE(compound_head(page) != head, page); |