Patch "base/memory, hotplug: fix a kernel oops in show_valid_zones()" has been added to the 4.4-stable tree
Subsystems (4)
This is a note to let you know that I've just added the patch titled
base/memory, hotplug: fix a kernel oops in show_valid_zones()
to the 4.4-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
base-memory-hotplug-fix-a-kernel-oops-in-show_valid_zones.patch
and it can be found in the queue-4.4 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let <[email protected]> know about it.
Quoted 1 line
From
a96dfddbcc04336bbed50dc2b24823e45e09e80cMon Sep 17 00:00:00 2001
From: Toshi Kani <[email protected]>
Date: Fri, 3 Feb 2017 13:13:23 -0800
Subject: base/memory, hotplug: fix a kernel oops in show_valid_zones()
From: Toshi Kani <[email protected]>
commit a96dfddbcc04336bbed50dc2b24823e45e09e80c upstream.
Reading a sysfs "memoryN/valid_zones" file leads to the following oops when the first page of a range is not backed by struct page. show_valid_zones() assumes that 'start_pfn' is always valid for page_zone().
BUG: unable to handle kernel paging request at ffffea017a000000 IP: show_valid_zones+0x6f/0x160
This issue may happen on x86-64 systems with 64GiB or more memory since their memory block size is bumped up to 2GiB. [1] An example of such systems is desribed below. 0x3240000000 is only aligned by 1GiB and this memory block starts from 0x3200000000, which is not backed by struct page.
BIOS-e820: [mem 0x0000003240000000-0x000000603fffffff] usable
Since test_pages_in_a_zone() already checks holes, fix this issue by extending this function to return 'valid_start' and 'valid_end' for a given range. show_valid_zones() then proceeds with the valid range.
[1] 'Commit bdee237c0343 ("x86: mm: Use 2GB memory block size on
large-memory x86-64 systems")'
--- drivers/base/memory.c | 11 +++++------ include/linux/memory_hotplug.h | 3 ++- mm/memory_hotplug.c | 20 +++++++++++++++----- 3 files changed, 22 insertions(+), 12 deletions(-)
drivers/base/memory.c +5 -6
@@ -388,30 +388,29 @@ static ssize_t show_valid_zones(struct d {struct memory_block *mem =to_memory_block (dev);unsigned long start_pfn, end_pfn; +unsigned long valid_start, valid_end;unsigned long nr_pages =PAGES_PER_SECTION * sections_per_block; -struct page *first_page;struct zone *zone; start_pfn =section_nr_to_pfn (mem->start_section_nr); end_pfn = start_pfn + nr_pages; - first_page =pfn_to_page (start_pfn);/* The block contains more than one zone can not be offlined. */ -if (!test_pages_in_a_zone (start_pfn, end_pfn)) +if (!test_pages_in_a_zone (start_pfn, end_pfn, &valid_start, &valid_end))return sprintf (buf,"none\n" ); - zone =page_zone (first_page); + zone =page_zone (pfn_to_page (valid_start));if (zone_idx (zone) ==ZONE_MOVABLE -1 ) {/*The mem block is the last memoryblock of this zone.*/ -if (end_pfn ==zone_end_pfn (zone)) +if (valid_end ==zone_end_pfn (zone))return sprintf (buf,"%s %s\n" , zone->name, (zone +1 )->name); }if (zone_idx (zone) ==ZONE_MOVABLE ) {/*The mem block is the first memoryblock of ZONE_MOVABLE.*/ -if (start_pfn == zone->zone_start_pfn) +if (valid_start == zone->zone_start_pfn)return sprintf (buf,"%s %s\n" , zone->name, (zone -1 )->name); }
include/linux/memory_hotplug.h +2 -1
@@ -85,7 +85,8 @@ extern int zone_grow_waitqueues(struct zextern int add_one_highpage (struct page *page,int pfn,int bad_ppro);/* VM interface that may be used by firmware interface */ extern int online_pages (unsigned long ,unsigned long ,int ); -extern int test_pages_in_a_zone (unsigned long ,unsigned long ); +extern int test_pages_in_a_zone (unsigned long start_pfn,unsigned long end_pfn, +unsigned long *valid_start,unsigned long *valid_end);extern void __offline_isolated_pages (unsigned long ,unsigned long );typedef void (*online_page_callback_t )(struct page *page);
mm/memory_hotplug.c +15 -5
@@ -1372,10 +1372,13 @@ int is_mem_section_removable(unsigned lo/* * Confirm all pages in a range [start, end) belong to the same zone. +* When true, return its valid [start, end). */ -int test_pages_in_a_zone (unsigned long start_pfn,unsigned long end_pfn) +int test_pages_in_a_zone (unsigned long start_pfn,unsigned long end_pfn, +unsigned long *valid_start,unsigned long *valid_end) {unsigned long pfn, sec_end_pfn; +unsigned long start, end;struct zone *zone =NULL ;struct page *page;int i;
@@ -1397,14 +1400,20 @@ int test_pages_in_a_zone(unsigned long s page =pfn_to_page (pfn + i);if (zone &&page_zone (page) != zone)return 0 ; +if (!zone) + start = pfn + i; zone =page_zone (page); + end = pfn +MAX_ORDER_NR_PAGES ; } } -if (zone) +if (zone) { + *valid_start = start; + *valid_end = end;return 1 ; -else + }else {return 0 ; + } }/*
@@ -1722,6 +1731,7 @@ static int __ref __offline_pages(unsignelong offlined_pages;int ret, drain, retry_max, node;unsigned long flags; +unsigned long valid_start, valid_end;struct zone *zone;struct memory_notify arg;
@@ -1732,10 +1742,10 @@ static int __ref __offline_pages(unsignereturn -EINVAL ;/* This makes hotplug much easier...and readable. we assume this for now. .*/ -if (!test_pages_in_a_zone (start_pfn, end_pfn)) +if (!test_pages_in_a_zone (start_pfn, end_pfn, &valid_start, &valid_end))return -EINVAL ; - zone =page_zone (pfn_to_page (start_pfn)); + zone =page_zone (pfn_to_page (valid_start)); node =zone_to_nid (zone); nr_pages = end_pfn - start_pfn;
Patches currently in stable-queue which might be from [email protected] are
queue-4.4/mm-memory_hotplug.c-check-start_pfn-in-test_pages_in_a_zone.patch queue-4.4/base-memory-hotplug-fix-a-kernel-oops-in-show_valid_zones.patch