This patch adds support for hashing the kernel and initrd as well as the command line parameters in the case that Qemu was provided the -kernel, -initrd and -apppend command line parameters. The hashes are then passed to SeaBIOS for logging. Typically SeaBIOS would take those measurements (hashing) but in the case Qemu gets these command line parameters, Qemu does not see the kernel file in its unmodified form anymore (it is modified before it is passed to the firmware interface). Support for measuring multiboot kernel entries is also added.
This patch relies on the existing firmware mechanism to pass byte arrays from Qemu to a BIOS, i.e., SeaBIOS. It introduces structures describing the header and the following content consisting of an array of structures that hold the measurements and descriptions of the above mentioned items. Since hashing requires a sha1 algorithm to be available to Qemu, this patch introduces a dependency on the freebl library for the sha1 algorithm. The code for accessing the freebl library's sha1 function has been isolated into its own file and wrapped with the function call qemu_sha1. Attempts to use the freebl library's SHA1 function directly didn't work due to clashes of datatypes with matching names defined by freebl and Qemu. Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com> --- Makefile.target | 2 - configure | 23 +++++++++++ hw/fw_cfg.h | 2 + hw/pc.c | 10 +++++ sha1.c | 19 +++++++++ sha1.h | 9 ++++ tpm.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tpm.h | 31 ++++++++++++++++ 8 files changed, 203 insertions(+), 1 deletion(-) Index: qemu-git/hw/pc.c =================================================================== --- qemu-git.orig/hw/pc.c +++ qemu-git/hw/pc.c @@ -677,6 +677,9 @@ static void load_linux(void *fw_cfg, exit(1); } + tpm_measure_start(); + tpm_measure_file(kernel_filename, TPM_MSR_TYPE_KERNEL, 8); + /* kernel protocol version */ #if 0 fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202)); @@ -734,6 +737,9 @@ static void load_linux(void *fw_cfg, (uint8_t*)strdup(kernel_cmdline), strlen(kernel_cmdline)+1); + tpm_measure_buffer(kernel_cmdline, strlen(kernel_cmdline), + TPM_MSR_TYPE_KERNEL_CMDLINE, 8, NULL, 0); + if (protocol >= 0x202) { stl_p(header+0x228, cmdline_addr); } else { @@ -795,9 +801,13 @@ static void load_linux(void *fw_cfg, fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size); fw_cfg_add_bytes(fw_cfg, FW_CFG_INITRD_DATA, initrd_data, initrd_size); + tpm_measure_buffer(initrd_data, initrd_size, TPM_MSR_TYPE_INITRD, 8, + initrd_filename, strlen(initrd_filename) + 1); + stl_p(header+0x218, initrd_addr); stl_p(header+0x21c, initrd_size); } + tpm_measure_end(fw_cfg); /* load kernel and setup */ setup_size = header[0x1f1]; Index: qemu-git/tpm.h =================================================================== --- qemu-git.orig/tpm.h +++ qemu-git/tpm.h @@ -1,6 +1,9 @@ #ifndef _HW_TPM_CONFIG_H #define _HW_TPM_CONFIG_H +#include "sysemu.h" +#include "hw/fw_cfg.h" + struct TPMState; typedef struct TPMState TPMState; @@ -108,6 +111,34 @@ void do_info_tpm(Monitor *mon); void tpm_display_backend_drivers(FILE *out); const TPMDriverOps *tpm_get_backend_driver(const char *id); +typedef enum TPMMeasureType { + TPM_MSR_TYPE_KERNEL_CMDLINE = 0x1105, + TPM_MSR_TYPE_KERNEL = 0x1205, + TPM_MSR_TYPE_INITRD = 0x1305, +} TPMMeasureType; + +typedef struct TPMMsrHdr { + uint16_t rev; + uint32_t totlen; + uint16_t numTPMMsrEntries; +} __attribute__((packed)) TPMMsrHdr; + +typedef struct TPMMsrEntry { + uint32_t len; + uint32_t pcrindex; + uint32_t type; + uint8_t digest[20]; + uint32_t eventdatasize; + uint32_t event; +} __attribute__((packed)) TPMMsrEntry; + +void tpm_measure_start(void); +void tpm_measure_end(FWCfgState *s); +void tpm_measure_file(const char *, TPMMeasureType type, uint8_t pcrindex); +void tpm_measure_buffer(const void *buffer, long length, + TPMMeasureType type, uint8_t pcrindex, + const void *data, uint32_t data_len); + extern TPMDriverOps tpm_builtin; #endif /* _HW_TPM_CONFIG_H */ Index: qemu-git/tpm.c =================================================================== --- qemu-git.orig/tpm.c +++ qemu-git/tpm.c @@ -14,6 +14,9 @@ #include "tpm.h" #include "monitor.h" #include "qerror.h" +#include "sha1.h" +#include "hw/loader.h" +#include "bswap.h" #ifdef CONFIG_TPM @@ -267,6 +270,88 @@ void tpm_config_parse(QemuOptsList *opts } } +static TPMMsrHdr *tpm_measurements; + +void tpm_measure_start(void) +{ + if (!tpm_measurements) { + tpm_measurements = qemu_mallocz(sizeof(TPMMsrHdr)); + tpm_measurements->rev = 1; + tpm_measurements->totlen = sizeof(TPMMsrHdr); + } +} + +void tpm_measure_end(FWCfgState *s) +{ + uint32_t totlen = tpm_measurements->totlen; + /* fix endianess */ + tpm_measurements->rev = cpu_to_le16(tpm_measurements->rev); + tpm_measurements->totlen = cpu_to_le32(totlen); + tpm_measurements->numTPMMsrEntries = + cpu_to_le16(tpm_measurements->numTPMMsrEntries); + + fw_cfg_add_i32(s, FW_CFG_TPM_MEASURE_SIZE, totlen); + fw_cfg_add_bytes(s, FW_CFG_TPM_MEASURE_DATA, + (unsigned char *)tpm_measurements, totlen); +} + +static void tpm_measure_add_hash(unsigned char digest[20], TPMMeasureType type, + uint8_t pcrindex, + const void *data, uint32_t len) +{ + TPMMsrEntry *entry; + + if (tpm_measurements) { + uint32_t entry_len = sizeof(TPMMsrEntry) + len; + tpm_measurements = qemu_realloc(tpm_measurements, + tpm_measurements->totlen + + entry_len); + if (tpm_measurements) { + entry = (void *)tpm_measurements + tpm_measurements->totlen; + + tpm_measurements->totlen += entry_len; + tpm_measurements->numTPMMsrEntries++; + + entry->len = cpu_to_le32(entry_len); + entry->pcrindex = cpu_to_le32(pcrindex); + entry->type = cpu_to_le32(type); + memcpy(entry->digest, digest, sizeof(entry->digest)); + entry->eventdatasize = cpu_to_le32(len); + if (len) { + memcpy(&entry->event, data, len); + } + } + } +} + +void tpm_measure_buffer(const void *buffer, long len, + TPMMeasureType type, uint8_t pcrindex, + const void *data, uint32_t data_len) +{ + unsigned char hash[20]; + + qemu_sha1(hash, buffer, len); + + tpm_measure_add_hash(hash, type, pcrindex, data, data_len); +} + +void tpm_measure_file(const char *filename, TPMMeasureType type, + uint8_t pcrindex) +{ + int len = get_image_size(filename); + + if (len > 0) { + uint8_t *buffer = qemu_malloc(len); + if (buffer) { + if (load_image(filename, buffer) == len) { + tpm_measure_buffer(buffer, len, type, pcrindex, + filename, strlen(filename) + 1); + } + qemu_free(buffer); + } + } +} + # else /* TARGET_I386 || TARGET_X86_64 */ void tpm_config_parse(QemuOptsList *opts_list, const char *optarg) @@ -287,5 +372,28 @@ void do_info_tpm(Monitor *mon) monitor_printf(mon, "TPM support: not compiled\n"); } + # endif + +#else /* ! CONFIG_TPM */ + +void tpm_measure_start(void) +{ +} + +void tpm_measure_end(FWCfgState *s) +{ +} + +void tpm_measure_buffer(const void *buffer, long len, + TPMMeasureType type, uint8_t pcrindex, + const void *data, uint32_t data_len) +{ +} + +void tpm_measure_file(const char *filename, TPMMeasureType type, + uint8_t pcrindex) +{ +} + #endif /* CONFIG_TPM */ Index: qemu-git/hw/fw_cfg.h =================================================================== --- qemu-git.orig/hw/fw_cfg.h +++ qemu-git/hw/fw_cfg.h @@ -27,6 +27,8 @@ #define FW_CFG_SETUP_SIZE 0x17 #define FW_CFG_SETUP_DATA 0x18 #define FW_CFG_FILE_DIR 0x19 +#define FW_CFG_TPM_MEASURE_SIZE 0x1a +#define FW_CFG_TPM_MEASURE_DATA 0x1b #define FW_CFG_FILE_FIRST 0x20 #define FW_CFG_FILE_SLOTS 0x10 Index: qemu-git/Makefile.target =================================================================== --- qemu-git.orig/Makefile.target +++ qemu-git/Makefile.target @@ -241,7 +241,7 @@ obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o obj-i386-$(CONFIG_KVM) += kvmclock.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o -obj-i386-$(CONFIG_TPM) += tpm_tis.o +obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o ifdef CONFIG_TPM_BUILTIN Index: qemu-git/sha1.c =================================================================== --- /dev/null +++ qemu-git/sha1.c @@ -0,0 +1,19 @@ +/* + * SHA1 Freebl wrapper + * + * Copyright (C) 2011 IBM Corporation + * Copyright (C) 2011 Stefan Berger + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "sha1.h" + +#include <nss3/blapi.h> + +int qemu_sha1(unsigned char hash[20], const unsigned char *data, uint32_t len) +{ + return SHA1_HashBuf(hash, data, len); +} Index: qemu-git/sha1.h =================================================================== --- /dev/null +++ qemu-git/sha1.h @@ -0,0 +1,9 @@ +#ifndef __SHA1_H +#define __SHA1_H + +#include <stdint.h> + +int qemu_sha1(unsigned char hash[20], const unsigned char *data, + uint32_t length); + +#endif /* __SHA1_H */ Index: qemu-git/configure =================================================================== --- qemu-git.orig/configure +++ qemu-git/configure @@ -2471,6 +2471,29 @@ fi # libtpms probe if test "$tpm" = "yes" ; then + if $pkg_config --atleast-version=3.12.8 nss-softokn >/dev/null 2>&1 ; then + tpmsupport_cflags=$($pkg_config --cflags nss-softokn 2>/dev/null) + tpmsupport_libs="-lfreebl -lnspr4 -lnssutil3" + QEMU_CFLAGS="$QEMU_CFLAGS $tpmsupport_cflags" + LIBS="$LIBS $tpmsupport_libs" + else + feature_not_found "nss-softokn" + fi + + # Check for nss-softokn-freebl-devel + cat > $TMPC <<EOF +#include <blapi.h> +int main(void) { + unsigned char hash[20]; + char src[1]; + return (int)SHA1_Hash(hash, src); +} +EOF + + if ! compile_prog "" "$tpmsupport_libs" ; then + feature_not_found "nss-softokn-freebl-devel" + fi + cat > $TMPC <<EOF #include <libtpms/tpm_library.h> int main(void) { return (int)TPMLIB_GetVersion(); }