Move the TPM handling into the TPM place

The fact that it currently lives in uefi-capsule is a historical
accident, and it doesn't really belong to tpm-eventlog either.

Fixes some of https://github.com/fwupd/fwupd/issues/3901
This commit is contained in:
Richard Hughes 2021-10-30 14:59:26 +01:00
parent 23b1d20294
commit e29c74180c
19 changed files with 772 additions and 671 deletions

View File

@ -8,10 +8,12 @@
#include <fwupdplugin.h>
#include "fu-tpm-device.h"
#include "fu-tpm-v1-device.h"
#include "fu-tpm-v2-device.h"
struct FuPluginData {
FuDevice *tpm_device;
FuTpmDevice *tpm_device;
FuDevice *bios_device;
gboolean has_tpm_v20;
};
@ -21,7 +23,7 @@ fu_plugin_init(FuPlugin *plugin)
fu_plugin_alloc_data(plugin, sizeof(FuPluginData));
fu_plugin_set_build_hash(plugin, FU_BUILD_HASH);
fu_plugin_add_udev_subsystem(plugin, "tpm");
fu_plugin_add_device_gtype(plugin, FU_TYPE_TPM_DEVICE);
fu_plugin_add_device_gtype(plugin, FU_TYPE_TPM_V2_DEVICE);
}
void
@ -30,6 +32,42 @@ fu_plugin_destroy(FuPlugin *plugin)
FuPluginData *data = fu_plugin_get_data(plugin);
if (data->tpm_device != NULL)
g_object_unref(data->tpm_device);
if (data->bios_device != NULL)
g_object_unref(data->bios_device);
}
static void
fu_plugin_tpm_set_bios_pcr0s(FuPlugin *plugin)
{
FuPluginData *data = fu_plugin_get_data(plugin);
g_autoptr(GError) error_local = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
if (data->tpm_device == NULL)
return;
if (data->bios_device == NULL)
return;
/* get all the PCR0s */
pcr0s = fu_tpm_device_get_checksums(data->tpm_device, 0);
if (pcr0s->len == 0)
return;
for (guint i = 0; i < pcr0s->len; i++) {
const gchar *checksum = g_ptr_array_index(pcr0s, i);
fu_device_add_checksum(data->bios_device, checksum);
}
fu_device_add_flag(data->bios_device, FWUPD_DEVICE_FLAG_CAN_VERIFY);
}
/* set the PCR0 as the device checksum */
void
fu_plugin_device_registered(FuPlugin *plugin, FuDevice *device)
{
FuPluginData *data = fu_plugin_get_data(plugin);
if (fu_device_has_instance_id(device, "main-system-firmware")) {
g_set_object(&data->bios_device, device);
fu_plugin_tpm_set_bios_pcr0s(plugin);
}
}
void
@ -38,10 +76,13 @@ fu_plugin_device_added(FuPlugin *plugin, FuDevice *dev)
FuPluginData *data = fu_plugin_get_data(plugin);
const gchar *family = fu_tpm_device_get_family(FU_TPM_DEVICE(dev));
g_set_object(&data->tpm_device, dev);
g_set_object(&data->tpm_device, FU_TPM_DEVICE(dev));
if (g_strcmp0(family, "2.0") == 0)
data->has_tpm_v20 = TRUE;
fu_plugin_add_report_metadata(plugin, "TpmFamily", family);
/* ensure */
fu_plugin_tpm_set_bios_pcr0s(plugin);
}
void
@ -67,7 +108,28 @@ fu_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs)
}
/* success */
fwupd_security_attr_add_guids(attr, fu_device_get_guids(data->tpm_device));
fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(data->tpm_device)));
fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS);
fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_FOUND);
}
gboolean
fu_plugin_startup(FuPlugin *plugin, GError **error)
{
FuPluginData *data = fu_plugin_get_data(plugin);
g_autofree gchar *sysfstpmdir = NULL;
g_autofree gchar *fn_pcrs = NULL;
/* look for TPM v1.2 */
sysfstpmdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_TPM);
fn_pcrs = g_build_filename(sysfstpmdir, "tmp0", "pcrs", NULL);
if (g_file_test(fn_pcrs, G_FILE_TEST_EXISTS) && g_getenv("FWUPD_FORCE_TPM2") == NULL) {
data->tpm_device = fu_tpm_v1_device_new(fu_plugin_get_context(plugin));
g_object_set(data->tpm_device, "device-file", fn_pcrs, NULL);
if (!fu_device_probe(FU_DEVICE(data->tpm_device), error))
return FALSE;
}
/* success */
return TRUE;
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-context-private.h"
#include "fu-tpm-v1-device.h"
#include "fu-tpm-v2-device.h"
static void
fu_tpm_device_1_2_func(void)
{
gboolean ret;
g_autoptr(FuContext) ctx = fu_context_new();
g_autoptr(FuTpmDevice) device = fu_tpm_v1_device_new(ctx);
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
g_autoptr(GPtrArray) pcrXs = NULL;
g_autofree gchar *testdatadir = NULL;
testdatadir = g_test_build_filename(G_TEST_DIST, "tests", "tpm0", "pcrs", NULL);
g_object_set(device, "device-file", testdatadir, NULL);
ret = fu_device_setup(FU_DEVICE(device), &error);
g_assert_no_error(error);
g_assert_true(ret);
pcr0s = fu_tpm_device_get_checksums(device, 0);
g_assert_nonnull(pcr0s);
g_assert_cmpint(pcr0s->len, ==, 1);
pcrXs = fu_tpm_device_get_checksums(device, 999);
g_assert_nonnull(pcrXs);
g_assert_cmpint(pcrXs->len, ==, 0);
}
static void
fu_tpm_device_2_0_func(void)
{
g_autoptr(FuContext) ctx = fu_context_new();
g_autoptr(FuTpmDevice) device = fu_tpm_v2_device_new(ctx);
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
g_autoptr(GPtrArray) pcrXs = NULL;
const gchar *tpm_server_running = g_getenv("TPM_SERVER_RUNNING");
g_setenv("FWUPD_FORCE_TPM2", "1", TRUE);
#ifdef HAVE_GETUID
if (tpm_server_running == NULL && (getuid() != 0 || geteuid() != 0)) {
g_test_skip("TPM2.0 tests require simulated TPM2.0 running or need root access "
"with physical TPM");
return;
}
#endif
if (!fu_device_setup(FU_DEVICE(device), &error)) {
if (tpm_server_running == NULL &&
g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
g_test_skip("no physical or simulated TPM 2.0 device available");
return;
}
}
g_assert_no_error(error);
pcr0s = fu_tpm_device_get_checksums(device, 0);
g_assert_nonnull(pcr0s);
g_assert_cmpint(pcr0s->len, >=, 1);
pcrXs = fu_tpm_device_get_checksums(device, 999);
g_assert_nonnull(pcrXs);
g_assert_cmpint(pcrXs->len, ==, 0);
g_unsetenv("FWUPD_FORCE_TPM2");
}
int
main(int argc, char **argv)
{
g_autofree gchar *testdatadir = NULL;
g_test_init(&argc, &argv, NULL);
testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL);
g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE);
g_setenv("FWUPD_SYSFSDRIVERDIR", testdatadir, TRUE);
g_setenv("FWUPD_UEFI_TEST", "1", TRUE);
/* only critical and error are fatal */
g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
g_setenv("G_MESSAGES_DEBUG", "all", TRUE);
/* tests go here */
g_test_add_func("/tpm/pcrs1.2", fu_tpm_device_1_2_func);
g_test_add_func("/tpm/pcrs2.0", fu_tpm_device_2_0_func);
return g_test_run();
}

