changeset 13902:953a602a9b70

3364 dboot should check boot archive integrity Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com> Reviewed by: Dan McDonald <danmcd@nexenta.com> Reviewed by: Richard Lowe <richlowe@richlowe.net> Reviewed by: Garrett D'Amore <garrett@damore.org> Approved by: Richard Lowe <richlowe@richlowe.net>
author Keith Wesolowski <keith.wesolowski@joyent.com>
date Tue, 18 Dec 2012 13:51:01 -0500
parents 8de47e31a5b1
children 93cd9d7e48de
files usr/src/common/crypto/sha1/sha1.c usr/src/uts/i86pc/Makefile.files usr/src/uts/i86pc/Makefile.rules usr/src/uts/i86pc/dboot/dboot_asm.s usr/src/uts/i86pc/dboot/dboot_startkern.c
diffstat 5 files changed, 157 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/common/crypto/sha1/sha1.c	Sun Dec 09 20:12:10 2012 -0500
+++ b/usr/src/common/crypto/sha1/sha1.c	Tue Dec 18 13:51:01 2012 -0500
@@ -32,13 +32,13 @@
  * and appreciated.
  */
 
-#ifndef _KERNEL
+#if !defined(_KERNEL) && !defined(_BOOT)
 #include <stdint.h>
 #include <strings.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/systeminfo.h>
-#endif  /* !_KERNEL */
+#endif  /* !_KERNEL && !_BOOT */
 
 #include <sys/types.h>
 #include <sys/param.h>
@@ -52,6 +52,11 @@
 #define	HAVE_HTONL
 #endif
 
+#ifdef	_BOOT
+#define	bcopy(_s, _d, _l)	((void) memcpy((_d), (_s), (_l)))
+#define	bzero(_m, _l)		((void) memset((_m), 0, (_l)))
+#endif
+
 static void Encode(uint8_t *, const uint32_t *, size_t);
 
 #if	defined(__sparc)
--- a/usr/src/uts/i86pc/Makefile.files	Sun Dec 09 20:12:10 2012 -0500
+++ b/usr/src/uts/i86pc/Makefile.files	Tue Dec 18 13:51:01 2012 -0500
@@ -173,6 +173,7 @@
 	memcpy.o		\
 	memset.o		\
 	muldiv.o		\
+	sha1.o			\
 	string.o		\
 	$(BOOT_DRIVER_OBJS)	\
 	$(DBOOT_OBJS_$(CLASS))
--- a/usr/src/uts/i86pc/Makefile.rules	Sun Dec 09 20:12:10 2012 -0500
+++ b/usr/src/uts/i86pc/Makefile.rules	Tue Dec 18 13:51:01 2012 -0500
@@ -242,6 +242,9 @@
 $(DBOOT_OBJS_DIR)/%.o:		$(UTSBASE)/intel/ia32/%.s
 	$(DBOOT_AS) -P -D_ASM $(DBOOT_DEFS) $(DBOOT_AS_INCL) -o $@ $<
 
+$(DBOOT_OBJS_DIR)/%.o:		$(COMMONBASE)/crypto/sha1/%.c
+	$(i386_CC) $(CERRWARN) -O $(DBOOT_DEFS) $(DBOOT_CC_INCL) -c -o $@ $<
+
 $(DBOOT_OBJS_DIR)/%.o:		$(COMMONBASE)/util/%.c
 	$(i386_CC) $(DBOOT_FLAGS) -O $(DBOOT_DEFS) $(DBOOT_CC_INCL) -c -o $@ $<
 
@@ -456,6 +459,9 @@
 $(DBOOT_LINTS_DIR)/%.ln: $(UTSBASE)/i86pc/dboot/%.s
 	@($(LHEAD) $(DBOOT_LINT) $(DBOOT_LOCAL_LINTFLAGS) $< $(LTAIL))
 
