mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-17 10:42:48 +00:00

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.
206 lines
5.0 KiB
C
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);
|
|
}
|