Skip to content

Commit 891cb2a

Browse files
Michal Hockotorvalds
authored andcommitted
mm, memory_hotplug: fix off-by-one in is_pageblock_removable
Rong Chen has reported the following boot crash: PGD 0 P4D 0 Oops: 0000 [#1] PREEMPT SMP PTI CPU: 1 PID: 239 Comm: udevd Not tainted 5.0.0-rc4-00149-gefad4e4 #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014 RIP: 0010:page_mapping+0x12/0x80 Code: 5d c3 48 89 df e8 0e ad 02 00 85 c0 75 da 89 e8 5b 5d c3 0f 1f 44 00 00 53 48 89 fb 48 8b 43 08 48 8d 50 ff a8 01 48 0f 45 da <48> 8b 53 08 48 8d 42 ff 83 e2 01 48 0f 44 c3 48 83 38 ff 74 2f 48 RSP: 0018:ffff88801fa87cd8 EFLAGS: 00010202 RAX: ffffffffffffffff RBX: fffffffffffffffe RCX: 000000000000000a RDX: fffffffffffffffe RSI: ffffffff820b9a20 RDI: ffff88801e5c0000 RBP: 6db6db6db6db6db7 R08: ffff88801e8bb000 R09: 0000000001b64d13 R10: ffff88801fa87cf8 R11: 0000000000000001 R12: ffff88801e640000 R13: ffffffff820b9a20 R14: ffff88801f145258 R15: 0000000000000001 FS: 00007fb2079817c0(0000) GS:ffff88801dd00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000006 CR3: 000000001fa82000 CR4: 00000000000006a0 Call Trace: __dump_page+0x14/0x2c0 is_mem_section_removable+0x24c/0x2c0 removable_show+0x87/0xa0 dev_attr_show+0x25/0x60 sysfs_kf_seq_show+0xba/0x110 seq_read+0x196/0x3f0 __vfs_read+0x34/0x180 vfs_read+0xa0/0x150 ksys_read+0x44/0xb0 do_syscall_64+0x5e/0x4a0 entry_SYSCALL_64_after_hwframe+0x49/0xbe and bisected it down to commit efad4e4 ("mm, memory_hotplug: is_mem_section_removable do not pass the end of a zone"). The reason for the crash is that the mapping is garbage for poisoned (uninitialized) page. This shouldn't happen as all pages in the zone's boundary should be initialized. Later debugging revealed that the actual problem is an off-by-one when evaluating the end_page. 'start_pfn + nr_pages' resp 'zone_end_pfn' refers to a pfn after the range and as such it might belong to a differen memory section. This along with CONFIG_SPARSEMEM then makes the loop condition completely bogus because a pointer arithmetic doesn't work for pages from two different sections in that memory model. Fix the issue by reworking is_pageblock_removable to be pfn based and only use struct page where necessary. This makes the code slightly easier to follow and we will remove the problematic pointer arithmetic completely. Link: http://lkml.kernel.org/r/[email protected] Fixes: efad4e4 ("mm, memory_hotplug: is_mem_section_removable do not pass the end of a zone") Signed-off-by: Michal Hocko <[email protected]> Reported-by: <[email protected]> Tested-by: <[email protected]> Acked-by: Mike Rapoport <[email protected]> Reviewed-by: Oscar Salvador <[email protected]> Cc: Matthew Wilcox <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 6c8fcc0 commit 891cb2a

File tree

1 file changed

+15
-12
lines changed

1 file changed

+15
-12
lines changed

mm/memory_hotplug.c

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,28 +1188,30 @@ static inline int pageblock_free(struct page *page)
11881188
return PageBuddy(page) && page_order(page) >= pageblock_order;
11891189
}
11901190

1191-
/* Return the start of the next active pageblock after a given page */
1192-
static struct page *next_active_pageblock(struct page *page)
1191+
/* Return the pfn of the start of the next active pageblock after a given pfn */
1192+
static unsigned long next_active_pageblock(unsigned long pfn)
11931193
{
1194+
struct page *page = pfn_to_page(pfn);
1195+
11941196
/* Ensure the starting page is pageblock-aligned */
1195-
BUG_ON(page_to_pfn(page) & (pageblock_nr_pages - 1));
1197+
BUG_ON(pfn & (pageblock_nr_pages - 1));
11961198

11971199
/* If the entire pageblock is free, move to the end of free page */
11981200
if (pageblock_free(page)) {
11991201
int order;
12001202
/* be careful. we don't have locks, page_order can be changed.*/
12011203
order = page_order(page);
12021204
if ((order < MAX_ORDER) && (order >= pageblock_order))
1203-
return page + (1 << order);
1205+
return pfn + (1 << order);
12041206
}
12051207

1206-
return page + pageblock_nr_pages;
1208+
return pfn + pageblock_nr_pages;
12071209
}
12081210

1209-
static bool is_pageblock_removable_nolock(struct page *page)
1211+
static bool is_pageblock_removable_nolock(unsigned long pfn)
12101212
{
1213+
struct page *page = pfn_to_page(pfn);
12111214
struct zone *zone;
1212-
unsigned long pfn;
12131215

12141216
/*
12151217
* We have to be careful here because we are iterating over memory
@@ -1232,13 +1234,14 @@ static bool is_pageblock_removable_nolock(struct page *page)
12321234
/* Checks if this range of memory is likely to be hot-removable. */
12331235
bool is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages)
12341236
{
1235-
struct page *page = pfn_to_page(start_pfn);
1236-
unsigned long end_pfn = min(start_pfn + nr_pages, zone_end_pfn(page_zone(page)));
1237-
struct page *end_page = pfn_to_page(end_pfn);
1237+
unsigned long end_pfn, pfn;
1238+
1239+
end_pfn = min(start_pfn + nr_pages,
1240+
zone_end_pfn(page_zone(pfn_to_page(start_pfn))));
12381241

12391242
/* Check the starting page of each pageblock within the range */
1240-
for (; page < end_page; page = next_active_pageblock(page)) {
1241-
if (!is_pageblock_removable_nolock(page))
1243+
for (pfn = start_pfn; pfn < end_pfn; pfn = next_active_pageblock(pfn)) {
1244+
if (!is_pageblock_removable_nolock(pfn))
12421245
return false;
12431246
cond_resched();
12441247
}

0 commit comments

Comments
 (0)