fwupd/plugins/uefi/fu-uefi-pcrs.c
Richard Hughes b768e4d924 Do not fail to start the daemon if tpm2_pcrlist hangs
In some situations SELinux prevents fwupd from executing tpm2_pcrlist, but the
failure mode is that the process just hangs and never completes. This causes
systemd to time out the fwupd daemon startup and then errors to be shown in
GNOME Software.

To prevent this happening, add an optional timeout argument to
fu_common_spawn_sync() and cancel the subprocess if it takes longer than this
to complete.

See https://bugzilla.redhat.com/show_bug.cgi?id=1665701 for details.
2019-02-26 14:27:13 +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, 1500, 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);
}