test_pages_in_a_zone() does not account for the possibility of missing sections in the given pfn range. pfn_valid_within always returns 1 when CONFIG_HOLES_IN_ZONE is not set, allowing invalid pfns from missing sections to pass the test, leading to a kernel oops. Wrap an additional pfn loop with PAGES_PER_SECTION granularity to check for missing sections before proceeding into the zone-check code. This also prevents a crash from offlining memory devices with missing sections. Despite this, it may be a good idea to keep the related patch '[PATCH 3/3] drivers: memory: prohibit offlining of memory blocks with missing sections' because missing sections in a memory block may lead to other problems not covered by the scope of this fix.
Unified · Split
| @@ -1375,23 +1375,30 @@ int is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages) · 1374 unmodified lines | |||
| 1375 | */ | 1375 | */ |
| 1376 | int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) | 1376 | int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) |
| 1377 | { | 1377 | { |
| 1378 | unsigned long pfn; | 1378 | unsigned long pfn, sec_end_pfn; |
| 1379 | struct zone *zone = NULL; | 1379 | struct zone *zone = NULL; |
| 1380 | struct page *page; | 1380 | struct page *page; |
| 1381 | int i; | 1381 | int i; |
| 1382 | for (pfn = start_pfn; | 1382 | for (pfn = start_pfn, sec_end_pfn = SECTION_ALIGN_UP(start_pfn); |
| 1383 | pfn < end_pfn; | 1383 | pfn < end_pfn; |
| 1384 | pfn += MAX_ORDER_NR_PAGES) { | 1384 | pfn = sec_end_pfn + 1, sec_end_pfn += PAGES_PER_SECTION) { |
| 1385 | i = 0; | 1385 | /* Make sure the memory section is present first */ |
| 1386 | /* This is just a CONFIG_HOLES_IN_ZONE check.*/ | 1386 | if (!present_section_nr(pfn_to_section_nr(pfn))) |
| 1387 | while ((i < MAX_ORDER_NR_PAGES) && !pfn_valid_within(pfn + i)) | ||
| 1388 | i++; | ||
| 1389 | if (i == MAX_ORDER_NR_PAGES) | ||
| 1390 | continue; | 1387 | continue; |
| 1391 | page = pfn_to_page(pfn + i); | 1388 | for (; pfn < sec_end_pfn && pfn < end_pfn; |
| 1392 | if (zone && page_zone(page) != zone) | 1389 | pfn += MAX_ORDER_NR_PAGES) { |
| 1393 | return 0; | 1390 | i = 0; |
| 1394 | zone = page_zone(page); | 1391 | /* This is just a CONFIG_HOLES_IN_ZONE check.*/ |
| 1392 | while ((i < MAX_ORDER_NR_PAGES) && | ||
| 1393 | !pfn_valid_within(pfn + i)) | ||
| 1394 | i++; | ||
| 1395 | if (i == MAX_ORDER_NR_PAGES) | ||
| 1396 | continue; | ||
| 1397 | page = pfn_to_page(pfn + i); | ||
| 1398 | if (zone && page_zone(page) != zone) | ||
| 1399 | return 0; | ||
| 1400 | zone = page_zone(page); | ||
| 1401 | } | ||
| 1395 | } | 1402 | } |
| 1396 | return 1; | 1403 | return 1; |
| 1397 | } | 1404 | } |