Print this page
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>

@@ -20,19 +20,22 @@
  */
 
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ *
+ * Copyright 2012 Joyent, Inc.  All rights reserved.
  */
 
 
 #include <sys/types.h>
 #include <sys/machparam.h>
 #include <sys/x86_archext.h>
 #include <sys/systm.h>
 #include <sys/mach_mmu.h>
 #include <sys/multiboot.h>
+#include <sys/sha1.h>
 
 #if defined(__xpv)
 
 #include <sys/hypervisor.h>
 uintptr_t xen_virt_start;

@@ -53,10 +56,12 @@
 #include "dboot_asm.h"
 #include "dboot_printf.h"
 #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
  * regular kernel execution. Its task is to setup the kernel memory image
  * and page tables.

@@ -765,11 +770,134 @@
         }
 }
 
 #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.
  */
 static void
 check_higher(paddr_t a)
 {

@@ -811,11 +939,11 @@
         check_higher((paddr_t)(uintptr_t)&_end);
         for (mod = (mb_module_t *)(mb_info->mods_addr), i = 0;
             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);
                 }
                 modules[i].bm_addr = mod->mod_start;
                 if (mod->mod_start > mod->mod_end) {

@@ -829,10 +957,12 @@
         bi->bi_modules = (native_ptr_t)(uintptr_t)modules;
         DBG(bi->bi_modules);
         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.
          */
         DBG_MSG("\nFinding Memory Map\n");