diff --git a/contrib/ci/Dockerfile-debian.in b/contrib/ci/Dockerfile-debian.in index 02c3e45ba..d4c07989c 100644 --- a/contrib/ci/Dockerfile-debian.in +++ b/contrib/ci/Dockerfile-debian.in @@ -11,6 +11,10 @@ Pin-Priority: 800\n\ \n\ Package: lintian\n\ Pin: release a=unstable\n\ +Pin-Priority: 901\n\ +\n\ +Package: tpm-udev\n\ +Pin: release a=unstable\n\ Pin-Priority: 901\n'\ > /etc/apt/preferences RUN cat /etc/apt/preferences diff --git a/contrib/ci/build_windows.sh b/contrib/ci/build_windows.sh index 9abd5185b..163559def 100755 --- a/contrib/ci/build_windows.sh +++ b/contrib/ci/build_windows.sh @@ -22,6 +22,7 @@ meson .. \ -Dplugin_altos=false \ -Dplugin_dell=false \ -Dplugin_nvme=false \ + -Dplugin_tpm=false \ -Dsystemd=false \ -Dplugin_emmc=false \ -Dplugin_amt=false \ diff --git a/contrib/ci/dependencies.xml b/contrib/ci/dependencies.xml index cc1789c1d..9c6d77064 100644 --- a/contrib/ci/dependencies.xml +++ b/contrib/ci/dependencies.xml @@ -1329,13 +1329,9 @@ tpm2-tss-devel - - amd64 - arm64 - armhf - i386 - + + libtss2-dev:s390x diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index 130039278..80305034d 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -352,6 +352,7 @@ rm ${RPM_BUILD_ROOT}%{_sbindir}/flashrom %{_libdir}/fwupd-plugins-3/libfu_plugin_thelio_io.so %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt.so %{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt_power.so +%{_libdir}/fwupd-plugins-3/libfu_plugin_tpm.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-3/libfu_plugin_uefi.so %{_libdir}/fwupd-plugins-3/libfu_plugin_uefi_recovery.so diff --git a/libfwupdplugin/fu-udev-device.c b/libfwupdplugin/fu-udev-device.c index 78fc5fae3..07feded94 100644 --- a/libfwupdplugin/fu-udev-device.c +++ b/libfwupdplugin/fu-udev-device.c @@ -653,6 +653,16 @@ fu_udev_device_set_physical_id (FuUdevDevice *self, const gchar *subsystem, GErr return FALSE; } physical_id = g_strdup_printf ("HID_PHYS=%s", tmp); + } else if (g_strcmp0 (subsystem, "tpm") == 0) { + tmp = g_udev_device_get_property (udev_device, "DEVNAME"); + if (tmp == NULL) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + "failed to find DEVPATH"); + return FALSE; + } + physical_id = g_strdup_printf ("DEVNAME=%s", tmp); } else { g_set_error (error, G_IO_ERROR, diff --git a/meson.build b/meson.build index ec9b8a3fb..1e9b8a4cc 100644 --- a/meson.build +++ b/meson.build @@ -277,6 +277,10 @@ if cc.has_function('pwrite', args : '-D_XOPEN_SOURCE') conf.set('HAVE_PWRITE', '1') endif +if build_standalone and get_option('plugin_tpm') + tpm2tss = dependency('tss2-esys', version : '>= 2.0') +endif + if build_standalone and get_option('plugin_uefi') cairo = dependency('cairo') fontconfig = cc.find_library('fontconfig') diff --git a/meson_options.txt b/meson_options.txt index 17c30446a..be0adfef6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -17,6 +17,7 @@ option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC option('plugin_synaptics', type: 'boolean', value: true, description : 'enable Synaptics MST hub support') option('plugin_thunderbolt', type : 'boolean', value : true, description : 'enable Thunderbolt support') option('plugin_redfish', type : 'boolean', value : true, description : 'enable Redfish support') +option('plugin_tpm', type : 'boolean', value : true, description : 'enable TPM support') option('plugin_uefi', type : 'boolean', value : true, description : 'enable UEFI support') option('plugin_nvme', type : 'boolean', value : true, description : 'enable NVMe support') option('plugin_modem_manager', type : 'boolean', value : false, description : 'enable ModemManager support') diff --git a/plugins/dell/fu-plugin-dell.c b/plugins/dell/fu-plugin-dell.c index efd8498ac..b448c74a6 100644 --- a/plugins/dell/fu-plugin-dell.c +++ b/plugins/dell/fu-plugin-dell.c @@ -852,6 +852,9 @@ fu_plugin_init (FuPlugin *plugin) /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi"); + + /* our TPM device is upgradable! */ + fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "tpm"); } void diff --git a/plugins/meson.build b/plugins/meson.build index 73f74eb20..cce9b252a 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -29,6 +29,10 @@ endif # depends on dfu subdir('csr') +if get_option('plugin_tpm') and get_option('gudev') +subdir('tpm') +endif + if get_option('plugin_emmc') and get_option('gudev') subdir('emmc') endif diff --git a/plugins/tpm/README.md b/plugins/tpm/README.md new file mode 100644 index 000000000..d4f539769 --- /dev/null +++ b/plugins/tpm/README.md @@ -0,0 +1,27 @@ +TPM Support +=========== + +Introduction +------------ + +This allows enumerating Trusted Platform Modules, also known as "TPM" devices, +although it does not allow the user to update the firmware on them. + +GUID Generation +--------------- + +These devices use custom GUIDs: + + + * `TPM\VEN_$(manufacturer)&DEV_$(type)` + * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)` + * `TPM\VEN_$(manufacturer)&DEV_$(type)_VER_$(family)`, + * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)_VER_$(family)` + +...where `family` is either `2.0` or `1.2` + +Example GUIDs from a real system containing a TPM from Intel: +``` + Guid: 34801700-3a50-5b05-820c-fe14580e4c2d <- TPM\VEN_INTC&DEV_0000 + Guid: 03f304f4-223e-54f4-b2c1-c3cf3b5817c6 <- TPM\VEN_INTC&DEV_0000&VER_2.0 +``` diff --git a/plugins/tpm/fu-plugin-tpm.c b/plugins/tpm/fu-plugin-tpm.c new file mode 100644 index 000000000..fec3842eb --- /dev/null +++ b/plugins/tpm/fu-plugin-tpm.c @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-hash.h" +#include "fu-plugin-vfuncs.h" + +#include "fu-tpm-device.h" + +void +fu_plugin_init (FuPlugin *plugin) +{ + fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); + fu_plugin_add_udev_subsystem (plugin, "tpm"); + fu_plugin_set_device_gtype (plugin, FU_TYPE_TPM_DEVICE); +} diff --git a/plugins/tpm/fu-tpm-device.c b/plugins/tpm/fu-tpm-device.c new file mode 100644 index 000000000..0ebd0311c --- /dev/null +++ b/plugins/tpm/fu-tpm-device.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-tpm-device.h" + +struct _FuTpmDevice { + FuUdevDevice parent_instance; +}; + +G_DEFINE_TYPE (FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_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_device_probe (FuUdevDevice *device, GError **error) +{ + return fu_udev_device_set_physical_id (device, "tpm", error); +} + +static gboolean +fu_tpm_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_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] != '\0') + 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) +{ + FwupdVersionFormat verfmt; + TSS2_RC rc; + const gchar *tmp; + guint32 tpm_type = 0; + guint32 version1 = 0; + guint32 version2 = 0; + guint64 version_raw; + g_autofree gchar *family = NULL; + 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; + } + + /* lookup guaranteed details from TPM */ + family = fu_tpm_device_get_string (ctx, TPM2_PT_FAMILY_INDICATOR, error); + if (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; + } + + /* 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); + + /* 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 thier own hardware */ + vendor_id = g_strdup_printf ("TPM:%s", manufacturer); + fu_device_set_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 (device, version, verfmt); + + /* success */ + return TRUE; +} + +static void +fu_tpm_device_init (FuTpmDevice *self) +{ + 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); + fu_device_add_icon (FU_DEVICE (self), "computer"); + fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_NONE); +} + +static void +fu_tpm_device_finalize (GObject *object) +{ + 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); + FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass); + object_class->finalize = fu_tpm_device_finalize; + klass_device->setup = fu_tpm_device_setup; + klass_udev_device->probe = fu_tpm_device_probe; +} diff --git a/plugins/tpm/fu-tpm-device.h b/plugins/tpm/fu-tpm-device.h new file mode 100644 index 000000000..55c1ef312 --- /dev/null +++ b/plugins/tpm/fu-tpm-device.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-plugin.h" + +#define FU_TYPE_TPM_DEVICE (fu_tpm_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice) diff --git a/plugins/tpm/meson.build b/plugins/tpm/meson.build new file mode 100644 index 000000000..3c1db60e2 --- /dev/null +++ b/plugins/tpm/meson.build @@ -0,0 +1,31 @@ +cargs = ['-DG_LOG_DOMAIN="FuPluginTpm"'] + +install_data([ + 'tpm.quirk', + ], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_tpm', + fu_hash, + sources : [ + 'fu-plugin-tpm.c', + 'fu-tpm-device.c', + ], + include_directories : [ + root_incdir, + fwupd_incdir, + fwupdplugin_incdir, + ], + install : true, + install_dir: plugin_dir, + link_with : [ + fwupdplugin, + fwupd, + ], + c_args : cargs, + dependencies : [ + plugin_deps, + tpm2tss, + ], +) diff --git a/plugins/tpm/tpm.quirk b/plugins/tpm/tpm.quirk new file mode 100644 index 000000000..4bba8583f --- /dev/null +++ b/plugins/tpm/tpm.quirk @@ -0,0 +1,2 @@ +[DeviceInstanceId=TPM] +Plugin = tpm