Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86: Make sure free_init_pages() frees pages on page boundary

When CONFIG_NO_BOOTMEM=y, it could use memory more effiently, or
in a more compact fashion.

Example:

Allocated new RAMDISK: 00ec2000 - 0248ce57
Move RAMDISK from 000000002ea04000 - 000000002ffcee56 to 00ec2000 - 0248ce56

The new RAMDISK's end is not page aligned.
Last page could be shared with other users.

When free_init_pages are called for initrd or .init, the page
could be freed and we could corrupt other data.

code segment in free_init_pages():

| for (; addr < end; addr += PAGE_SIZE) {
| ClearPageReserved(virt_to_page(addr));
| init_page_count(virt_to_page(addr));
| memset((void *)(addr & ~(PAGE_SIZE-1)),
| POISON_FREE_INITMEM, PAGE_SIZE);
| free_page(addr);
| totalram_pages++;
| }

last half page could be used as one whole free page.

So page align the boundaries.

-v2: make the original initramdisk to be aligned, according to
Johannes, otherwise we have the chance to lose one page.
we still need to keep initrd_end not aligned, otherwise it could
confuse decompressor.
-v3: change to WARN_ON instead, suggested by Johannes.
-v4: use PAGE_ALIGN, suggested by Johannes.
We may fix that macro name later to PAGE_ALIGN_UP, and PAGE_ALIGN_DOWN
Add comments about assuming ramdisk start is aligned
in relocate_initrd(), change to re get ramdisk_image instead of save it
to make diff smaller. Add warning for wrong range, suggested by Johannes.
-v6: remove one WARN()
We need to align beginning in free_init_pages()
do not copy more than ramdisk_size, noticed by Johannes

Reported-by: Stanislaw Gruszka <sgruszka@redhat.com>
Tested-by: Stanislaw Gruszka <sgruszka@redhat.com>
Signed-off-by: Yinghai Lu <yinghai@kernel.org>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: David Miller <davem@davemloft.net>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
LKML-Reference: <1269830604-26214-3-git-send-email-yinghai@kernel.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>

authored by

Yinghai Lu and committed by
Ingo Molnar
c967da6a 596b711e

