s390/modules: add relocation overflow checking

Given enough debug options some modules can grow large enough
that the GOT table gets bigger than 4K. On s390 the modules
are compiled with -fpic which limits the GOT to 4K. The end
result is a module that is loaded but won't work.

Add a sanity check to apply_rela and return with an error if
a relocation error is detected for a module.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/arch/s390/kernel/module.c b/arch/s390/kernel/module.c
index 4610dea..06f1731 100644
--- a/arch/s390/kernel/module.c
+++ b/arch/s390/kernel/module.c
@@ -65,8 +65,7 @@
 	vfree(module_region);
 }
 
-static void
-check_rela(Elf_Rela *rela, struct module *me)
+static void check_rela(Elf_Rela *rela, struct module *me)
 {
 	struct mod_arch_syminfo *info;
 
@@ -115,9 +114,8 @@
  * Account for GOT and PLT relocations. We can't add sections for
  * got and plt but we can increase the core module size.
  */
-int
-module_frob_arch_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
-			  char *secstrings, struct module *me)
+int module_frob_arch_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
+			      char *secstrings, struct module *me)
 {
 	Elf_Shdr *symtab;
 	Elf_Sym *symbols;
@@ -179,13 +177,52 @@
 	return 0;
 }
 
-static int
-apply_rela(Elf_Rela *rela, Elf_Addr base, Elf_Sym *symtab, 
-	   struct module *me)
+static int apply_rela_bits(Elf_Addr loc, Elf_Addr val,
+			   int sign, int bits, int shift)
+{
+	unsigned long umax;
+	long min, max;
+
+	if (val & ((1UL << shift) - 1))
+		return -ENOEXEC;
+	if (sign) {
+		val = (Elf_Addr)(((long) val) >> shift);
+		min = -(1L << (bits - 1));
+		max = (1L << (bits - 1)) - 1;
+		if ((long) val < min || (long) val > max)
+			return -ENOEXEC;
+	} else {
+		val >>= shift;
+		umax = ((1UL << (bits - 1)) << 1) - 1;
+		if ((unsigned long) val > umax)
+			return -ENOEXEC;
+	}
+
+	if (bits == 8)
+		*(unsigned char *) loc = val;
+	else if (bits == 12)
+		*(unsigned short *) loc = (val & 0xfff) |
+			(*(unsigned short *) loc & 0xf000);
+	else if (bits == 16)
+		*(unsigned short *) loc = val;
+	else if (bits == 20)
+		*(unsigned int *) loc = (val & 0xfff) << 16 |
+			(val & 0xff000) >> 4 |
+			(*(unsigned int *) loc & 0xf00000ff);
+	else if (bits == 32)
+		*(unsigned int *) loc = val;
+	else if (bits == 64)
+		*(unsigned long *) loc = val;
+	return 0;
+}
+
+static int apply_rela(Elf_Rela *rela, Elf_Addr base, Elf_Sym *symtab,
+		      const char *strtab, struct module *me)
 {
 	struct mod_arch_syminfo *info;
 	Elf_Addr loc, val;
 	int r_type, r_sym;
+	int rc;
 
 	/* This is where to make the change */
 	loc = base + rela->r_offset;
@@ -205,20 +242,17 @@
 	case R_390_64:		/* Direct 64 bit.  */
 		val += rela->r_addend;
 		if (r_type == R_390_8)
-			*(unsigned char *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 8, 0);
 		else if (r_type == R_390_12)
-			*(unsigned short *) loc = (val & 0xfff) |
-				(*(unsigned short *) loc & 0xf000);
+			rc = apply_rela_bits(loc, val, 0, 12, 0);
 		else if (r_type == R_390_16)
-			*(unsigned short *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 16, 0);
 		else if (r_type == R_390_20)
-			*(unsigned int *) loc =
-				(*(unsigned int *) loc & 0xf00000ff) |
-				(val & 0xfff) << 16 | (val & 0xff000) >> 4;
+			rc = apply_rela_bits(loc, val, 1, 20, 0);
 		else if (r_type == R_390_32)
-			*(unsigned int *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 32, 0);
 		else if (r_type == R_390_64)
-			*(unsigned long *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 64, 0);
 		break;
 	case R_390_PC16:	/* PC relative 16 bit.  */
 	case R_390_PC16DBL:	/* PC relative 16 bit shifted by 1.  */
@@ -227,15 +261,15 @@
 	case R_390_PC64:	/* PC relative 64 bit.	*/
 		val += rela->r_addend - loc;
 		if (r_type == R_390_PC16)
-			*(unsigned short *) loc = val;
+			rc = apply_rela_bits(loc, val, 1, 16, 0);
 		else if (r_type == R_390_PC16DBL)
-			*(unsigned short *) loc = val >> 1;
+			rc = apply_rela_bits(loc, val, 1, 16, 1);
 		else if (r_type == R_390_PC32DBL)
-			*(unsigned int *) loc = val >> 1;
+			rc = apply_rela_bits(loc, val, 1, 32, 1);
 		else if (r_type == R_390_PC32)
-			*(unsigned int *) loc = val;
+			rc = apply_rela_bits(loc, val, 1, 32, 0);
 		else if (r_type == R_390_PC64)
-			*(unsigned long *) loc = val;
+			rc = apply_rela_bits(loc, val, 1, 64, 0);
 		break;
 	case R_390_GOT12:	/* 12 bit GOT offset.  */
 	case R_390_GOT16:	/* 16 bit GOT offset.  */
@@ -260,26 +294,24 @@
 		val = info->got_offset + rela->r_addend;
 		if (r_type == R_390_GOT12 ||
 		    r_type == R_390_GOTPLT12)
-			*(unsigned short *) loc = (val & 0xfff) |
-				(*(unsigned short *) loc & 0xf000);
+			rc = apply_rela_bits(loc, val, 0, 12, 0);
 		else if (r_type == R_390_GOT16 ||
 			 r_type == R_390_GOTPLT16)
-			*(unsigned short *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 16, 0);
 		else if (r_type == R_390_GOT20 ||
 			 r_type == R_390_GOTPLT20)
-			*(unsigned int *) loc =
-				(*(unsigned int *) loc & 0xf00000ff) |
-				(val & 0xfff) << 16 | (val & 0xff000) >> 4;
+			rc = apply_rela_bits(loc, val, 1, 20, 0);
 		else if (r_type == R_390_GOT32 ||
 			 r_type == R_390_GOTPLT32)
-			*(unsigned int *) loc = val;
-		else if (r_type == R_390_GOTENT ||
-			 r_type == R_390_GOTPLTENT)
-			*(unsigned int *) loc =
-				(val + (Elf_Addr) me->module_core - loc) >> 1;
+			rc = apply_rela_bits(loc, val, 0, 32, 0);
 		else if (r_type == R_390_GOT64 ||
 			 r_type == R_390_GOTPLT64)
-			*(unsigned long *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 64, 0);
+		else if (r_type == R_390_GOTENT ||
+			 r_type == R_390_GOTPLTENT) {
+			val += (Elf_Addr) me->module_core - loc;
+			rc = apply_rela_bits(loc, val, 1, 32, 1);
+		}
 		break;
 	case R_390_PLT16DBL:	/* 16 bit PC rel. PLT shifted by 1.  */
 	case R_390_PLT32DBL:	/* 32 bit PC rel. PLT shifted by 1.  */
@@ -321,17 +353,17 @@
 			val += rela->r_addend - loc;
 		}
 		if (r_type == R_390_PLT16DBL)