+$(DBOOT_LINTS_DIR)/%.ln: $(COMMONBASE)/crypto/sha1/%.c
+	@($(LHEAD) $(DBOOT_LINT) $(DBOOT_LOCAL_LINTFLAGS) $< $(LTAIL))
+
 $(DBOOT_LINTS_DIR)/%.ln: $(UTSBASE)/i86pc/boot/%.c
 	@($(LHEAD) $(DBOOT_LINT) $(DBOOT_LOCAL_LINTFLAGS) $< $(LTAIL))
 
--- a/usr/src/uts/i86pc/dboot/dboot_asm.s	Sun Dec 09 20:12:10 2012 -0500
+++ b/usr/src/uts/i86pc/dboot/dboot_asm.s	Tue Dec 18 13:51:01 2012 -0500
@@ -24,8 +24,6 @@
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <sys/asm_linkage.h>
 #include <sys/asm_misc.h>
 
@@ -87,6 +85,12 @@
 	ret
 	SET_SIZE(inb)
 
+	ENTRY(htonl)
+	movl    %edi, %eax
+	bswap   %eax
+	ret
+	SET_SIZE(htonl)
+
 #elif defined(__i386)
 
 	.code32
@@ -128,6 +132,12 @@
 	ret
 	SET_SIZE(inb)
 
+	ENTRY(htonl)
+	movl    4(%esp), %eax
+	bswap   %eax
+	ret
+	SET_SIZE(htonl)
+
 #endif	/* __i386 */
 
 #endif /* __lint */
--- a/usr/src/uts/i86pc/dboot/dboot_startkern.c	Sun Dec 09 20:12:10 2012 -0500
+++ b/usr/src/uts/i86pc/dboot/dboot_startkern.c	Tue Dec 18 13:51:01 2012 -0500
@@ -22,6 +22,8 @@
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ *
+ * Copyright 2012 Joyent, Inc.  All rights reserved.
  */
 
 
@@ -31,6 +33,7 @@
 #include <sys/systm.h>
 #include <sys/mach_mmu.h>
 #include <sys/multiboot.h>
+#include <sys/sha1.h>
 
 #if defined(__xpv)
 
@@ -55,6 +58,8 @@
 #include "dboot_xboot.h"
 #include "dboot_elfload.h"
 
+#define	SHA1_ASCII_LENGTH	(SHA1_DIGEST_LENGTH * 2)
+
 /*
  * This file contains code that runs to transition us from either a multiboot
  * compliant loader (32 bit non-paging) or a XPV domain loader to
@@ -767,6 +772,129 @@
 
 #else	/* !__xpv */
 
