Skip to content

Commit 87ebcc5

Browse files
toshikanigregkh
authored andcommitted
base/memory, hotplug: fix a kernel oops in show_valid_zones()
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 bdee237 ("x86: mm: Use 2GB memory block size on large-memory x86-64 systems")' Link: http://lkml.kernel.org/r/20170127222149.30893-3-toshi.kani@hpe.com Signed-off-by: Toshi Kani <toshi.kani@hpe.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Zhang Zhen <zhenzhang.zhang@huawei.com> Cc: Reza Arbab <arbab@linux.vnet.ibm.com> Cc: David Rientjes <rientjes@google.com> Cc: Dan Williams <dan.j.williams@intel.com> Cc: <stable@vger.kernel.org> [4.4+] Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 374d066 commit 87ebcc5

3 files changed

Lines changed: 22 additions & 12 deletions

File tree

drivers/base/memory.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,30 +388,29 @@ static ssize_t show_valid_zones(struct device *dev,
388388
{
389389
struct memory_block *mem = to_memory_block(dev);
390390
unsigned long start_pfn, end_pfn;
391+
unsigned long valid_start, valid_end;
391392
unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block;
392-
struct page *first_page;
393393
struct zone *zone;
394394

395395
start_pfn = section_nr_to_pfn(mem->start_section_nr);
396396
end_pfn = start_pfn + nr_pages;
397-
first_page = pfn_to_page(start_pfn);
398397

399398
/* The block contains more than one zone can not be offlined. */
400-
if (!test_pages_in_a_zone(start_pfn, end_pfn))
399+
if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end))
401400
return sprintf(buf, "none\n");
402401

403-
zone = page_zone(first_page);
402+
zone = page_zone(pfn_to_page(valid_start));
404403

405404
if (zone_idx(zone) == ZONE_MOVABLE - 1) {
406405
/*The mem block is the last memoryblock of this zone.*/
407-
if (end_pfn == zone_end_pfn(zone))
406+
if (valid_end == zone_end_pfn(zone))
408407
return sprintf(buf, "%s %s\n",
409408
zone->name, (zone + 1)->name);
410409
}
411410

412411
if (zone_idx(zone) == ZONE_MOVABLE) {
413412
/*The mem block is the first memoryblock of ZONE_MOVABLE.*/
414-
if (start_pfn == zone->zone_start_pfn)
413+
if (valid_start == zone->zone_start_pfn)
415414
return sprintf(buf, "%s %s\n",
416415
zone->name, (zone - 1)->name);
417416
}

include/linux/memory_hotplug.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ extern int zone_grow_waitqueues(struct zone *zone, unsigned long nr_pages);
8585
extern int add_one_highpage(struct page *page, int pfn, int bad_ppro);
8686
/* VM interface that may be used by firmware interface */
8787
extern int online_pages(unsigned long, unsigned long, int);
88-
extern int test_pages_in_a_zone(unsigned long, unsigned long);
88+
extern int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn,
89+
unsigned long *valid_start, unsigned long *valid_end);
8990
extern void __offline_isolated_pages(unsigned long, unsigned long);
9091

9192
typedef void (*online_page_callback_t)(struct page *page);

mm/memory_hotplug.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,10 +1372,13 @@ int is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages)
13721372

13731373
/*
13741374
* Confirm all pages in a range [start, end) belong to the same zone.
1375+
* When true, return its valid [start, end).
13751376
*/
1376-
int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn)
1377+
int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn,
1378+
unsigned long *valid_start, unsigned long *valid_end)
13771379
{
13781380
unsigned long pfn, sec_end_pfn;
1381+
unsigned long start, end;
13791382
struct zone *zone = NULL;
13801383
struct page *page;
13811384
int i;
@@ -1397,14 +1400,20 @@ int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn)
13971400
page = pfn_to_page(pfn + i);
13981401
if (zone && page_zone(page) != zone)
13991402
return 0;
1403+
if (!zone)
1404+
start = pfn + i;
14001405
zone = page_zone(page);
1406+
end = pfn + MAX_ORDER_NR_PAGES;
14011407
}
14021408
}
14031409

1404-
if (zone)
1410+
if (zone) {
1411+
*valid_start = start;
1412+
*valid_end = end;
14051413
return 1;
1406-
else
1414+
} else {
14071415
return 0;
1416+
}
14081417
}
14091418

14101419
/*
@@ -1722,6 +1731,7 @@ static int __ref __offline_pages(unsigned long start_pfn,
17221731
long offlined_pages;
17231732
int ret, drain, retry_max, node;
17241733
unsigned long flags;
1734+
unsigned long valid_start, valid_end;
17251735
struct zone *zone;
17261736
struct memory_notify arg;
17271737

@@ -1732,10 +1742,10 @@ static int __ref __offline_pages(unsigned long start_pfn,
17321742
return -EINVAL;
17331743
/* This makes hotplug much easier...and readable.
17341744
we assume this for now. .*/
1735-
if (!test_pages_in_a_zone(start_pfn, end_pfn))
1745+
if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end))
17361746
return -EINVAL;
17371747

1738-
zone = page_zone(pfn_to_page(start_pfn));
1748+
zone = page_zone(pfn_to_page(valid_start));
17391749
node = zone_to_nid(zone);
17401750
nr_pages = end_pfn - start_pfn;
17411751

0 commit comments

Comments
 (0)