Blob Blame History Raw
From: Philipp Rudo <prudo@linux.ibm.com>
Subject: s390/kdump: Fix elfcorehdr size calculation
Patch-mainline: v4.19-rc1
Git-commit: 8cce437fbb5c1f2af2f63834fa05082596beca5d
References: bsc#1117953, LTC#171112

Description:  s390/kdump: Fix elfcorehdr size calculation
Symptom:      BUG_ON triggeres kernel panic in crash kernel.
Problem:      When the crash kernel allocates memory for the elfcorehdr, the
              assumed per-cpu memory is too small. The mis-calculation is
              usually countered by enough spare memory in the overhead
              (Elf_Ehdr, vmcoreinfo, etc.). However, with a growing overhead
              and/or huge cpu count the calculated size gets too small
              triggering the BUG_ON.
Solution:     Properly calculate the required size.
Reproduction: -

Upstream-Description:

              s390/kdump: Fix elfcorehdr size calculation

              Before the memory for the elfcorehdr is allocated the required size is
              estimated with

                     alloc_size = 0x1000 + get_cpu_cnt() * 0x4a0 +
                             mem_chunk_cnt * sizeof(Elf64_Phdr);

              Where 0x4a0 is used as size for the ELF notes to store the register
              contend. This size is 8 bytes too small. Usually this does not immediately
              cause a problem because the page reserved for overhead (Elf_Ehdr,
              vmcoreinfo, etc.) is pretty generous. So usually there is enough spare
              memory to counter the mis-calculated per cpu size. However, with growing
              overhead and/or a huge cpu count the allocated size gets too small for the
              elfcorehdr. Ultimately a BUG_ON is triggered causing the crash kernel to
              panic.

              Fix this by properly calculating the required size instead of relying on
              magic numbers.

              Fixes: a62bc07392539 ("s390/kdump: add support for vector extension")
              Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
              Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>


Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
Acked-by: Petr Tesarik <ptesarik@suse.com>
---
 arch/s390/kernel/crash_dump.c |  104 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 98 insertions(+), 6 deletions(-)

--- a/arch/s390/kernel/crash_dump.c
+++ b/arch/s390/kernel/crash_dump.c
@@ -305,6 +305,15 @@ static void *kzalloc_panic(int len)
 	return rc;
 }
 
+static const char *nt_name(Elf64_Word type)
+{
+	const char *name = "LINUX";
+
+	if (type == NT_PRPSINFO || type == NT_PRSTATUS || type == NT_PRFPREG)
+		name = KEXEC_CORE_NOTE_NAME;
+	return name;
+}
+
 /*
  * Initialize ELF note
  */
@@ -331,11 +340,26 @@ static void *nt_init_name(void *buf, Elf
 
 static inline void *nt_init(void *buf, Elf64_Word type, void *desc, int d_len)
 {
-	const char *note_name = "LINUX";
+	return nt_init_name(buf, type, desc, d_len, nt_name(type));
+}
 
-	if (type == NT_PRPSINFO || type == NT_PRSTATUS || type == NT_PRFPREG)
-		note_name = KEXEC_CORE_NOTE_NAME;
-	return nt_init_name(buf, type, desc, d_len, note_name);
+/*
+ * Calculate the size of ELF note
+ */
+static size_t nt_size_name(int d_len, const char *name)
+{
+	size_t size;
+
+	size = sizeof(Elf64_Nhdr);
+	size += roundup(strlen(name) + 1, 4);
+	size += roundup(d_len, 4);
+
+	return size;
+}
+
+static inline size_t nt_size(Elf64_Word type, int d_len)
+{
+	return nt_size_name(d_len, nt_name(type));
 }
 
 /*
@@ -374,6 +398,29 @@ static void *fill_cpu_elf_notes(void *pt
 }
 
 /*
+ * Calculate size of ELF notes per cpu
+ */
+static size_t get_cpu_elf_notes_size(void)
+{
+	struct save_area *sa = NULL;
+	size_t size;
+
+	size =	nt_size(NT_PRSTATUS, sizeof(struct elf_prstatus));
+	size +=  nt_size(NT_PRFPREG, sizeof(elf_fpregset_t));
+	size +=  nt_size(NT_S390_TIMER, sizeof(sa->timer));
+	size +=  nt_size(NT_S390_TODCMP, sizeof(sa->todcmp));
+	size +=  nt_size(NT_S390_TODPREG, sizeof(sa->todpreg));
+	size +=  nt_size(NT_S390_CTRS, sizeof(sa->ctrs));
+	size +=  nt_size(NT_S390_PREFIX, sizeof(sa->prefix));
+	if (MACHINE_HAS_VX) {
+		size += nt_size(NT_S390_VXRS_HIGH, sizeof(sa->vxrs_high));
+		size += nt_size(NT_S390_VXRS_LOW, sizeof(sa->vxrs_low));
+	}
+
+	return size;
+}
+
+/*
  * Initialize prpsinfo note (new kernel)
  */
 static void *nt_prpsinfo(void *ptr)
@@ -428,6 +475,30 @@ static void *nt_vmcoreinfo(void *ptr)
 	return nt_init_name(ptr, 0, vmcoreinfo, size, "VMCOREINFO");
 }
 
+static size_t nt_vmcoreinfo_size(void)
+{
+	const char *name = "VMCOREINFO";
+	char nt_name[11];
+	Elf64_Nhdr note;
+	void *addr;
+
+	if (copy_oldmem_kernel(&addr, &S390_lowcore.vmcore_info, sizeof(addr)))
+		return 0;
+
+	if (copy_oldmem_kernel(&note, addr, sizeof(note)))
+		return 0;
+
+	memset(nt_name, 0, sizeof(nt_name));
+	if (copy_oldmem_kernel(nt_name, addr + sizeof(note),
+			       sizeof(nt_name) - 1))
+		return 0;
+
+	if (strcmp(nt_name, name) != 0)
+		return 0;
+
+	return nt_size_name(note.n_descsz, name);
+}
+
 /*
  * Initialize final note (needed for /proc/vmcore code)
  */
@@ -538,6 +609,27 @@ static void *notes_init(Elf64_Phdr *phdr
 	return ptr;
 }
 
+static size_t get_elfcorehdr_size(int mem_chunk_cnt)
+{
+	size_t size;
+
+	size = sizeof(Elf64_Ehdr);
+	/* PT_NOTES */
+	size += sizeof(Elf64_Phdr);
+	/* nt_prpsinfo */
+	size += nt_size(NT_PRPSINFO, sizeof(struct elf_prpsinfo));
+	/* regsets */
+	size += get_cpu_cnt() * get_cpu_elf_notes_size();
+	/* nt_vmcoreinfo */
+	size += nt_vmcoreinfo_size();
+	/* nt_final */
+	size += sizeof(Elf64_Nhdr);
+	/* PT_LOADS */
+	size += mem_chunk_cnt * sizeof(Elf64_Phdr);
+
+	return size;
+}
+
 /*
  * Create ELF core header (new kernel)
  */
@@ -565,8 +657,8 @@ int elfcorehdr_alloc(unsigned long long
 
 	mem_chunk_cnt = get_mem_chunk_cnt();
 
-	alloc_size = 0x1000 + get_cpu_cnt() * 0x4a0 +
-		mem_chunk_cnt * sizeof(Elf64_Phdr);
+	alloc_size = get_elfcorehdr_size(mem_chunk_cnt);
+
 	hdr = kzalloc_panic(alloc_size);
 	/* Init elf header */
 	ptr = ehdr_init(hdr, mem_chunk_cnt);