+static uint8_t
+dboot_a2h(char v)
+{
+	if (v >= 'a')
+		return (v - 'a' + 0xa);
+	else if (v >= 'A')
+		return (v - 'A' + 0xa);
+	else if (v >= '0')
+		return (v - '0');
+	else
+		dboot_panic("bad ASCII hex character %c\n", v);
+
+	return (0);
+}
+
+static void
+digest_a2h(const char *ascii, uint8_t *digest)
+{
+	unsigned int i;
+
+	for (i = 0; i < SHA1_DIGEST_LENGTH; i++) {
+		digest[i] = dboot_a2h(ascii[i * 2]) << 4;
+		digest[i] |= dboot_a2h(ascii[i * 2 + 1]);
+	}
+}
+
+/*
+ * Generate a SHA-1 hash of the first len bytes of image, and compare it with
+ * the ASCII-format hash found in the 40-byte buffer at ascii.  If they
+ * match, return 0, otherwise -1.  This works only for images smaller than
+ * 4 GB, which should not be a problem.
+ */
+static int
+check_image_hash(const char *ascii, const void *image, size_t len)
+{
+	SHA1_CTX ctx;
+	uint8_t digest[SHA1_DIGEST_LENGTH];
+	uint8_t baseline[SHA1_DIGEST_LENGTH];
+	unsigned int i;
+
+	digest_a2h(ascii, baseline);
+
+	SHA1Init(&ctx);
+	SHA1Update(&ctx, image, len);
+	SHA1Final(digest, &ctx);
+
+	for (i = 0; i < SHA1_DIGEST_LENGTH; i++) {
+		if (digest[i] != baseline[i])
+			return (-1);
+	}
+
+	return (0);
+}
+
+static void
+check_images(void)
+{
+	int i;
+	char *hashes;
+	mb_module_t *mod, *hashmod;
+	char *hash;
+	char displayhash[SHA1_ASCII_LENGTH + 1];
+	size_t hashlen;
+	size_t len;
+
+	/*
+	 * A brief note on lengths and sizes: GRUB, for reasons unknown, passes
+	 * the address of the last valid byte in a module plus 1 as mod_end.
+	 * This is of course a bug; the multiboot specification simply states
+	 * that mod_start and mod_end "contain the start and end addresses of
+	 * the boot module itself" which is pretty obviously not what GRUB is
+	 * doing.  However, fixing it requires that not only this code be
+	 * changed but also that other code consuming this value and values
+	 * derived from it be fixed, and that the kernel and GRUB must either
+	 * both have the bug or neither.  While there are a lot of combinations
+	 * that will work, there are also some that won't, so for simplicity
+	 * we'll just cope with the bug.  That means we won't actually hash the
+	 * byte at mod_end, and we will expect that mod_end for the hash file
+	 * itself is one greater than some multiple of 41 (40 bytes of ASCII
+	 * hash plus a newline for each module).
+	 */
+
+	if (mb_info->mods_count > 1) {
+		mod = (mb_module_t *)mb_info->mods_addr;
+		hashmod = mod + (mb_info->mods_count - 1);
+		hashes = (char *)hashmod->mod_start;
+		hashlen = (size_t)(hashmod->mod_end - hashmod->mod_start);
+		hash = hashes;
+		if (prom_debug) {
+			dboot_printf("Hash module found at %lx size %lx\n",
+			    (ulong_t)hashes, (ulong_t)hashlen);
+		}
+	} else {
+		DBG_MSG("Skipping hash check; no hash module found.\n");
+		return;
+	}
+
+	for (mod = (mb_module_t *)(mb_info->mods_addr), i = 0;
+	    i < mb_info->mods_count - 1; ++mod, ++i) {
+		if ((hash - hashes) + SHA1_ASCII_LENGTH + 1 > hashlen) {
+			dboot_printf("Short hash module of length 0x%lx bytes; "
+			    "skipping hash checks\n", (ulong_t)hashlen);
+			break;
+		}
+
+		(void) memcpy(displayhash, hash, SHA1_ASCII_LENGTH);
+		displayhash[SHA1_ASCII_LENGTH] = '\0';
+		if (prom_debug) {
+			dboot_printf("Checking hash for module %d [%s]: ",
+			    i, displayhash);
+		}
+
+		len = mod->mod_end - mod->mod_start;	/* see above */
+		if (check_image_hash(hash, (void *)mod->mod_start, len) != 0) {
+			dboot_panic("SHA-1 hash mismatch on %s; expected %s\n",
+			    (char *)mod->mod_name, displayhash);
+		} else {
+			DBG_MSG("OK\n");
+		}
+		hash += SHA1_ASCII_LENGTH + 1;
+	}
+}
+
 /*
  * During memory allocation, find the highest address not used yet.
  */
@@ -813,7 +941,7 @@
 	    i < mb_info->mods_count;
 	    ++mod, ++i) {
 		if (prom_debug) {
-			dboot_printf("\tmodule #%d: %s at: 0x%lx, len 0x%lx\n",
+			dboot_printf("\tmodule #%d: %s at: 0x%lx, end 0x%lx\n",
 			    i, (char *)(mod->mod_name),
 			    (ulong_t)mod->mod_start, (ulong_t)mod->mod_end);
 		}
@@ -831,6 +959,8 @@
 	bi->bi_module_cnt = mb_info->mods_count;
 	DBG(bi->bi_module_cnt);
 
+	check_images();
+
 	/*
 	 * Walk through the memory map from multiboot and build our memlist
 	 * structures. Note these will have native format pointers.