[STABLE,BACKPORT] mm/vmalloc.c: huge-vmap: fail gracefully on unexpected huge vmap mappings

Message ID 20170703111351.26561-1-ard.biesheuvel@linaro.org
State New
Headers show

Commit Message

Ard Biesheuvel July 3, 2017, 11:13 a.m.
Existing code that uses vmalloc_to_page() may assume that any address
for which is_vmalloc_addr() returns true may be passed into
vmalloc_to_page() to retrieve the associated struct page.

This is not un unreasonable assumption to make, but on architectures
that have CONFIG_HAVE_ARCH_HUGE_VMAP=y, it no longer holds, and we need
to ensure that vmalloc_to_page() does not go off into the weeds trying
to dereference huge PUDs or PMDs as table entries.

Given that vmalloc() and vmap() themselves never create huge mappings or
deal with compound pages at all, there is no correct answer in this
case, so return NULL instead, and issue a warning.

When reading /proc/kcore on arm64, you will hit an oops as soon as you
hit the huge mappings used for the various segments that make up the
mapping of vmlinux.  With this patch applied, you will no longer hit the
oops, but the kcore contents willl be incorrect (these regions will be
zeroed out)

We are fixing this for kcore specifically, so it avoids vread() for
those regions.  At least one other problematic user exists, i.e.,
/dev/kmem, but that is currently broken on arm64 for other reasons.

Link: http://lkml.kernel.org/r/20170609082226.26152-1-ard.biesheuvel@linaro.org
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

Acked-by: Mark Rutland <mark.rutland@arm.com>

Reviewed-by: Laura Abbott <labbott@redhat.com>

Cc: Michal Hocko <mhocko@suse.com>
Cc: zhong jiang <zhongjiang@huawei.com>
Cc: Dave Hansen <dave.hansen@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

(cherry picked from commit 029c54b09599573015a5c18dbe59cbdf42742237)
[ardb: non-trivial backport to v4.9]
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

---
 mm/vmalloc.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

-- 
2.9.3

Comments

Greg KH July 3, 2017, 11:46 a.m. | #1
On Mon, Jul 03, 2017 at 12:13:51PM +0100, Ard Biesheuvel wrote:
> Existing code that uses vmalloc_to_page() may assume that any address

> for which is_vmalloc_addr() returns true may be passed into

> vmalloc_to_page() to retrieve the associated struct page.

> 

> This is not un unreasonable assumption to make, but on architectures

> that have CONFIG_HAVE_ARCH_HUGE_VMAP=y, it no longer holds, and we need

> to ensure that vmalloc_to_page() does not go off into the weeds trying

> to dereference huge PUDs or PMDs as table entries.

> 

> Given that vmalloc() and vmap() themselves never create huge mappings or

> deal with compound pages at all, there is no correct answer in this

> case, so return NULL instead, and issue a warning.

> 

> When reading /proc/kcore on arm64, you will hit an oops as soon as you

> hit the huge mappings used for the various segments that make up the

> mapping of vmlinux.  With this patch applied, you will no longer hit the

> oops, but the kcore contents willl be incorrect (these regions will be

> zeroed out)

> 

> We are fixing this for kcore specifically, so it avoids vread() for

> those regions.  At least one other problematic user exists, i.e.,

> /dev/kmem, but that is currently broken on arm64 for other reasons.

> 

> Link: http://lkml.kernel.org/r/20170609082226.26152-1-ard.biesheuvel@linaro.org

> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

> Acked-by: Mark Rutland <mark.rutland@arm.com>

> Reviewed-by: Laura Abbott <labbott@redhat.com>

> Cc: Michal Hocko <mhocko@suse.com>

> Cc: zhong jiang <zhongjiang@huawei.com>

> Cc: Dave Hansen <dave.hansen@intel.com>

> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

> (cherry picked from commit 029c54b09599573015a5c18dbe59cbdf42742237)

> [ardb: non-trivial backport to v4.9]


Thanks for this, now queued up.

greg k-h

Patch

diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index f2481cb4e6b2..195de42bea1f 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -244,11 +244,21 @@  struct page *vmalloc_to_page(const void *vmalloc_addr)
 	 */
 	VIRTUAL_BUG_ON(!is_vmalloc_or_module_addr(vmalloc_addr));
 
+	/*
+	 * Don't dereference bad PUD or PMD (below) entries. This will also
+	 * identify huge mappings, which we may encounter on architectures
+	 * that define CONFIG_HAVE_ARCH_HUGE_VMAP=y. Such regions will be
+	 * identified as vmalloc addresses by is_vmalloc_addr(), but are
+	 * not [unambiguously] associated with a struct page, so there is
+	 * no correct value to return for them.
+	 */
 	if (!pgd_none(*pgd)) {
 		pud_t *pud = pud_offset(pgd, addr);
-		if (!pud_none(*pud)) {
+		WARN_ON_ONCE(pud_bad(*pud));
+		if (!pud_none(*pud) && !pud_bad(*pud)) {
 			pmd_t *pmd = pmd_offset(pud, addr);
-			if (!pmd_none(*pmd)) {
+			WARN_ON_ONCE(pmd_bad(*pmd));
+			if (!pmd_none(*pmd) && !pmd_bad(*pmd)) {
 				pte_t *ptep, pte;
 
 				ptep = pte_offset_map(pmd, addr);