View File

@ -6,264 +6,102 @@
#include "config.h"
#include <tss2/tss2_esys.h>
#include "fu-tpm-device.h"
struct _FuTpmDevice {
FuUdevDevice parent_instance;
typedef struct {
guint idx;
gchar *checksum;
} FuTpmDevicePcrItem;
typedef struct {
gchar *family;
};
GPtrArray *items; /* of FuTpmDevicePcrItem */
} FuTpmDevicePrivate;
G_DEFINE_TYPE(FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_DEVICE)
G_DEFINE_TYPE_WITH_PRIVATE(FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_DEVICE)
static void
Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context)
#define GET_PRIVATE(o) (fu_tpm_device_get_instance_private(o))
void
fu_tpm_device_set_family(FuTpmDevice *self, const gchar *family)
{
Esys_Finalize(&esys_context);
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_TPM_DEVICE(self));
priv->family = g_strdup(family);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup)
const gchar *
fu_tpm_device_get_family(FuTpmDevice *self)
{
return self->family;
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_TPM_DEVICE(self), NULL);
return priv->family;
}
static gboolean
fu_tpm_device_probe(FuDevice *device, GError **error)
void
fu_tpm_device_add_checksum(FuTpmDevice *self, guint idx, const gchar *checksum)
{
/* FuUdevDevice->probe */
if (!FU_DEVICE_CLASS(fu_tpm_device_parent_class)->probe(device, error))
return FALSE;
return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "tpm", error);
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
FuTpmDevicePcrItem *item = g_new0(FuTpmDevicePcrItem, 1);
g_return_if_fail(FU_IS_TPM_DEVICE(self));
g_return_if_fail(checksum != NULL);
item->idx = idx;
item->checksum = g_strdup(checksum);
g_debug("added PCR-%02u=%s", item->idx, item->checksum);
g_ptr_array_add(priv->items, item);
}
static gboolean
fu_tpm_device_get_uint32(ESYS_CONTEXT *ctx, guint32 query, guint32 *val, GError **error)
GPtrArray *
fu_tpm_device_get_checksums(FuTpmDevice *self, guint idx)
{
TSS2_RC rc;
g_autofree TPMS_CAPABILITY_DATA *capability = NULL;
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free);
g_return_val_if_fail(val != NULL, FALSE);
g_return_val_if_fail(FU_IS_TPM_DEVICE(self), NULL);
rc = Esys_GetCapability(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
TPM2_CAP_TPM_PROPERTIES,
query,
1,
NULL,
&capability);
if (rc != TSS2_RC_SUCCESS) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"capability request failed for query %x",
query);
return FALSE;
for (guint i = 0; i < priv->items->len; i++) {
FuTpmDevicePcrItem *item = g_ptr_array_index(priv->items, i);
if (item->idx == idx)
g_ptr_array_add(array, g_strdup(item->checksum));
}
if (capability->data.tpmProperties.count == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no properties returned for query %x",
query);
return FALSE;
}
if (capability->data.tpmProperties.tpmProperty[0].property != query) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"wrong query returned (got %x expected %x)",
capability->data.tpmProperties.tpmProperty[0].property,
query);
return FALSE;
return g_steal_pointer(&array);
}
*val = capability->data.tpmProperties.tpmProperty[0].value;
return TRUE;
}
static gchar *
fu_tpm_device_get_string(ESYS_CONTEXT *ctx, guint32 query, GError **error)
{
guint32 val_be = 0;
guint32 val;
gchar result[5] = {'\0'};
/* return four bytes */
if (!fu_tpm_device_get_uint32(ctx, query, &val_be, error))
return NULL;
val = GUINT32_FROM_BE(val_be);
memcpy(result, (gchar *)&val, 4);
/* convert non-ASCII into spaces */
for (guint i = 0; i < 4; i++) {
if (!g_ascii_isgraph(result[i]))
result[i] = 0x20;
}
return fu_common_strstrip(result);
}
/* taken from TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf */
static const gchar *
fu_tpm_device_convert_manufacturer(const gchar *manufacturer)
{
if (g_strcmp0(manufacturer, "AMD") == 0)
return "AMD";
if (g_strcmp0(manufacturer, "ATML") == 0)
return "Atmel";
if (g_strcmp0(manufacturer, "BRCM") == 0)
return "Broadcom";
if (g_strcmp0(manufacturer, "HPE") == 0)
return "HPE";
if (g_strcmp0(manufacturer, "IBM") == 0)
return "IBM";
if (g_strcmp0(manufacturer, "IFX") == 0)
return "Infineon";
if (g_strcmp0(manufacturer, "INTC") == 0)
return "Intel";
if (g_strcmp0(manufacturer, "LEN") == 0)
return "Lenovo";
if (g_strcmp0(manufacturer, "MSFT") == 0)
return "Microsoft";
if (g_strcmp0(manufacturer, "NSM") == 0)
return "National Semiconductor";
if (g_strcmp0(manufacturer, "NTZ") == 0)
return "Nationz";
if (g_strcmp0(manufacturer, "NTC") == 0)
return "Nuvoton Technology";
if (g_strcmp0(manufacturer, "QCOM") == 0)
return "Qualcomm";
if (g_strcmp0(manufacturer, "SMSC") == 0)
return "SMSC";
if (g_strcmp0(manufacturer, "STM") == 0)
return "ST Microelectronics";
if (g_strcmp0(manufacturer, "SMSN") == 0)
return "Samsung";
if (g_strcmp0(manufacturer, "SNS") == 0)
return "Sinosun";
if (g_strcmp0(manufacturer, "TXN") == 0)
return "Texas Instruments";
if (g_strcmp0(manufacturer, "WEC") == 0)
return "Winbond";
if (g_strcmp0(manufacturer, "ROCC") == 0)
return "Fuzhou Rockchip";
if (g_strcmp0(manufacturer, "GOOG") == 0)
return "Google";
return NULL;
}
static gboolean
fu_tpm_device_setup(FuDevice *device, GError **error)
static void
fu_tpm_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuTpmDevice *self = FU_TPM_DEVICE(device);
FwupdVersionFormat verfmt;
TSS2_RC rc;
const gchar *tmp;
guint32 tpm_type = 0;
guint32 version1 = 0;
guint32 version2 = 0;
guint64 version_raw;
g_autofree gchar *id1 = NULL;
g_autofree gchar *id2 = NULL;
g_autofree gchar *id3 = NULL;
g_autofree gchar *id4 = NULL;
g_autofree gchar *manufacturer = NULL;
g_autofree gchar *model1 = NULL;
g_autofree gchar *model2 = NULL;
g_autofree gchar *model3 = NULL;
g_autofree gchar *model4 = NULL;
g_autofree gchar *model = NULL;
g_autofree gchar *vendor_id = NULL;
g_autofree gchar *version = NULL;
g_autoptr(ESYS_CONTEXT) ctx = NULL;
/* setup TSS */
rc = Esys_Initialize(&ctx, NULL, NULL);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"failed to initialize TPM library");
return FALSE;
}
rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to initialize TPM");
return FALSE;
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
if (priv->family != NULL)
fu_common_string_append_kv(str, idt, "Family", priv->family);
}
/* lookup guaranteed details from TPM */
self->family = fu_tpm_device_get_string(ctx, TPM2_PT_FAMILY_INDICATOR, error);
if (self->family == NULL) {
g_prefix_error(error, "failed to read TPM family: ");
return FALSE;
}
manufacturer = fu_tpm_device_get_string(ctx, TPM2_PT_MANUFACTURER, error);
if (manufacturer == NULL) {
g_prefix_error(error, "failed to read TPM manufacturer: ");
return FALSE;
}
model1 = fu_tpm_device_get_string(ctx, TPM2_PT_VENDOR_STRING_1, error);
if (model1 == NULL) {
g_prefix_error(error, "failed to read TPM vendor string: ");
return FALSE;
}
if (!fu_tpm_device_get_uint32(ctx, TPM2_PT_VENDOR_TPM_TYPE, &tpm_type, error)) {
g_prefix_error(error, "failed to read TPM type: ");
return FALSE;
static void
fu_tpm_v2_device_item_free(FuTpmDevicePcrItem *item)
{
g_free(item->checksum);
g_free(item);
}
/* these are not guaranteed by spec and may be NULL */
model2 = fu_tpm_device_get_string(ctx, TPM2_PT_VENDOR_STRING_2, error);
model3 = fu_tpm_device_get_string(ctx, TPM2_PT_VENDOR_STRING_3, error);
model4 = fu_tpm_device_get_string(ctx, TPM2_PT_VENDOR_STRING_4, error);
model = g_strjoin("", model1, model2, model3, model4, NULL);
static void
fu_tpm_device_finalize(GObject *object)
{
FuTpmDevice *self = FU_TPM_DEVICE(object);
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
/* add GUIDs to daemon */
id1 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X", manufacturer, tpm_type);
fu_device_add_instance_id(device, id1);
id2 = g_strdup_printf("TPM\\VEN_%s&MOD_%s", manufacturer, model);
fu_device_add_instance_id(device, id2);
id3 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X&VER_%s", manufacturer, tpm_type, self->family);
fu_device_add_instance_id(device, id3);
id4 = g_strdup_printf("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, self->family);
fu_device_add_instance_id(device, id4);
g_free(priv->family);
g_ptr_array_unref(priv->items);
/* enforce vendors can only ship updates for their own hardware */
vendor_id = g_strdup_printf("TPM:%s", manufacturer);
fu_device_add_vendor_id(device, vendor_id);
tmp = fu_tpm_device_convert_manufacturer(manufacturer);
fu_device_set_vendor(device, tmp != NULL ? tmp : manufacturer);
/* get version */
if (!fu_tpm_device_get_uint32(ctx, TPM2_PT_FIRMWARE_VERSION_1, &version1, error))
return FALSE;
if (!fu_tpm_device_get_uint32(ctx, TPM2_PT_FIRMWARE_VERSION_2, &version2, error))
return FALSE;
version_raw = ((guint64)version1) << 32 | ((guint64)version2);
fu_device_set_version_raw(device, version_raw);
/* this has to be done after _add_instance_id() sets the quirks */
verfmt = fu_device_get_version_format(device);
version = fu_common_version_from_uint64(version_raw, verfmt);
fu_device_set_version_format(device, verfmt);
fu_device_set_version(device, version);
/* success */
return TRUE;
G_OBJECT_CLASS(fu_tpm_device_parent_class)->finalize(object);
}
static void
fu_tpm_device_init(FuTpmDevice *self)
{
FuTpmDevicePrivate *priv = GET_PRIVATE(self);
priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_tpm_v2_device_item_free);
fu_device_set_name(FU_DEVICE(self), "TPM");
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
@ -274,20 +112,12 @@ fu_tpm_device_init(FuTpmDevice *self)
FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS);
}
static void
fu_tpm_device_finalize(GObject *object)
{
FuTpmDevice *self = FU_TPM_DEVICE(object);
g_free(self->family);
G_OBJECT_CLASS(fu_tpm_device_parent_class)->finalize(object);
}
static void
fu_tpm_device_class_init(FuTpmDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
object_class->finalize = fu_tpm_device_finalize;
klass_device->setup = fu_tpm_device_setup;
klass_device->probe = fu_tpm_device_probe;
klass_device->to_string = fu_tpm_device_to_string;
}

