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,38 **** --- 20,41 ---- */ /* * 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,62 **** --- 56,67 ---- #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,775 **** --- 770,903 ---- } } #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,821 **** 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", 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) { --- 939,949 ---- 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, 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,838 **** --- 957,968 ---- 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");