Commit c967da6a0ba837f762042e931d4afcf72045547c

Authored by Yinghai Lu
Committed by Ingo Molnar
1 parent 596b711ed6

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>

Showing 4 changed files with 37 additions and 12 deletions Side-by-side Diff

arch/x86/kernel/head32.c
... ... @@ -7,6 +7,7 @@
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>
13 14  
... ... @@ -44,9 +45,10 @@
44 45 #ifdef CONFIG_BLK_DEV_INITRD
45 46 /* Reserve INITRD */
46 47 if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
  48 + /* Assume only end is not page aligned */
47 49 u64 ramdisk_image = boot_params.hdr.ramdisk_image;
48 50 u64 ramdisk_size = boot_params.hdr.ramdisk_size;
49   - u64 ramdisk_end = ramdisk_image + ramdisk_size;
  51 + u64 ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size);
50 52 reserve_early(ramdisk_image, ramdisk_end, "RAMDISK");
51 53 }
52 54 #endif
arch/x86/kernel/head64.c
... ... @@ -103,9 +103,10 @@
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
arch/x86/kernel/setup.c
... ... @@ -314,16 +314,17 @@
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)
... ... @@ -332,7 +333,7 @@
332 333  
333 334 /* Note: this includes all the lowmem currently occupied by
334 335 the initrd, we rely on that fact to keep the data intact. */
335   - reserve_early(ramdisk_here, ramdisk_here + ramdisk_size,
  336 + reserve_early(ramdisk_here, ramdisk_here + area_size,
336 337 "NEW RAMDISK");
337 338 initrd_start = ramdisk_here + PAGE_OFFSET;
338 339 initrd_end = initrd_start + ramdisk_size;
339 340  
... ... @@ -376,9 +377,10 @@
376 377  
377 378 static void __init reserve_initrd(void)
378 379 {
  380 + /* Assume only end is not page aligned */
379 381 u64 ramdisk_image = boot_params.hdr.ramdisk_image;
380 382 u64 ramdisk_size = boot_params.hdr.ramdisk_size;
381   - u64 ramdisk_end = ramdisk_image + ramdisk_size;
  383 + u64 ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size);
382 384 u64 end_of_lowmem = max_low_pfn_mapped << PAGE_SHIFT;
383 385  
384 386 if (!boot_params.hdr.type_of_loader ||
... ... @@ -331,11 +331,23 @@
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;
338 348  
  349 + addr = begin;
  350 +
339 351 /*
340 352 * If debugging page accesses then do not free this memory but
341 353 * mark them not present - any buggy init-section access will
... ... @@ -343,7 +355,7 @@
343 355 */
344 356 #ifdef CONFIG_DEBUG_PAGEALLOC
345 357 printk(KERN_INFO "debug: unmapping init memory %08lx..%08lx\n",
346   - begin, PAGE_ALIGN(end));
  358 + begin, end);
347 359 set_memory_np(begin, (end - begin) >> PAGE_SHIFT);
348 360 #else
349 361 /*
... ... @@ -358,8 +370,7 @@
358 370 for (; addr < end; addr += PAGE_SIZE) {
359 371 ClearPageReserved(virt_to_page(addr));
360 372 init_page_count(virt_to_page(addr));
361   - memset((void *)(addr & ~(PAGE_SIZE-1)),
362   - POISON_FREE_INITMEM, PAGE_SIZE);
  373 + memset((void *)addr, POISON_FREE_INITMEM, PAGE_SIZE);
363 374 free_page(addr);
364 375 totalram_pages++;
365 376 }
... ... @@ -376,7 +387,16 @@
376 387 #ifdef CONFIG_BLK_DEV_INITRD
377 388 void free_initrd_mem(unsigned long start, unsigned long end)
378 389 {
379   - free_init_pages("initrd memory", start, end);
  390 + /*
  391 + * end could be not aligned, and We can not align that,
  392 + * decompresser could be confused by aligned initrd_end
  393 + * We already reserve the end partial page before in
  394 + * - i386_start_kernel()
  395 + * - x86_64_start_kernel()
  396 + * - relocate_initrd()
  397 + * So here We can do PAGE_ALIGN() safely to get partial page to be freed
  398 + */
  399 + free_init_pages("initrd memory", start, PAGE_ALIGN(end));
380 400 }
381 401 #endif