DragonFlyBSD Kernel Audit
DF-2773 / fix.diff
← back to finding ↓ download raw
--- a/sys/kern/link_elf_obj.c
+++ b/sys/kern/link_elf_obj.c
@@ -48,6 +48,14 @@
 #include <vm/pmap.h>
 #include <vm/vm_map.h>
 
+/*
+ * No legitimate KLD section comes anywhere near this size.  Capping
+ * sh_size keeps the mapsize accumulator in link_elf_obj_load_file from
+ * wrapping 2^64 on crafted modules (which would size the KVA mapping
+ * far below the sum of the per-section bzero()/read lengths).
+ */
+#define	LINKER_MAX_SECSIZE	(1LL << 44)
+
 static int	link_elf_obj_preload_file(const char *, linker_file_t *);
 static int	link_elf_obj_preload_finish(linker_file_t);
 static int	link_elf_obj_load_file(const char *, linker_file_t *);
@@ -63,7 +71,7 @@
 static int
 link_elf_obj_lookup_set(linker_file_t, const char *,
 		    void ***, void ***, int *);
-static void	link_elf_obj_reloc_local(linker_file_t lf);
+static int	link_elf_obj_reloc_local(linker_file_t lf);
 static int elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps, Elf_Addr *);
 
 static struct linker_class_ops link_elf_obj_class_ops = {
@@ -137,6 +145,27 @@
 static int	relocate_file(linker_file_t lf);
 
 /*
+ * A REL/RELA section must name (via sh_info) a section that actually
+ * gets a progtab entry, otherwise findbase() fails later and the loader
+ * panics ("lost base for reltab/relatab") instead of rejecting the
+ * file.  skip_empty is set for the file case, where size-0 sections are
+ * never entered into progtab.
+ */
+static int
+elf_obj_reloc_target_ok(const Elf_Shdr *shdr, int e_shnum,
+    Elf_Size sh_info, int skip_empty)
+{
+	if (sh_info >= (Elf_Size)e_shnum)
+		return (0);
+	if (shdr[sh_info].sh_type != SHT_PROGBITS &&
+	    shdr[sh_info].sh_type != SHT_NOBITS)
+		return (0);
+	if (skip_empty && shdr[sh_info].sh_size == 0)
+		return (0);
+	return (1);
+}
+
+/*
  * The kernel symbol table starts here.
  */
 extern struct _dynamic _DYNAMIC;
@@ -232,9 +261,19 @@
 			symstrindex = shdr[i].sh_link;
 			break;
 		case SHT_REL:
+			if (!elf_obj_reloc_target_ok(shdr, hdr->e_shnum,
+			    shdr[i].sh_info, 0)) {
+				error = ENOEXEC;
+				goto out;
+			}
 			ef->nreltab++;
 			break;
 		case SHT_RELA:
+			if (!elf_obj_reloc_target_ok(shdr, hdr->e_shnum,
+			    shdr[i].sh_info, 0)) {
+				error = ENOEXEC;
+				goto out;
+			}
 			ef->nrelatab++;
 			break;
 		}
@@ -365,7 +404,9 @@
 		panic("lost relatab");
 
 	/* Local intra-module relocations */
-	link_elf_obj_reloc_local(lf);
+	error = link_elf_obj_reloc_local(lf);
+	if (error)
+		goto out;
 
 	*result = lf;
 	return (0);
@@ -528,9 +569,19 @@
 			symstrindex = shdr[i].sh_link;
 			break;
 		case SHT_REL:
+			if (!elf_obj_reloc_target_ok(shdr, hdr->e_shnum,
+			    shdr[i].sh_info, 1)) {
+				error = ENOEXEC;
+				goto out;
+			}
 			ef->nreltab++;
 			break;
 		case SHT_RELA:
+			if (!elf_obj_reloc_target_ok(shdr, hdr->e_shnum,
+			    shdr[i].sh_info, 1)) {
+				error = ENOEXEC;
+				goto out;
+			}
 			ef->nrelatab++;
 			break;
 		case SHT_STRTAB:
