History

mm/memory_hotplug: don't access uninitialized memmaps in shrink_zone_span()

Let's limit shrinking to !ZONE_DEVICE so we can fix the current code. We should never try to touch the memmap of offline sections where we could have uninitialized memmaps and could trigger BUGs when calling page_to_nid() on poisoned pages. There is no reliable way to distinguish an uninitialized memmap from an initialized memmap that belongs to ZONE_DEVICE, as we don't have anything like SECTION_IS_ONLINE we can use similar to pfn_to_online_section() for !ZONE_DEVICE memory. E.g., set_zone_contiguous() similarly relies on pfn_to_online_section() and will therefore never set a ZONE_DEVICE zone consecutive. Stopping to shrink the ZONE_DEVICE therefore results in no observable changes, besides /proc/zoneinfo indicating different boundaries - something we can totally live with. Before commit d0dc12e86b31 ("mm/memory_hotplug: optimize memory hotplug"), the memmap was initialized with 0 and the node with the right value. So the zone might be wrong but not garbage. After that commit, both the zone and the node will be garbage when touching uninitialized memmaps. Toshiki reported a BUG (race between delayed initialization of ZONE_DEVICE memmaps without holding the memory hotplug lock and concurrent zone shrinking). https://lkml.org/lkml/2019/11/14/1040 "Iteration of create and destroy namespace causes the panic as below: kernel BUG at mm/page_alloc.c:535! CPU: 7 PID: 2766 Comm: ndctl Not tainted 5.4.0-rc4 #6 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.11.0-0-g63451fca13-prebuilt.qemu-project.org 04/01/2014 RIP: 0010:set_pfnblock_flags_mask+0x95/0xf0 Call Trace: memmap_init_zone_device+0x165/0x17c memremap_pages+0x4c1/0x540 devm_memremap_pages+0x1d/0x60 pmem_attach_disk+0x16b/0x600 [nd_pmem] nvdimm_bus_probe+0x69/0x1c0 really_probe+0x1c2/0x3e0 driver_probe_device+0xb4/0x100 device_driver_attach+0x4f/0x60 bind_store+0xc9/0x110 kernfs_fop_write+0x116/0x190 vfs_write+0xa5/0x1a0 ksys_write+0x59/0xd0 do_syscall_64+0x5b/0x180 entry_SYSCALL_64_after_hwframe+0x44/0xa9 While creating a namespace and initializing memmap, if you destroy the namespace and shrink the zone, it will initialize the memmap outside the zone and trigger VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page) in set_pfnblock_flags_mask()." This BUG is also mitigated by this commit, where we for now stop to shrink the ZONE_DEVICE zone until we can do it in a safe and clean way.

David Hildenbrand committed 6 years ago

Commit 7ce700b1 parent 94b07b61 file · +13 −3Browse files

Unified · Split

mm/memory_hotplug.c+13−3
@@ -331,7 +331,7 @@ static unsigned long find_smallest_section_pfn(int nid, struct zone *zone, · 330 unmodified lines
331 unsigned long end_pfn)331 unsigned long end_pfn)
332{332{
333 for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {333 for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {
334 if (unlikely(!pfn_valid(start_pfn)))334 if (unlikely(!pfn_to_online_page(start_pfn)))
335 continue;335 continue;
336 336
337 if (unlikely(pfn_to_nid(start_pfn) != nid))337 if (unlikely(pfn_to_nid(start_pfn) != nid))
@@ -356,7 +356,7 @@ static unsigned long find_biggest_section_pfn(int nid, struct zone *zone, · 18 unmodified lines
356 /* pfn is the end pfn of a memory section. */356 /* pfn is the end pfn of a memory section. */
357 pfn = end_pfn - 1;357 pfn = end_pfn - 1;
358 for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {358 for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {
359 if (unlikely(!pfn_valid(pfn)))359 if (unlikely(!pfn_to_online_page(pfn)))
360 continue;360 continue;
361 361
362 if (unlikely(pfn_to_nid(pfn) != nid))362 if (unlikely(pfn_to_nid(pfn) != nid))
@@ -415,7 +415,7 @@ static void shrink_zone_span(struct zone *zone, unsigned long start_pfn, · 52 unmodified lines
415 */415 */
416 pfn = zone_start_pfn;416 pfn = zone_start_pfn;
417 for (; pfn < zone_end_pfn; pfn += PAGES_PER_SUBSECTION) {417 for (; pfn < zone_end_pfn; pfn += PAGES_PER_SUBSECTION) {
418 if (unlikely(!pfn_valid(pfn)))418 if (unlikely(!pfn_to_online_page(pfn)))
419 continue;419 continue;
420 420
421 if (page_zone(pfn_to_page(pfn)) != zone)421 if (page_zone(pfn_to_page(pfn)) != zone)
@@ -471,6 +471,16 @@ static void __remove_zone(struct zone *zone, unsigned long start_pfn, · 49 unmodified lines
471 struct pglist_data *pgdat = zone->zone_pgdat;471 struct pglist_data *pgdat = zone->zone_pgdat;
472 unsigned long flags;472 unsigned long flags;
473 473
474#ifdef CONFIG_ZONE_DEVICE
475 /*
476 * Zone shrinking code cannot properly deal with ZONE_DEVICE. So
477 * we will not try to shrink the zones - which is okay as
478 * set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
479 */
480 if (zone_idx(zone) == ZONE_DEVICE)
481 return;
482#endif
483
474 pgdat_resize_lock(zone->zone_pgdat, &flags);484 pgdat_resize_lock(zone->zone_pgdat, &flags);
475 shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);485 shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
476 update_pgdat_span(pgdat);486 update_pgdat_span(pgdat);