+37 -12
+3 -1
arch/x86/kernel/head32.c
··· 7 7 8 8 #include <linux/init.h> 9 9 #include <linux/start_kernel.h> 10 + #include <linux/mm.h> 10 11 11 12 #include <asm/setup.h> 12 13 #include <asm/sections.h> ··· 45 44 #ifdef CONFIG_BLK_DEV_INITRD 46 45 /* Reserve INITRD */ 47 46 if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) { 47 + /* Assume only end is not page aligned */ 48 48 u64 ramdisk_image = boot_params.hdr.ramdisk_image; 49 49 u64 ramdisk_size = boot_params.hdr.ramdisk_size; 50 - u64 ramdisk_end = ramdisk_image + ramdisk_size; 50 + u64 ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size); 51 51 reserve_early(ramdisk_image, ramdisk_end, "RAMDISK"); 52 52 } 53 53 #endif
+2 -1
arch/x86/kernel/head64.c
··· 103 103 #ifdef CONFIG_BLK_DEV_INITRD 104 104 /* Reserve INITRD */ 105 105 if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) { 106 + /* Assume only end is not page aligned */ 106 107 unsigned long ramdisk_image = boot_params.hdr.ramdisk_image; 107 108 unsigned long ramdisk_size = boot_params.hdr.ramdisk_size; 108 - unsigned long ramdisk_end = ramdisk_image + ramdisk_size; 109 + unsigned long ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size); 109 110 reserve_early(ramdisk_image, ramdisk_end, "RAMDISK"); 110 111 } 111 112 #endif
+6 -4
arch/x86/kernel/setup.c
··· 314 314 #define MAX_MAP_CHUNK (NR_FIX_BTMAPS << PAGE_SHIFT) 315 315 static void __init relocate_initrd(void) 316 316 { 317 - 317 + /* Assume only end is not page aligned */ 318 318 u64 ramdisk_image = boot_params.hdr.ramdisk_image; 319 319 u64 ramdisk_size = boot_params.hdr.ramdisk_size; 320 + u64 area_size = PAGE_ALIGN(ramdisk_size); 320 321 u64 end_of_lowmem = max_low_pfn_mapped << PAGE_SHIFT; 321 322 u64 ramdisk_here; 322 323 unsigned long slop, clen, mapaddr; 323 324 char *p, *q; 324 325 325 326 /* We need to move the initrd down into lowmem */ 326 - ramdisk_here = find_e820_area(0, end_of_lowmem, ramdisk_size, 327 + ramdisk_here = find_e820_area(0, end_of_lowmem, area_size, 327 328 PAGE_SIZE); 328 329 329 330 if (ramdisk_here == -1ULL) ··· 333 332 334 333 /* Note: this includes all the lowmem currently occupied by 335 334 the initrd, we rely on that fact to keep the data intact. */ 336 - reserve_early(ramdisk_here, ramdisk_here + ramdisk_size, 335 + reserve_early(ramdisk_here, ramdisk_here + area_size, 337 336 "NEW RAMDISK"); 338 337 initrd_start = ramdisk_here + PAGE_OFFSET; 339 338 initrd_end = initrd_start + ramdisk_size; ··· 377 376 378 377 static void __init reserve_initrd(void) 379 378 { 379 + /* Assume only end is not page aligned */ 380 380 u64 ramdisk_image = boot_params.hdr.ramdisk_image; 381 381 u64 ramdisk_size = boot_params.hdr.ramdisk_size; 382 - u64 ramdisk_end = ramdisk_image + ramdisk_size; 382 + u64 ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size); 383 383 u64 end_of_lowmem = max_low_pfn_mapped << PAGE_SHIFT; 384 384 385 385 if (!boot_params.hdr.type_of_loader ||
+26 -6
arch/x86/mm/init.c
··· 331 331 332 332 void free_init_pages(char *what, unsigned long begin, unsigned long end) 333 333 { 334 - unsigned long addr = begin; 334 + unsigned long addr; 335 + unsigned long begin_aligned, end_aligned; 335 336 336 - if (addr >= end) 337 + /* Make sure boundaries are page aligned */ 338 + begin_aligned = PAGE_ALIGN(begin); 339 + end_aligned = end & PAGE_MASK; 340 + 341 + if (WARN_ON(begin_aligned != begin || end_aligned != end)) { 342 + begin = begin_aligned; 343 + end = end_aligned; 344 + } 345 + 346 + if (begin >= end) 337 347 return; 348 + 349 + addr = begin; 338 350 339 351 /* 340 352 * If debugging page accesses then do not free this memory but ··· 355 343 */ 356 344 #ifdef CONFIG_DEBUG_PAGEALLOC 357 345 printk(KERN_INFO "debug: unmapping init memory %08lx..%08lx\n", 358 - begin, PAGE_ALIGN(end)); 346 + begin, end); 359 347 set_memory_np(begin, (end - begin) >> PAGE_SHIFT); 360 348 #else 361 349 /* ··· 370 358 for (; addr < end; addr += PAGE_SIZE) { 371 359 ClearPageReserved(virt_to_page(addr)); 372 360 init_page_count(virt_to_page(addr)); 373 - memset((void *)(addr & ~(PAGE_SIZE-1)), 374 - POISON_FREE_INITMEM, PAGE_SIZE); 361 + memset((void *)addr, POISON_FREE_INITMEM, PAGE_SIZE); 375 362 free_page(addr); 376 363 totalram_pages++; 377 364 } ··· 387 376 #ifdef CONFIG_BLK_DEV_INITRD 388 377 void free_initrd_mem(unsigned long start, unsigned long end) 389 378 { 390 - free_init_pages("initrd memory", start, end); 379 + /* 380 + * end could be not aligned, and We can not align that, 381 + * decompresser could be confused by aligned initrd_end 382 + * We already reserve the end partial page before in 383 + * - i386_start_kernel() 384 + * - x86_64_start_kernel() 385 + * - relocate_initrd() 386 + * So here We can do PAGE_ALIGN() safely to get partial page to be freed 387 + */ 388 + free_init_pages("initrd memory", start, PAGE_ALIGN(end)); 391 389 } 392 390 #endif