@@ -624,6 +675,12 @@
 		switch (shdr[i].sh_type) {
 		case SHT_PROGBITS:
 		case SHT_NOBITS:
+			if (shdr[i].sh_size > (Elf_Size)LINKER_MAX_SECSIZE) {
+				link_elf_obj_error(filename,
+				    "section too large");
+				error = ENOEXEC;
+				goto out;
+			}
 			alignmask = shdr[i].sh_addralign - 1;
 			mapsize += alignmask;
 			mapsize &= ~alignmask;
@@ -815,7 +872,9 @@
 		      (vm_offset_t) ef->address + mapsize);
 
 	/* Local intra-module relocations */
-	link_elf_obj_reloc_local(lf);
+	error = link_elf_obj_reloc_local(lf);
+	if (error)
+		goto out;
 
 	/* Pull in dependencies */
 	error = linker_load_dependencies(lf);
@@ -884,12 +943,19 @@
 		return;
 	}
 
-	for (i = 0; i < ef->nreltab; i++)
-		if (ef->reltab[i].rel)
-			kfree(ef->reltab[i].rel, M_LINKER);
-	for (i = 0; i < ef->nrelatab; i++)
-		if (ef->relatab[i].rela)
-			kfree(ef->relatab[i].rela, M_LINKER);
+	/*
+	 * The counters may be non-zero from the scan loop even though the
+	 * load aborted before the tables were allocated (e.g. "file has no
+	 * contents"), so guard the arrays themselves.
+	 */
+	if (ef->reltab)
+		for (i = 0; i < ef->nreltab; i++)
+			if (ef->reltab[i].rel)
+				kfree(ef->reltab[i].rel, M_LINKER);
+	if (ef->relatab)
+		for (i = 0; i < ef->nrelatab; i++)
+			if (ef->relatab[i].rela)
+				kfree(ef->relatab[i].rela, M_LINKER);
 	if (ef->reltab)
 		kfree(ef->reltab, M_LINKER);
 	if (ef->relatab)
@@ -974,7 +1040,7 @@
 		rellim = rel + ef->reltab[i].nrel;
 		base = findbase(ef, ef->reltab[i].sec);
 		if (base == 0)
-			panic("lost base for reltab");
+			return (ENOEXEC);
 		for ( ; rel < rellim; rel++) {
 			symidx = ELF_R_SYM(rel->r_info);
 			if (symidx >= ef->ddbsymcnt)
@@ -1001,7 +1067,7 @@
 		relalim = rela + ef->relatab[i].nrela;
 		base = findbase(ef, ef->relatab[i].sec);
 		if (base == 0)
-			panic("lost base for relatab");
+			return (ENOEXEC);
 		for ( ; rela < relalim; rela++) {
 			symidx = ELF_R_SYM(rela->r_info);
 			if (symidx >= ef->ddbsymcnt)
@@ -1219,7 +1285,7 @@
 	}
 }
 
-static void
+static int
 link_elf_obj_reloc_local(linker_file_t lf)
 {
 	elf_file_t ef = lf->priv;
@@ -1242,7 +1308,7 @@
 		rellim = rel + ef->reltab[i].nrel;
 		base = findbase(ef, ef->reltab[i].sec);
 		if (base == 0)
-			panic("lost base for reltab");
+			return (ENOEXEC);
 		for ( ; rel < rellim; rel++) {
 			symidx = ELF_R_SYM(rel->r_info);
 			if (symidx >= ef->ddbsymcnt)
@@ -1264,7 +1330,7 @@
 		relalim = rela + ef->relatab[i].nrela;
 		base = findbase(ef, ef->relatab[i].sec);
 		if (base == 0)
-			panic("lost base for relatab");
+			return (ENOEXEC);
 		for ( ; rela < relalim; rela++) {
 			symidx = ELF_R_SYM(rela->r_info);
 			if (symidx >= ef->ddbsymcnt)
@@ -1277,4 +1343,6 @@
 			    elf_obj_lookup);
 		}
 	}
+
+	return (0);
 }