-			*(unsigned short *) loc = val >> 1;
+			rc = apply_rela_bits(loc, val, 1, 16, 1);
 		else if (r_type == R_390_PLTOFF16)
-			*(unsigned short *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 16, 0);
 		else if (r_type == R_390_PLT32DBL)
-			*(unsigned int *) loc = val >> 1;
+			rc = apply_rela_bits(loc, val, 1, 32, 1);
 		else if (r_type == R_390_PLT32 ||
 			 r_type == R_390_PLTOFF32)
-			*(unsigned int *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 32, 0);
 		else if (r_type == R_390_PLT64 ||
 			 r_type == R_390_PLTOFF64)
-			*(unsigned long *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 64, 0);
 		break;
 	case R_390_GOTOFF16:	/* 16 bit offset to GOT.  */
 	case R_390_GOTOFF32:	/* 32 bit offset to GOT.  */
@@ -339,20 +371,20 @@
 		val = val + rela->r_addend -
 			((Elf_Addr) me->module_core + me->arch.got_offset);
 		if (r_type == R_390_GOTOFF16)
-			*(unsigned short *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 16, 0);
 		else if (r_type == R_390_GOTOFF32)
-			*(unsigned int *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 32, 0);
 		else if (r_type == R_390_GOTOFF64)
-			*(unsigned long *) loc = val;
+			rc = apply_rela_bits(loc, val, 0, 64, 0);
 		break;
 	case R_390_GOTPC:	/* 32 bit PC relative offset to GOT. */
 	case R_390_GOTPCDBL:	/* 32 bit PC rel. off. to GOT shifted by 1. */
 		val = (Elf_Addr) me->module_core + me->arch.got_offset +
 			rela->r_addend - loc;
 		if (r_type == R_390_GOTPC)
-			*(unsigned int *) loc = val;
+			rc = apply_rela_bits(loc, val, 1, 32, 0);
 		else if (r_type == R_390_GOTPCDBL)
-			*(unsigned int *) loc = val >> 1;
+			rc = apply_rela_bits(loc, val, 1, 32, 1);
 		break;
 	case R_390_COPY:
 	case R_390_GLOB_DAT:	/* Create GOT entry.  */
@@ -360,19 +392,25 @@
 	case R_390_RELATIVE:	/* Adjust by program base.  */
 		/* Only needed if we want to support loading of 
 		   modules linked with -shared. */
-		break;
+		return -ENOEXEC;
 	default:
-		printk(KERN_ERR "module %s: Unknown relocation: %u\n",
+		printk(KERN_ERR "module %s: unknown relocation: %u\n",
 		       me->name, r_type);
 		return -ENOEXEC;
 	}
+	if (rc) {
+		printk(KERN_ERR "module %s: relocation error for symbol %s "
+		       "(r_type %i, value 0x%lx)\n",
+		       me->name, strtab + symtab[r_sym].st_name,
+		       r_type, (unsigned long) val);
+		return rc;
+	}
 	return 0;
 }
 
-int
-apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
-		   unsigned int symindex, unsigned int relsec,
-		   struct module *me)
+int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
+		       unsigned int symindex, unsigned int relsec,
+		       struct module *me)
 {
 	Elf_Addr base;
 	Elf_Sym *symtab;
@@ -388,7 +426,7 @@
 	n = sechdrs[relsec].sh_size / sizeof(Elf_Rela);
 
 	for (i = 0; i < n; i++, rela++) {
-		rc = apply_rela(rela, base, symtab, me);
+		rc = apply_rela(rela, base, symtab, strtab, me);
 		if (rc)
 			return rc;
 	}