powerpc/fadump: handle crash memory ranges array index overflow
authorHari Bathini <hbathini@linux.ibm.com>
Mon, 6 Aug 2018 20:42:45 +0000 (02:12 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 9 Sep 2018 17:55:54 +0000 (19:55 +0200)
commit 1bd6a1c4b80a28d975287630644e6b47d0f977a5 upstream.

Crash memory ranges is an array of memory ranges of the crashing kernel
to be exported as a dump via /proc/vmcore file. The size of the array
is set based on INIT_MEMBLOCK_REGIONS, which works alright in most cases
where memblock memory regions count is less than INIT_MEMBLOCK_REGIONS
value. But this count can grow beyond INIT_MEMBLOCK_REGIONS value since
commit 142b45a72e22 ("memblock: Add array resizing support").

On large memory systems with a few DLPAR operations, the memblock memory
regions count could be larger than INIT_MEMBLOCK_REGIONS value. On such
systems, registering fadump results in crash or other system failures
like below:

  task: c00007f39a290010 ti: c00000000b738000 task.ti: c00000000b738000
  NIP: c000000000047df4 LR: c0000000000f9e58 CTR: c00000000010f180
  REGS: c00000000b73b570 TRAP: 0300   Tainted: G          L   X  (4.4.140+)
  MSR: 8000000000009033 <SF,EE,ME,IR,DR,RI,LE>  CR: 22004484  XER: 20000000
  CFAR: c000000000008500 DAR: 000007a450000000 DSISR: 40000000 SOFTE: 0
  ...
  NIP [c000000000047df4] smp_send_reschedule+0x24/0x80
  LR [c0000000000f9e58] resched_curr+0x138/0x160
  Call Trace:
    resched_curr+0x138/0x160 (unreliable)
    check_preempt_curr+0xc8/0xf0
    ttwu_do_wakeup+0x38/0x150
    try_to_wake_up+0x224/0x4d0
    __wake_up_common+0x94/0x100
    ep_poll_callback+0xac/0x1c0
    __wake_up_common+0x94/0x100
    __wake_up_sync_key+0x70/0xa0
    sock_def_readable+0x58/0xa0
    unix_stream_sendmsg+0x2dc/0x4c0
    sock_sendmsg+0x68/0xa0
    ___sys_sendmsg+0x2cc/0x2e0
    __sys_sendmsg+0x5c/0xc0
    SyS_socketcall+0x36c/0x3f0
    system_call+0x3c/0x100

as array index overflow is not checked for while setting up crash memory
ranges causing memory corruption. To resolve this issue, dynamically
allocate memory for crash memory ranges and resize it incrementally,
in units of pagesize, on hitting array size limit.

Fixes: 2df173d9e85d ("fadump: Initialize elfcore header and add PT_LOAD program headers.")
Cc: stable@vger.kernel.org # v3.4+
Signed-off-by: Hari Bathini <hbathini@linux.ibm.com>
Reviewed-by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com>
[mpe: Just use PAGE_SIZE directly, fixup variable placement]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/powerpc/include/asm/fadump.h
arch/powerpc/kernel/fadump.c

index 5a23010af600337b89887cab179cf2e9b1b385b0..1e7a33592e297aae7f6e5001c20d3b005eb6d81c 100644 (file)
@@ -195,9 +195,6 @@ struct fadump_crash_info_header {
        struct cpumask  online_mask;
 };
 
-/* Crash memory ranges */
-#define INIT_CRASHMEM_RANGES   (INIT_MEMBLOCK_REGIONS + 2)
-
 struct fad_crash_memory_ranges {
        unsigned long long      base;
        unsigned long long      size;
index d0020bc1f2095c6d1433dc390d30aa327b99a9e6..5a6470383ca3968af8b3f5d3eb0856238d746435 100644 (file)
@@ -47,8 +47,10 @@ static struct fadump_mem_struct fdm;
 static const struct fadump_mem_struct *fdm_active;
 
 static DEFINE_MUTEX(fadump_mutex);
-struct fad_crash_memory_ranges crash_memory_ranges[INIT_CRASHMEM_RANGES];
+struct fad_crash_memory_ranges *crash_memory_ranges;
+int crash_memory_ranges_size;
 int crash_mem_ranges;
+int max_crash_mem_ranges;
 
 /* Scan the Firmware Assisted dump configuration details. */
 int __init early_init_dt_scan_fw_dump(unsigned long node,
@@ -843,38 +845,88 @@ static int __init process_fadump(const struct fadump_mem_struct *fdm_active)
        return 0;
 }
 
-static inline void fadump_add_crash_memory(unsigned long long base,
-                                       unsigned long long end)
+static void free_crash_memory_ranges(void)
+{
+       kfree(crash_memory_ranges);
+       crash_memory_ranges = NULL;
+       crash_memory_ranges_size = 0;
+       max_crash_mem_ranges = 0;
+}
+
+/*
+ * Allocate or reallocate crash memory ranges array in incremental units
+ * of PAGE_SIZE.
+ */
+static int allocate_crash_memory_ranges(void)
+{
+       struct fad_crash_memory_ranges *new_array;
+       u64 new_size;
+
+       new_size = crash_memory_ranges_size + PAGE_SIZE;
+       pr_debug("Allocating %llu bytes of memory for crash memory ranges\n",
+                new_size);
+
+       new_array = krealloc(crash_memory_ranges, new_size, GFP_KERNEL);
+       if (new_array == NULL) {
+               pr_err("Insufficient memory for setting up crash memory ranges\n");
+               free_crash_memory_ranges();
+               return -ENOMEM;
+       }
+
+       crash_memory_ranges = new_array;
+       crash_memory_ranges_size = new_size;
+       max_crash_mem_ranges = (new_size /
+                               sizeof(struct fad_crash_memory_ranges));
+       return 0;
+}
+
+static inline int fadump_add_crash_memory(unsigned long long base,
+                                         unsigned long long end)
 {
        if (base == end)
-               return;
+               return 0;
+
+       if (crash_mem_ranges == max_crash_mem_ranges) {
+               int ret;
+
+               ret = allocate_crash_memory_ranges();
+               if (ret)
+                       return ret;
+       }
 
        pr_debug("crash_memory_range[%d] [%#016llx-%#016llx], %#llx bytes\n",
                crash_mem_ranges, base, end - 1, (end - base));
        crash_memory_ranges[crash_mem_ranges].base = base;
        crash_memory_ranges[crash_mem_ranges].size = end - base;
        crash_mem_ranges++;
+       return 0;
 }
 
-static void fadump_exclude_reserved_area(unsigned long long start,
+static int fadump_exclude_reserved_area(unsigned long long start,
                                        unsigned long long end)
 {
        unsigned long long ra_start, ra_end;
+       int ret = 0;
 
        ra_start = fw_dump.reserve_dump_area_start;
        ra_end = ra_start + fw_dump.reserve_dump_area_size;
 
        if ((ra_start < end) && (ra_end > start)) {
                if ((start < ra_start) && (end > ra_end)) {
-                       fadump_add_crash_memory(start, ra_start);
-                       fadump_add_crash_memory(ra_end, end);
+                       ret = fadump_add_crash_memory(start, ra_start);
+                       if (ret)
+                               return ret;
+
+                       ret = fadump_add_crash_memory(ra_end, end);
                } else if (start < ra_start) {
-                       fadump_add_crash_memory(start, ra_start);
+                       ret = fadump_add_crash_memory(start, ra_start);
                } else if (ra_end < end) {
-                       fadump_add_crash_memory(ra_end, end);
+                       ret = fadump_add_crash_memory(ra_end, end);
                }
        } else
-               fadump_add_crash_memory(start, end);
+               ret = fadump_add_crash_memory(start, end);
+
+       return ret;
 }
 
 static int fadump_init_elfcore_header(char *bufp)
@@ -914,10 +966,11 @@ static int fadump_init_elfcore_header(char *bufp)
  * Traverse through memblock structure and setup crash memory ranges. These
  * ranges will be used create PT_LOAD program headers in elfcore header.
  */
-static void fadump_setup_crash_memory_ranges(void)
+static int fadump_setup_crash_memory_ranges(void)
 {
        struct memblock_region *reg;
        unsigned long long start, end;
+       int ret;
 
        pr_debug("Setup crash memory ranges.\n");
        crash_mem_ranges = 0;
@@ -928,7 +981,9 @@ static void fadump_setup_crash_memory_ranges(void)
         * specified during fadump registration. We need to create a separate
         * program header for this chunk with the correct offset.
         */
-       fadump_add_crash_memory(RMA_START, fw_dump.boot_memory_size);
+       ret = fadump_add_crash_memory(RMA_START, fw_dump.boot_memory_size);
+       if (ret)
+               return ret;
 
        for_each_memblock(memory, reg) {
                start = (unsigned long long)reg->base;
@@ -948,8 +1003,12 @@ static void fadump_setup_crash_memory_ranges(void)
                }
 
                /* add this range excluding the reserved dump area. */
-               fadump_exclude_reserved_area(start, end);
+               ret = fadump_exclude_reserved_area(start, end);
+               if (ret)
+                       return ret;
        }
+
+       return 0;
 }
 
 /*
@@ -1072,6 +1131,7 @@ static int register_fadump(void)
 {
        unsigned long addr;
        void *vaddr;
+       int ret;
 
        /*
         * If no memory is reserved then we can not register for firmware-
@@ -1080,7 +1140,9 @@ static int register_fadump(void)
        if (!fw_dump.reserve_dump_area_size)
                return -ENODEV;
 
-       fadump_setup_crash_memory_ranges();
+       ret = fadump_setup_crash_memory_ranges();
+       if (ret)
+               return ret;
 
        addr = be64_to_cpu(fdm.rmr_region.destination_address) + be64_to_cpu(fdm.rmr_region.source_len);
        /* Initialize fadump crash info header. */
@@ -1158,6 +1220,7 @@ void fadump_cleanup(void)
        } else if (fw_dump.dump_registered) {
                /* Un-register Firmware-assisted dump if it was registered. */
                fadump_unregister_dump(&fdm);
+               free_crash_memory_ranges();
        }
 }