View File

@ -9,7 +9,18 @@
#include <fwupdplugin.h>
#define FU_TYPE_TPM_DEVICE (fu_tpm_device_get_type())
G_DECLARE_FINAL_TYPE(FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice)
G_DECLARE_DERIVABLE_TYPE(FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice)
struct _FuTpmDeviceClass {
FuDeviceClass parent_class;
gpointer __reserved[31];
};
void
fu_tpm_device_set_family(FuTpmDevice *self, const gchar *family);
const gchar *
fu_tpm_device_get_family(FuTpmDevice *self);
void
fu_tpm_device_add_checksum(FuTpmDevice *self, guint idx, const gchar *checksum);
GPtrArray *
fu_tpm_device_get_checksums(FuTpmDevice *self, guint idx);

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-tpm-v1-device.h"
struct _FuTpmV1Device {
FuTpmDevice parent_instance;
};
G_DEFINE_TYPE(FuTpmV1Device, fu_tpm_v1_device, FU_TYPE_TPM_DEVICE)
static gboolean
_g_string_isxdigit(GString *str)
{
for (gsize i = 0; i < str->len; i++) {
if (!g_ascii_isxdigit(str->str[i]))
return FALSE;
}
return TRUE;
}
static void
fu_tpm_device_parse_line(const gchar *line, gpointer user_data)
{
FuTpmDevice *self = FU_TPM_DEVICE(user_data);
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, ":", -1);
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]);
fu_common_string_replace(str, " ", "");
if ((str->len != 40 && str->len != 64) || !_g_string_isxdigit(str)) {
g_debug("not SHA-1 or SHA-256, skipping: %s", split[1]);
return;
}
g_string_ascii_down(str);
fu_tpm_device_add_checksum(self, idx, str->str);
}
static gboolean
fu_tpm_v1_device_probe(FuDevice *device, GError **error)
{
FuTpmV1Device *self = FU_TPM_V1_DEVICE(device);
g_auto(GStrv) lines = NULL;
g_autofree gchar *buf_pcrs = NULL;
/* FuUdevDevice->probe */
if (!FU_DEVICE_CLASS(fu_tpm_v1_device_parent_class)->probe(device, error))
return FALSE;
/* get entire contents */
if (!g_file_get_contents(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)),
&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_tpm_device_parse_line(lines[i] + 4, self);
}
return TRUE;
}
static void
fu_tpm_v1_device_init(FuTpmV1Device *self)
{
}
static void
fu_tpm_v1_device_class_init(FuTpmV1DeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->probe = fu_tpm_v1_device_probe;
}
FuTpmDevice *
fu_tpm_v1_device_new(FuContext *ctx)
{
return FU_TPM_DEVICE(g_object_new(FU_TYPE_TPM_V1_DEVICE, "context", ctx, NULL));
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#include "fu-tpm-device.h"
#define FU_TYPE_TPM_V1_DEVICE (fu_tpm_v1_device_get_type())
G_DECLARE_FINAL_TYPE(FuTpmV1Device, fu_tpm_v1_device, FU, TPM_V1_DEVICE, FuTpmDevice)
FuTpmDevice *
fu_tpm_v1_device_new(FuContext *ctx);

View File

@ -0,0 +1,357 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <tss2/tss2_esys.h>
#include "fu-tpm-v2-device.h"
struct _FuTpmV2Device {
FuTpmDevice parent_instance;
};
G_DEFINE_TYPE(FuTpmV2Device, fu_tpm_v2_device, FU_TYPE_TPM_DEVICE)
static void
Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context)
{
Esys_Finalize(&esys_context);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup)
static gboolean
fu_tpm_v2_device_probe(FuDevice *device, GError **error)
{
/* FuUdevDevice->probe */
if (!FU_DEVICE_CLASS(fu_tpm_v2_device_parent_class)->probe(device, error))
return FALSE;
return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "tpm", error);
}
static gboolean
fu_tpm_v2_device_get_uint32(ESYS_CONTEXT *ctx, guint32 query, guint32 *val, GError **error)
{
TSS2_RC rc;
g_autofree TPMS_CAPABILITY_DATA *capability = NULL;
g_return_val_if_fail(val != NULL, FALSE);
rc = Esys_GetCapability(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
TPM2_CAP_TPM_PROPERTIES,
query,
1,
NULL,
&capability);
if (rc != TSS2_RC_SUCCESS) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"capability request failed for query %x",
query);
return FALSE;
}
if (capability->data.tpmProperties.count == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no properties returned for query %x",
query);
return FALSE;
}
if (capability->data.tpmProperties.tpmProperty[0].property != query) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"wrong query returned (got %x expected %x)",
capability->data.tpmProperties.tpmProperty[0].property,
query);
return FALSE;
}
*val = capability->data.tpmProperties.tpmProperty[0].value;
return TRUE;
}
static gchar *
fu_tpm_v2_device_get_string(ESYS_CONTEXT *ctx, guint32 query, GError **error)
{
guint32 val_be = 0;
guint32 val;
gchar result[5] = {'\0'};
/* return four bytes */
if (!fu_tpm_v2_device_get_uint32(ctx, query, &val_be, error))
return NULL;
val = GUINT32_FROM_BE(val_be);
memcpy(result, (gchar *)&val, 4);
/* convert non-ASCII into spaces */
for (guint i = 0; i < 4; i++) {
if (!g_ascii_isgraph(result[i]))
result[i] = 0x20;
}
return fu_common_strstrip(result);
}
/* taken from TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf */
static const gchar *
fu_tpm_v2_device_convert_manufacturer(const gchar *manufacturer)
{
if (g_strcmp0(manufacturer, "AMD") == 0)
return "AMD";
if (g_strcmp0(manufacturer, "ATML") == 0)
return "Atmel";
if (g_strcmp0(manufacturer, "BRCM") == 0)
return "Broadcom";
if (g_strcmp0(manufacturer, "HPE") == 0)
return "HPE";
if (g_strcmp0(manufacturer, "IBM") == 0)
return "IBM";
if (g_strcmp0(manufacturer, "IFX") == 0)
return "Infineon";
if (g_strcmp0(manufacturer, "INTC") == 0)
return "Intel";
if (g_strcmp0(manufacturer, "LEN") == 0)
return "Lenovo";
if (g_strcmp0(manufacturer, "MSFT") == 0)
return "Microsoft";
if (g_strcmp0(manufacturer, "NSM") == 0)
return "National Semiconductor";
if (g_strcmp0(manufacturer, "NTZ") == 0)
return "Nationz";
if (g_strcmp0(manufacturer, "NTC") == 0)
return "Nuvoton Technology";
if (g_strcmp0(manufacturer, "QCOM") == 0)
return "Qualcomm";
if (g_strcmp0(manufacturer, "SMSC") == 0)
return "SMSC";
if (g_strcmp0(manufacturer, "STM") == 0)
return "ST Microelectronics";
if (g_strcmp0(manufacturer, "SMSN") == 0)
return "Samsung";
if (g_strcmp0(manufacturer, "SNS") == 0)
return "Sinosun";
if (g_strcmp0(manufacturer, "TXN") == 0)
return "Texas Instruments";
if (g_strcmp0(manufacturer, "WEC") == 0)
return "Winbond";
if (g_strcmp0(manufacturer, "ROCC") == 0)
return "Fuzhou Rockchip";
if (g_strcmp0(manufacturer, "GOOG") == 0)
return "Google";
return NULL;
}
static gboolean
fu_tpm_v2_device_setup_pcrs(FuTpmV2Device *self, ESYS_CONTEXT *ctx, GError **error)
{
TSS2_RC rc;
g_autofree TPMS_CAPABILITY_DATA *capability_data = NULL;
TPML_PCR_SELECTION pcr_selection_in = {
0,
};
g_autofree TPML_DIGEST *pcr_values = NULL;
/* get hash algorithms supported by the TPM */
rc = Esys_GetCapability(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
TPM2_CAP_PCRS,
0,
1,
NULL,
&capability_data);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to get hash algorithms supported by TPM");
return FALSE;
}
/* fetch PCR 0 for every supported hash algorithm */
pcr_selection_in.count = capability_data->data.assignedPCR.count;
for (guint i = 0; i < pcr_selection_in.count; i++) {
pcr_selection_in.pcrSelections[i].hash =
capability_data->data.assignedPCR.pcrSelections[i].hash;
pcr_selection_in.pcrSelections[i].sizeofSelect =
capability_data->data.assignedPCR.pcrSelections[i].sizeofSelect;
pcr_selection_in.pcrSelections[i].pcrSelect[0] = 0b00000001;
}
rc = Esys_PCR_Read(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&pcr_selection_in,
NULL,
NULL,
&pcr_values);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to read PCR values from TPM");
return FALSE;
}
for (guint i = 0; i < pcr_values->count; i++) {
g_autoptr(GString) str = NULL;
gboolean valid = FALSE;
str = g_string_new(NULL);
for (guint j = 0; j < pcr_values->digests[i].size; j++) {
gint64 val = pcr_values->digests[i].buffer[j];
if (val > 0)
valid = TRUE;
g_string_append_printf(str, "%02x", pcr_values->digests[i].buffer[j]);
}
if (valid) {
/* constant PCR index 0, since we only read this single PCR */
fu_tpm_device_add_checksum(FU_TPM_DEVICE(self), 0, str->str);
}
}
/* success */
return TRUE;
}
static gboolean
fu_tpm_v2_device_setup(FuDevice *device, GError **error)
{
FuTpmV2Device *self = FU_TPM_V2_DEVICE(device);
FwupdVersionFormat verfmt;
TSS2_RC rc;
const gchar *tmp;
guint32 tpm_type = 0;
guint32 version1 = 0;
guint32 version2 = 0;
guint64 version_raw;
g_autofree gchar *id1 = NULL;
g_autofree gchar *id2 = NULL;
g_autofree gchar *id3 = NULL;
g_autofree gchar *id4 = NULL;
g_autofree gchar *manufacturer = NULL;
g_autofree gchar *model1 = NULL;
g_autofree gchar *model2 = NULL;
g_autofree gchar *model3 = NULL;
g_autofree gchar *model4 = NULL;
g_autofree gchar *model = NULL;
g_autofree gchar *vendor_id = NULL;
g_autofree gchar *version = NULL;
g_autofree gchar *family = NULL;
g_autoptr(ESYS_CONTEXT) ctx = NULL;
/* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */
if (g_getenv("FWUPD_UEFI_VERBOSE") == NULL)
g_setenv("TSS2_LOG", "esys+error,tcti+none", FALSE);
/* setup TSS */
rc = Esys_Initialize(&ctx, NULL, NULL);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"failed to initialize TPM library");
return FALSE;
}
rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to initialize TPM");
return FALSE;
}
/* lookup guaranteed details from TPM */
family = fu_tpm_v2_device_get_string(ctx, TPM2_PT_FAMILY_INDICATOR, error);
if (family == NULL) {
g_prefix_error(error, "failed to read TPM family: ");
return FALSE;
}
fu_tpm_device_set_family(FU_TPM_DEVICE(self), family);
manufacturer = fu_tpm_v2_device_get_string(ctx, TPM2_PT_MANUFACTURER, error);
if (manufacturer == NULL) {
g_prefix_error(error, "failed to read TPM manufacturer: ");
return FALSE;
}
model1 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_1, error);
if (model1 == NULL) {
g_prefix_error(error, "failed to read TPM vendor string: ");
return FALSE;
}
if (!fu_tpm_v2_device_get_uint32(ctx, TPM2_PT_VENDOR_TPM_TYPE, &tpm_type, error)) {
g_prefix_error(error, "failed to read TPM type: ");
return FALSE;
}
/* these are not guaranteed by spec and may be NULL */
model2 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_2, error);
model3 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_3, error);
model4 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_4, error);
model = g_strjoin("", model1, model2, model3, model4, NULL);
/* add GUIDs to daemon */
id1 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X", manufacturer, tpm_type);
fu_device_add_instance_id(device, id1);
id2 = g_strdup_printf("TPM\\VEN_%s&MOD_%s", manufacturer, model);
fu_device_add_instance_id(device, id2);
id3 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X&VER_%s", manufacturer, tpm_type, family);
fu_device_add_instance_id(device, id3);
id4 = g_strdup_printf("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, family);
fu_device_add_instance_id(device, id4);
/* enforce vendors can only ship updates for their own hardware */
vendor_id = g_strdup_printf("TPM:%s", manufacturer);
fu_device_add_vendor_id(device, vendor_id);
tmp = fu_tpm_v2_device_convert_manufacturer(manufacturer);
fu_device_set_vendor(device, tmp != NULL ? tmp : manufacturer);
/* get version */
if (!fu_tpm_v2_device_get_uint32(ctx, TPM2_PT_FIRMWARE_VERSION_1, &version1, error))
return FALSE;
if (!fu_tpm_v2_device_get_uint32(ctx, TPM2_PT_FIRMWARE_VERSION_2, &version2, error))
return FALSE;
version_raw = ((guint64)version1) << 32 | ((guint64)version2);
fu_device_set_version_raw(device, version_raw);
/* this has to be done after _add_instance_id() sets the quirks */
verfmt = fu_device_get_version_format(device);
version = fu_common_version_from_uint64(version_raw, verfmt);
fu_device_set_version_format(device, verfmt);
fu_device_set_version(device, version);
/* get PCRs */
return fu_tpm_v2_device_setup_pcrs(self, ctx, error);
}
static void
fu_tpm_v2_device_init(FuTpmV2Device *self)
{
}
static void
fu_tpm_v2_device_class_init(FuTpmV2DeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->setup = fu_tpm_v2_device_setup;
klass_device->probe = fu_tpm_v2_device_probe;
}
FuTpmDevice *
fu_tpm_v2_device_new(FuContext *ctx)
{
FuTpmV2Device *self;
self = g_object_new(FU_TYPE_TPM_V2_DEVICE, "context", ctx, NULL);
return FU_TPM_DEVICE(self);
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#include "fu-tpm-device.h"
#define FU_TYPE_TPM_V2_DEVICE (fu_tpm_v2_device_get_type())
G_DECLARE_FINAL_TYPE(FuTpmV2Device, fu_tpm_v2_device, FU, TPM_V2_DEVICE, FuTpmDevice)
FuTpmDevice *
fu_tpm_v2_device_new(FuContext *ctx);

View File

@ -15,6 +15,8 @@ shared_module('fu_plugin_tpm',
sources : [
'fu-plugin-tpm.c',
'fu-tpm-device.c',
'fu-tpm-v1-device.c',
'fu-tpm-v2-device.c',
],
include_directories : [
root_incdir,
@ -33,4 +35,36 @@ shared_module('fu_plugin_tpm',
tpm2tss,
],
)
if get_option('tests')
env = environment()
env.set('G_TEST_SRCDIR', meson.current_source_dir())
env.set('G_TEST_BUILDDIR', meson.current_build_dir())
env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var')
e = executable(
'tpm-self-test',
fu_hash,
sources : [
'fu-self-test.c',
'fu-tpm-device.c',
'fu-tpm-v1-device.c',
'fu-tpm-v2-device.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
dependencies : [
plugin_deps,
tpm2tss,
],
link_with : [
fwupd,
fwupdplugin,
],
c_args : cargs
)
test('tpm-self-test', e, env: env)
endif
endif

View File

@ -14,73 +14,6 @@
#include "fu-uefi-bgrt.h"
#include "fu-uefi-cod-device.h"
#include "fu-uefi-common.h"
#include "fu-uefi-pcrs.h"
static void
fu_uefi_pcrs_1_2_func(void)
{
gboolean ret;
g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new();
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
g_autoptr(GPtrArray) pcrXs = NULL;
g_autofree gchar *testdatadir = NULL;
testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL);
g_setenv("FWUPD_SYSFSTPMDIR", testdatadir, TRUE);
ret = fu_uefi_pcrs_setup(pcrs, &error);
g_assert_no_error(error);
g_assert_true(ret);
pcr0s = fu_uefi_pcrs_get_checksums(pcrs, 0);
g_assert_nonnull(pcr0s);
g_assert_cmpint(pcr0s->len, ==, 1);
pcrXs = fu_uefi_pcrs_get_checksums(pcrs, 999);
g_assert_nonnull(pcrXs);
g_assert_cmpint(pcrXs->len, ==, 0);
g_unsetenv("FWUPD_SYSFSTPMDIR");
}
static void
fu_uefi_pcrs_2_0_func(void)
{
g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new();
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
g_autoptr(GPtrArray) pcrXs = NULL;
const gchar *tpm_server_running = g_getenv("TPM_SERVER_RUNNING");
g_setenv("FWUPD_FORCE_TPM2", "1", TRUE);
#ifndef HAVE_TSS2
g_test_skip("Compiled without TPM2.0 support");
return;
#endif
#ifdef HAVE_GETUID
if (tpm_server_running == NULL && (getuid() != 0 || geteuid() != 0)) {
g_test_skip("TPM2.0 tests require simulated TPM2.0 running or need root access "
"with physical TPM");
return;
}
#endif
if (!fu_uefi_pcrs_setup(pcrs, &error)) {
if (tpm_server_running == NULL &&
g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
g_test_skip("no physical or simulated TPM 2.0 device available");
return;
}
}
g_assert_no_error(error);
pcr0s = fu_uefi_pcrs_get_checksums(pcrs, 0);
g_assert_nonnull(pcr0s);
g_assert_cmpint(pcr0s->len, >=, 1);
pcrXs = fu_uefi_pcrs_get_checksums(pcrs, 999);
g_assert_nonnull(pcrXs);
g_assert_cmpint(pcrXs->len, ==, 0);
g_unsetenv("FWUPD_FORCE_TPM2");
}
static void
fu_uefi_ucs2_func(void)
@ -375,8 +308,6 @@ main(int argc, char **argv)
g_setenv("G_MESSAGES_DEBUG", "all", TRUE);
/* tests go here */
g_test_add_func("/uefi/pcrs1.2", fu_uefi_pcrs_1_2_func);
g_test_add_func("/uefi/pcrs2.0", fu_uefi_pcrs_2_0_func);
g_test_add_func("/uefi/ucs2", fu_uefi_ucs2_func);
g_test_add_func("/uefi/bgrt", fu_uefi_bgrt_func);
g_test_add_func("/uefi/framebuffer", fu_uefi_framebuffer_func);

View File

@ -16,7 +16,6 @@
#include "fu-uefi-common.h"
#include "fu-uefi-device.h"
#include "fu-uefi-devpath.h"
#include "fu-uefi-pcrs.h"
typedef struct {
FuVolume *esp;
@ -592,39 +591,6 @@ fu_uefi_device_cleanup(FuDevice *device, FwupdInstallFlags flags, GError **error
return TRUE;
}
static gboolean
fu_uefi_device_add_system_checksum(FuDevice *device, GError **error)
{
g_autoptr(FuUefiPcrs) pcrs = fu_uefi_pcrs_new();
g_autoptr(GError) error_local = NULL;
g_autoptr(GPtrArray) pcr0s = NULL;
/* get all the PCRs */
if (!fu_uefi_pcrs_setup(pcrs, &error_local)) {
if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) ||
g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
g_debug("%s", error_local->message);
return TRUE;
}
g_propagate_error(error, g_steal_pointer(&error_local));
return FALSE;
}
/* get all the PCR0s */
pcr0s = fu_uefi_pcrs_get_checksums(pcrs, 0);
if (pcr0s->len == 0) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no PCR0s detected");
return FALSE;
}
for (guint i = 0; i < pcr0s->len; i++) {
const gchar *checksum = g_ptr_array_index(pcr0s, i);
fu_device_add_checksum(device, checksum);
}
/* success */
return TRUE;
}
static gboolean
fu_uefi_device_probe(FuDevice *device, GError **error)
{
@ -688,14 +654,6 @@ fu_uefi_device_probe(FuDevice *device, GError **error)
fu_device_add_instance_id(device, "main-system-firmware");
}
/* set the PCR0 as the device checksum */
if (priv->kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) {
g_autoptr(GError) error_local = NULL;
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY);
if (!fu_uefi_device_add_system_checksum(device, &error_local))
g_warning("Failed to get PCR0s: %s", error_local->message);
}
/* whether to create a missing header */
if (priv->kind == FU_UEFI_DEVICE_KIND_FMP ||
priv->kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE)

