From: Dave Anderson <anderson@redhat.com>
Date: Thu Feb 20 15:36:25 2014 -0500
Subject: Fix kmem slab initialization failures
References: bnc#885082
Patch-mainline: v6.0.6
Git-commit: c0b7a74fc13121203810d06d163550436b2d5476

Fix to prevent a possible invocation-time error on Linux 3.7 and
later kernels configured with CONFIG_SLAB, running against vmcore
files filtered with the makedumpfile(8) facility.  Without the
patch, the message "crash: page excluded: kernel virtual address:
<address>  type: kmem_cache buffer" is immediately followed by
the message "crash: unable to initialize kmem slab cache subsystem".
Because of a kernel data structure name change from "cache_cache" to
"kmem_cache_boot", the crash utility failed to properly downsize
the stored size of the kernel's kmem_cache data structure from the
size indicated by the vmlinux debuginfo data.  This in turn could
lead to reading beyond the end of a kmem_cache data structure into
a page of memory that had been excluded from the vmcore.  The fix
was also applied to kernels configured with CONFIG_SLUB.
(anderson@redhat.com)

Acked-by: Petr Tesarik <ptesarik@suse.cz>

diff --git a/memory.c b/memory.c
index 17e5d1c..01c5ffe 100644
--- a/memory.c
+++ b/memory.c
@@ -8677,13 +8677,32 @@ static void
 kmem_cache_downsize(void)
 {
 	char *cache_buf;
-	uint buffer_size; 
+	ulong kmem_cache;
+	uint buffer_size, object_size; 
 	int nr_node_ids;
 	int nr_cpu_ids;
 
+	if (vt->flags & KMALLOC_SLUB) {
+		if (kernel_symbol_exists("kmem_cache") &&
+		    VALID_MEMBER(kmem_cache_objsize) &&
+		    try_get_symbol_data("kmem_cache", 
+		    sizeof(ulong), &kmem_cache) &&
+		    readmem(kmem_cache + OFFSET(kmem_cache_objsize), 
+		    KVADDR, &object_size, sizeof(int), 
+		    "kmem_cache objsize/object_size", RETURN_ON_ERROR)) {
+			ASSIGN_SIZE(kmem_cache) = object_size;
+			if (CRASHDEBUG(1))
+				fprintf(fp, "\nkmem_cache_downsize: %ld to %ld\n",
+					STRUCT_SIZE("kmem_cache"), 
+					SIZE(kmem_cache));
+		}
+		return;
+	}
+
 	if ((THIS_KERNEL_VERSION < LINUX(2,6,22)) ||
 	    !(vt->flags & PERCPU_KMALLOC_V2_NODES) ||
-	    !kernel_symbol_exists("cache_cache") ||
+	    (!kernel_symbol_exists("cache_cache") && 
+	     !kernel_symbol_exists("kmem_cache_boot")) ||
 	    (!MEMBER_EXISTS("kmem_cache", "buffer_size") &&
 	     !MEMBER_EXISTS("kmem_cache", "size"))) {
 		return;
@@ -8691,7 +8710,28 @@ kmem_cache_downsize(void)
 
 	if (vt->flags & NODELISTS_IS_PTR) {
 		/* 
-		 * kmem_cache.array[] is actually sized by 
+		 * More recent kernels have kmem_cache.array[] sized
+		 * by the number of cpus plus the number of nodes.
+		 */
+		if (kernel_symbol_exists("kmem_cache_boot") &&
+		    MEMBER_EXISTS("kmem_cache", "object_size") &&
+		    readmem(symbol_value("kmem_cache_boot") +
+		    MEMBER_OFFSET("kmem_cache", "object_size"), 
+		    KVADDR, &object_size, sizeof(int), 
+		    "kmem_cache_boot object_size", RETURN_ON_ERROR))
+			ASSIGN_SIZE(kmem_cache_s) = object_size;
+		else if (kernel_symbol_exists("cache_cache") &&
+		    MEMBER_EXISTS("kmem_cache", "object_size") &&
+		    readmem(symbol_value("cache_cache") +
+		    MEMBER_OFFSET("kmem_cache", "object_size"), 
+		    KVADDR, &object_size, sizeof(int), 
+		    "cache_cache object_size", RETURN_ON_ERROR))
+			ASSIGN_SIZE(kmem_cache_s) = object_size;
+		else
+			object_size = 0;
+
+		/* 
+		 * Older kernels have kmem_cache.array[] sized by 
 		 * the number of cpus; real value is nr_cpu_ids, 
 		 * but fallback is kt->cpus.
 		 */
@@ -8702,10 +8742,12 @@ kmem_cache_downsize(void)
 			nr_cpu_ids = kt->cpus;
 	
 		ARRAY_LENGTH(kmem_cache_s_array) = nr_cpu_ids;
-		ASSIGN_SIZE(kmem_cache_s) = OFFSET(kmem_cache_s_array) +
-			sizeof(ulong) * nr_cpu_ids;
+
+		if (!object_size)
+			ASSIGN_SIZE(kmem_cache_s) = OFFSET(kmem_cache_s_array) +
+				sizeof(ulong) * nr_cpu_ids;
 		if (CRASHDEBUG(1))
-			fprintf(fp, "kmem_cache_downsize: %ld to %ld\n",
+			fprintf(fp, "\nkmem_cache_downsize: %ld to %ld\n",
 				STRUCT_SIZE("kmem_cache"), SIZE(kmem_cache_s));
 		return;
 	}
@@ -8713,7 +8755,7 @@ kmem_cache_downsize(void)
 	cache_buf = GETBUF(SIZE(kmem_cache_s));
 
 	if (!readmem(symbol_value("cache_cache"), KVADDR, cache_buf, 
-	    SIZE(kmem_cache_s), "kmem_cache buffer", FAULT_ON_ERROR)) {
+	    SIZE(kmem_cache_s), "kmem_cache buffer", RETURN_ON_ERROR)) {
 		FREEBUF(cache_buf);
 		return;
 	}
@@ -8741,8 +8783,7 @@ kmem_cache_downsize(void)
 
 		if (CRASHDEBUG(1)) {
      			fprintf(fp, 
-			    "\nkmem_cache_downsize: SIZE(kmem_cache_s): %ld "
-			    "cache_cache.buffer_size: %d\n",
+			    "\nkmem_cache_downsize: %ld to %d\n",
 				STRUCT_SIZE("kmem_cache"), buffer_size);
 			fprintf(fp,
 			    "kmem_cache_downsize: nr_node_ids: %ld\n",
@@ -16790,6 +16831,8 @@ kmem_cache_init_slub(void)
 		    "kmem_cache_init_slub: numnodes: %d without CONFIG_NUMA\n",
 			vt->numnodes);
 
+	kmem_cache_downsize();
+
 	vt->cpu_slab_type = MEMBER_TYPE("kmem_cache", "cpu_slab");
 
 	vt->flags |= KMEM_CACHE_INIT;
