fwupd/plugins/uefi/fu-uefi-pcrs.c
Richard Hughes b56015ed3f uefi: Add the PCR0 value as the device checksum for system firmware
We can't actually access the UEFI ROM from userspace, but the PCR0 is a hash
built from the ROM itself. We could use this value to ensure the firmware has
been written correctly, and that the PCR0 matches the expected value specified
in the metadata.
2018-12-14 19:23:04 +00:00

206 lines
5.0 KiB
C

/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-common.h"
#include "fu-uefi-pcrs.h"
typedef struct {
guint idx;
gchar *checksum;
} FuUefiPcrItem;
struct _FuUefiPcrs {
GObject parent_instance;
GPtrArray *items; /* of FuUefiPcrItem */
};
G_DEFINE_TYPE (FuUefiPcrs, fu_uefi_pcrs, G_TYPE_OBJECT)
static void
fu_uefi_pcrs_parse_line (const gchar *line, gpointer user_data)
{
FuUefiPcrs *self = FU_UEFI_PCRS (user_data);
FuUefiPcrItem *item;
guint64 idx;
g_autofree gchar *idxstr = NULL;
g_auto(GStrv) split = NULL;
g_autoptr(GString) str = NULL;
/* split into index:hash */
if (line == NULL || line[0] == '\0')
return;
split = g_strsplit (line, ":", 2);
if (g_strv_length (split) != 2) {
g_debug ("unexpected format, skipping: %s", line);
return;
}
/* get index */
idxstr = fu_common_strstrip (split[0]);
idx = fu_common_strtoull (idxstr);
if (idx > 64) {
g_debug ("unexpected index, skipping: %s", idxstr);
return;
}
/* parse hash */
str = g_string_new (split[1]);
if (str->len < 16)
return;
fu_common_string_replace (str, " ", "");
g_string_ascii_down (str);
item = g_new0 (FuUefiPcrItem, 1);
item->idx = idx;
item->checksum = g_string_free (g_steal_pointer (&str), FALSE);
g_ptr_array_add (self->items, item);
g_debug ("added PCR-%02u=%s", item->idx, item->checksum);
}
static gboolean
fu_uefi_pcrs_setup_dummy (FuUefiPcrs *self, const gchar *test_yaml, GError **error)
{
g_auto(GStrv) lines = g_strsplit (test_yaml, "\n", -1);
for (guint i = 0; lines[i] != NULL; i++)
fu_uefi_pcrs_parse_line (lines[i], self);
return TRUE;
}
static gboolean
fu_uefi_pcrs_setup_tpm12 (FuUefiPcrs *self, const gchar *fn_pcrs, GError **error)
{
g_auto(GStrv) lines = NULL;
g_autofree gchar *buf_pcrs = NULL;
/* get entire contents */
if (!g_file_get_contents (fn_pcrs, &buf_pcrs, NULL, error))
return FALSE;
/* find PCR lines */
lines = g_strsplit (buf_pcrs, "\n", -1);
for (guint i = 0; lines[i] != NULL; i++) {
if (g_str_has_prefix (lines[i], "PCR-"))
fu_uefi_pcrs_parse_line (lines[i] + 4, self);
}
return TRUE;
}
static gboolean
fu_uefi_pcrs_setup_tpm20 (FuUefiPcrs *self, const gchar *argv0, GError **error)
{
const gchar *argv[] = { argv0, NULL };
return fu_common_spawn_sync (argv, fu_uefi_pcrs_parse_line, self, NULL, error);
}
gboolean
fu_uefi_pcrs_setup (FuUefiPcrs *self, GError **error)
{
g_autofree gchar *devpath = NULL;
g_autofree gchar *sysfstpmdir = NULL;
g_autofree gchar *fn_pcrs = NULL;
const gchar *test_yaml = g_getenv ("FWUPD_UEFI_TPM2_YAML_DATA");
g_return_val_if_fail (FU_IS_UEFI_PCRS (self), FALSE);
/* check the TPM device exists at all */
sysfstpmdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_TPM);
devpath = g_build_filename (sysfstpmdir, "tpm0", NULL);
if (!g_file_test (devpath, G_FILE_TEST_EXISTS)) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no TPM device found");
return FALSE;
}
fn_pcrs = g_build_filename (devpath, "pcrs", NULL);
/* fake device */
if (test_yaml != NULL) {
if (!fu_uefi_pcrs_setup_dummy (self, test_yaml, error))
return FALSE;
/* look for TPM 1.2 */
} else if (g_file_test (fn_pcrs, G_FILE_TEST_EXISTS)) {
if (!fu_uefi_pcrs_setup_tpm12 (self, fn_pcrs, error))
return FALSE;
/* assume TPM 2.0 */
} else {
g_autofree gchar *argv0 = NULL;
/* old name, then new name */
argv0 = fu_common_find_program_in_path ("tpm2_listpcrs", NULL);
if (argv0 == NULL)
argv0 = fu_common_find_program_in_path ("tpm2_pcrlist", error);
if (argv0 == NULL)
return FALSE;
if (!fu_uefi_pcrs_setup_tpm20 (self, argv0, error))
return FALSE;
}
/* check we got anything */
if (self->items->len == 0) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no TPMxx measurements found");
return FALSE;
}
/* success */
return TRUE;
}
GPtrArray *
fu_uefi_pcrs_get_checksums (FuUefiPcrs *self, guint idx)
{
g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free);
g_return_val_if_fail (FU_IS_UEFI_PCRS (self), NULL);
for (guint i = 0; i < self->items->len; i++) {
FuUefiPcrItem *item = g_ptr_array_index (self->items, i);
if (item->idx == idx)
g_ptr_array_add (array, g_strdup (item->checksum));
}
return g_steal_pointer (&array);
}
static void
fu_uefi_pcrs_item_free (FuUefiPcrItem *item)
{
g_free (item->checksum);
g_free (item);
}
static void
fu_uefi_pcrs_finalize (GObject *object)
{
FuUefiPcrs *self = FU_UEFI_PCRS (object);
g_ptr_array_unref (self->items);
G_OBJECT_CLASS (fu_uefi_pcrs_parent_class)->finalize (object);
}
static void
fu_uefi_pcrs_class_init (FuUefiPcrsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_uefi_pcrs_finalize;
}
static void
fu_uefi_pcrs_init (FuUefiPcrs *self)
{
self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_uefi_pcrs_item_free);
}
FuUefiPcrs *
fu_uefi_pcrs_new (void)
{
FuUefiPcrs *self;
self = g_object_new (FU_TYPE_UEFI_PCRS, NULL);
return FU_UEFI_PCRS (self);
}