View File

@ -1,296 +0,0 @@
/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#ifdef HAVE_TSS2
#include <tss2/tss2_esys.h>
#endif
#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)
#ifdef HAVE_TSS2
static void
Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context)
{
Esys_Finalize(&esys_context);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup)
#endif
static gboolean
_g_string_isxdigit(GString *str)
{
for (gsize i = 0; i < str->len; i++) {
if (!g_ascii_isxdigit(str->str[i]))
return FALSE;
}
return TRUE;
}
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, ":", -1);
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]);
fu_common_string_replace(str, " ", "");
if ((str->len != 40 && str->len != 64) || !_g_string_isxdigit(str)) {
g_debug("not SHA-1 or SHA-256, skipping: %s", split[1]);
return;
}
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_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, GError **error)
{
#ifdef HAVE_TSS2
TSS2_RC rc;
g_autoptr(ESYS_CONTEXT) ctx = NULL;
g_autofree TPMS_CAPABILITY_DATA *capability_data = NULL;
TPML_PCR_SELECTION pcr_selection_in = {
0,
};
g_autofree TPML_DIGEST *pcr_values = NULL;
/* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */
if (g_getenv("FWUPD_UEFI_VERBOSE") == NULL) {
g_setenv("TSS2_LOG", "esys+error,tcti+none", FALSE);
}
rc = Esys_Initialize(&ctx, NULL, NULL);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"failed to initialize TPM library");
return FALSE;
}
rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to initialize TPM");
return FALSE;
}
/* get hash algorithms supported by the TPM */
rc = Esys_GetCapability(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
TPM2_CAP_PCRS,
0,
1,
NULL,
&capability_data);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to get hash algorithms supported by TPM");
return FALSE;
}
/* fetch PCR 0 for every supported hash algorithm */
pcr_selection_in.count = capability_data->data.assignedPCR.count;
for (guint i = 0; i < pcr_selection_in.count; i++) {
pcr_selection_in.pcrSelections[i].hash =
capability_data->data.assignedPCR.pcrSelections[i].hash;
pcr_selection_in.pcrSelections[i].sizeofSelect =
capability_data->data.assignedPCR.pcrSelections[i].sizeofSelect;
pcr_selection_in.pcrSelections[i].pcrSelect[0] = 0b00000001;
}
rc = Esys_PCR_Read(ctx,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&pcr_selection_in,
NULL,
NULL,
&pcr_values);
if (rc != TSS2_RC_SUCCESS) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to read PCR values from TPM");
return FALSE;
}
for (guint i = 0; i < pcr_values->count; i++) {
FuUefiPcrItem *item;
g_autoptr(GString) str = NULL;
gboolean valid = FALSE;
str = g_string_new(NULL);
for (guint j = 0; j < pcr_values->digests[i].size; j++) {
gint64 val = pcr_values->digests[i].buffer[j];
if (val > 0)
valid = TRUE;
g_string_append_printf(str, "%02x", pcr_values->digests[i].buffer[j]);
}
if (valid) {
item = g_new0(FuUefiPcrItem, 1);
item->idx =
0; /* constant PCR index 0, since we only read this single PCR */
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);
}
}
#endif
/* success */
return TRUE;
}
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;
g_return_val_if_fail(FU_IS_UEFI_PCRS(self), FALSE);
/* look for TPM 1.2 */
sysfstpmdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_TPM);
devpath = g_build_filename(sysfstpmdir, "tpm0", NULL);
fn_pcrs = g_build_filename(devpath, "pcrs", NULL);
if (g_file_test(fn_pcrs, G_FILE_TEST_EXISTS) && g_getenv("FWUPD_FORCE_TPM2") == NULL) {
if (!fu_uefi_pcrs_setup_tpm12(self, fn_pcrs, error))
return FALSE;
/* assume TPM 2.0 */
} else {
if (!fu_uefi_pcrs_setup_tpm20(self, 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);
}

View File

@ -1,17 +0,0 @@
/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#define FU_TYPE_UEFI_PCRS (fu_uefi_pcrs_get_type())
G_DECLARE_FINAL_TYPE(FuUefiPcrs, fu_uefi_pcrs, FU, UEFI_PCRS, GObject)
FuUefiPcrs *
fu_uefi_pcrs_new(void);
gboolean
fu_uefi_pcrs_setup(FuUefiPcrs *self, GError **error);
GPtrArray *
fu_uefi_pcrs_get_checksums(FuUefiPcrs *self, guint idx);

View File

@ -42,7 +42,6 @@ shared_module('fu_plugin_uefi_capsule',
'fu-uefi-grub-device.c',
'fu-uefi-device.c',
'fu-uefi-devpath.c',
'fu-uefi-pcrs.c',
'fu-uefi-update-info.c',
backend_srcs,
],
@ -62,7 +61,6 @@ shared_module('fu_plugin_uefi_capsule',
plugin_deps,
platform_deps,
efiboot,
tpm2tss,
],
)
@ -81,7 +79,6 @@ fwupdate = executable(
'fu-uefi-nvram-device.c',
'fu-uefi-grub-device.c',
'fu-uefi-devpath.c',
'fu-uefi-pcrs.c',
'fu-uefi-update-info.c',
backend_srcs,
],
@ -94,7 +91,6 @@ fwupdate = executable(
plugin_deps,
platform_deps,
efiboot,
tpm2tss,
],
link_with : [
fwupd,
@ -167,7 +163,6 @@ if get_option('tests')
'fu-uefi-nvram-device.c',
'fu-uefi-grub-device.c',
'fu-uefi-devpath.c',
'fu-uefi-pcrs.c',
'fu-uefi-update-info.c',
'fu-ucs2.c',
backend_srcs,
@ -181,7 +176,6 @@ if get_option('tests')
plugin_deps,
platform_deps,
efiboot,
tpm2tss,
],
link_with : [
fwupd,