Received: by 2002:a25:ab43:0:0:0:0:0 with SMTP id u61csp2414340ybi; Sun, 9 Jun 2019 11:10:12 -0700 (PDT) X-Google-Smtp-Source: APXvYqyGbaSQha3yJTv2ktOpFF7gAcDYDhe01Gf8V5f1GCtGTyeDw7ckSi++f5zOQ/FghdNPBT90 X-Received: by 2002:a17:902:e282:: with SMTP id cf2mr43882617plb.301.1560103812357; Sun, 09 Jun 2019 11:10:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1560103812; cv=none; d=google.com; s=arc-20160816; b=o2iVxDIA/mtDr7/d1VvW+/WW4B63B1L3V6DmhWsoG9RbLdgrcHxFpNFVCcEMSMAM2j EZiEIzqGd4+1twyN6p9asx/EAaSCOkNntX+roWpudnxRVrYdNvNvTlPvKhPcyJy641n2 8YfTh0ELxNel+BtRusnm7GtVHOfa7k+4GulZ97sjGNQva9dtPwQfaTfKpQojgmSdfBRp Af+Wb1P6OJw3CI6AYz0qU6ma1Nh4U8pl8OOkfJuDuDVRXBszf7dcDmr4Cz3IyhyhTLDp 5FzVPT3ZOq46oCcMrMfOVs2zZlo8xANdCBkO97DHql2hRQlcm5HqIUNYIFU56Mp9QX3Y IQxQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :user-agent:references:in-reply-to:message-id:date:subject:cc:to :from:dkim-signature; bh=OjQ5n51iOS/liKTuGRzXnkl/qwmSGTS50idyk4kcz08=; b=qzE7ESKoPmeG36ZIGGj56CfPIJ8p/0avMmyBDAxVQNCW0oS/jskITk/XiIIhUes24n C9cZDjxTP3dP0gn3j4TWIDqHE2atcd4nuvN+yLHCQ50oFNmmAvmTXviEGaLn9QFX9Vsh tXy1E6eSMZUbCBjIM8pGvSswCig154ZWySFPUKutXk3cg+Pe/WqPhhtE0TnIerclshDl o7gyj77JRI6N81dWxu6Slx3lEKaAwcskF9kipQVTSr4EJmOPbodcc3x1rJcLNf723IHl 6Lf4p/5NaADfjHZCy1rXGh625wnNE/GvIIUt2SmzyE027UucxP7Pncq11jlYi0q3sl+E I68A== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=y2rQg2NU; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id a18si3574373plm.171.2019.06.09.11.09.56; Sun, 09 Jun 2019 11:10:12 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=y2rQg2NU; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732512AbfFIQx7 (ORCPT + 99 others); Sun, 9 Jun 2019 12:53:59 -0400 Received: from mail.kernel.org ([198.145.29.99]:55402 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1732505AbfFIQx5 (ORCPT ); Sun, 9 Jun 2019 12:53:57 -0400 Received: from localhost (83-86-89-107.cable.dynamic.v4.ziggo.nl [83.86.89.107]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id DACB62081C; Sun, 9 Jun 2019 16:53:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1560099236; bh=7Z2eSdjGURg30XUUYIo4u7JEH8kXWqo29Oy1NRh4YyU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=y2rQg2NUttk6u4rmIDBGCNtmYLaLUNT/RNcB4YzvGxvLuwT19HPmMYTAIZ9Akrp4H XKCQpp5+tKyvJP0PoTR4ZLac/ojElJL2D0UmhiUiD5RedQ2OrJrab7ulVNbNxOs05w SSU5FeAR3W4DP3xKnHbfEY8hYKInqPhnqO3BYPDg= From: Greg Kroah-Hartman To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , stable@vger.kernel.org, Jann Horn , Matthew Wilcox , Linus Torvalds , Ben Hutchings Subject: [PATCH 4.9 57/83] mm: prevent get_user_pages() from overflowing page refcount Date: Sun, 9 Jun 2019 18:42:27 +0200 Message-Id: <20190609164132.774263938@linuxfoundation.org> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190609164127.843327870@linuxfoundation.org> References: <20190609164127.843327870@linuxfoundation.org> User-Agent: quilt/0.66 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Linus Torvalds commit 8fde12ca79aff9b5ba951fce1a2641901b8d8e64 upstream. If the page refcount wraps around past zero, it will be freed while there are still four billion references to it. One of the possible avenues for an attacker to try to make this happen is by doing direct IO on a page multiple times. This patch makes get_user_pages() refuse to take a new page reference if there are already more than two billion references to the page. Reported-by: Jann Horn Acked-by: Matthew Wilcox Signed-off-by: Linus Torvalds [bwh: Backported to 4.9: - Add the "err" variable in follow_hugetlb_page() - Adjust context] Signed-off-by: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- mm/gup.c | 45 ++++++++++++++++++++++++++++++++++----------- mm/hugetlb.c | 16 +++++++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) --- a/mm/gup.c +++ b/mm/gup.c @@ -153,7 +153,10 @@ retry: } if (flags & FOLL_GET) { - get_page(page); + if (unlikely(!try_get_page(page))) { + page = ERR_PTR(-ENOMEM); + goto out; + } /* drop the pgmap reference now that we hold the page */ if (pgmap) { @@ -292,7 +295,10 @@ struct page *follow_page_mask(struct vm_ if (pmd_trans_unstable(pmd)) ret = -EBUSY; } else { - get_page(page); + if (unlikely(!try_get_page(page))) { + spin_unlock(ptl); + return ERR_PTR(-ENOMEM); + } spin_unlock(ptl); lock_page(page); ret = split_huge_page(page); @@ -348,7 +354,10 @@ static int get_gate_page(struct mm_struc goto unmap; *page = pte_page(*pte); } - get_page(*page); + if (unlikely(!try_get_page(*page))) { + ret = -ENOMEM; + goto unmap; + } out: ret = 0; unmap: @@ -1231,6 +1240,20 @@ struct page *get_dump_page(unsigned long */ #ifdef CONFIG_HAVE_GENERIC_RCU_GUP +/* + * Return the compund head page with ref appropriately incremented, + * or NULL if that failed. + */ +static inline struct page *try_get_compound_head(struct page *page, int refs) +{ + struct page *head = compound_head(page); + if (WARN_ON_ONCE(page_ref_count(head) < 0)) + return NULL; + if (unlikely(!page_cache_add_speculative(head, refs))) + return NULL; + return head; +} + #ifdef __HAVE_ARCH_PTE_SPECIAL static int gup_pte_range(pmd_t pmd, unsigned long addr, unsigned long end, int write, struct page **pages, int *nr) @@ -1263,9 +1286,9 @@ static int gup_pte_range(pmd_t pmd, unsi VM_BUG_ON(!pfn_valid(pte_pfn(pte))); page = pte_page(pte); - head = compound_head(page); - if (!page_cache_get_speculative(head)) + head = try_get_compound_head(page, 1); + if (!head) goto pte_unmap; if (unlikely(pte_val(pte) != pte_val(*ptep))) { @@ -1321,8 +1344,8 @@ static int gup_huge_pmd(pmd_t orig, pmd_ refs++; } while (addr += PAGE_SIZE, addr != end); - head = compound_head(pmd_page(orig)); - if (!page_cache_add_speculative(head, refs)) { + head = try_get_compound_head(pmd_page(orig), refs); + if (!head) { *nr -= refs; return 0; } @@ -1355,8 +1378,8 @@ static int gup_huge_pud(pud_t orig, pud_ refs++; } while (addr += PAGE_SIZE, addr != end); - head = compound_head(pud_page(orig)); - if (!page_cache_add_speculative(head, refs)) { + head = try_get_compound_head(pud_page(orig), refs); + if (!head) { *nr -= refs; return 0; } @@ -1390,8 +1413,8 @@ static int gup_huge_pgd(pgd_t orig, pgd_ refs++; } while (addr += PAGE_SIZE, addr != end); - head = compound_head(pgd_page(orig)); - if (!page_cache_add_speculative(head, refs)) { + head = try_get_compound_head(pgd_page(orig), refs); + if (!head) { *nr -= refs; return 0; } --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -3984,6 +3984,7 @@ long follow_hugetlb_page(struct mm_struc unsigned long vaddr = *position; unsigned long remainder = *nr_pages; struct hstate *h = hstate_vma(vma); + int err = -EFAULT; while (vaddr < vma->vm_end && remainder) { pte_t *pte; @@ -4055,6 +4056,19 @@ long follow_hugetlb_page(struct mm_struc pfn_offset = (vaddr & ~huge_page_mask(h)) >> PAGE_SHIFT; page = pte_page(huge_ptep_get(pte)); + + /* + * Instead of doing 'try_get_page()' below in the same_page + * loop, just check the count once here. + */ + if (unlikely(page_count(page) <= 0)) { + if (pages) { + spin_unlock(ptl); + remainder = 0; + err = -ENOMEM; + break; + } + } same_page: if (pages) { pages[i] = mem_map_offset(page, pfn_offset); @@ -4081,7 +4095,7 @@ same_page: *nr_pages = remainder; *position = vaddr; - return i ? i : -EFAULT; + return i ? i : err; } #ifndef __HAVE_ARCH_FLUSH_HUGETLB_TLB_RANGE