tpm2: add hwdb quirk db, to deal with borked TPMs (#40931)

This adds infrastructure so that we can maintain quirks for TPM devices
in hwdb, to deal with issues such as #40485.

I expected that we need this sooner or later anyway, so here we go.

Fixes: #40485
This commit is contained in:
Lennart Poettering
2026-03-04 09:54:10 +01:00
committed by GitHub
27 changed files with 631 additions and 49 deletions
+8
View File
@@ -231,6 +231,11 @@ CHANGES WITH 260 in spe:
ID_INTEGRATION= because it was never used and the new variable covers
the idea that variable was intended for better.
* A new udev builtin "tpm2_id" is now available which will extract
vendor/model identification from connected TPM2 devices as they are
probed. This is then used to import data from the udev database,
possibly containing quirk and other information about specific TPMs.
Changes in systemd-networkd:
* MultiPathRoute= option now supports interface-bound ECMP routes.
@@ -469,6 +474,9 @@ CHANGES WITH 260 in spe:
system components to synchronously hook into the OOM killing logic,
by registering a Varlink socket in a special directory.
* systemd-analyze learnt a new verb "identify-tpm2" which shows
vendor/model information extracted from the system's TPM.
Changes in units:
* runlevel[0-6].target units that were removed in v258 have been
+9
View File
@@ -1015,3 +1015,12 @@ non-volatile indexes (NV Indexes), could not be initialized.
This typically means the persistent NV index memory available on the TPM is
taken up by other resources, or is extremely limited. A TPM reset might help
recovering space (but will invalidate all TPM bound keys and resources).
-- 8f07a5b814ca4762b89fcc3082e48aed
Subject: TPM NV index backed PCRs not supported on the local TPM
Defined-By: systemd
Support: %SUPPORT_URL%
The Trusted Platform Module's (TPM) NV index support is too limited to properly
implement NV index backed additional PCRs. NvPCRs will not be allocated or
initialized, and will not be available on the system.
+14
View File
@@ -0,0 +1,14 @@
# This file is part of systemd.
#
# Use "systemd-analyze identify-tpm2" to generate the modalias string for your
# hardware. Don't forget to prefix it with "tpm:" for inclusion in a match here.
#
# Currently, the only relevant property to set here is TPM2_BROKEN_NVPCR=1,
# which should be set on TPMs where NvPCRs don't work. Specifically, because
# on some hardware the combination of TPMA_NV_ORDERLY + TPM2_NT_EXTEND cause
# NV_Extend() operations to time out. For details, see:
# https://github.com/systemd/systemd/issues/40485
# ST33TPHF2ESPI Firmware 73.4
tpm2:*:mfSTM:*:fw73.4.*:
TPM2_BROKEN_NVPCR=1
+1
View File
@@ -26,6 +26,7 @@ hwdb_files_test = files(
'60-keyboard.hwdb',
'60-seat.hwdb',
'60-sensor.hwdb',
'60-tpm2.hwdb',
'70-analyzers.hwdb',
'70-av-production.hwdb',
'70-cameras.hwdb',
+30 -27
View File
@@ -100,13 +100,14 @@ TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
# Patterns that are used to set general properties on a device
GENERAL_MATCHES = {'acpi',
'bluetooth',
'usb',
'dmi',
'ieee1394',
'OUI',
'pci',
'sdio',
'tpm2',
'usb',
'vmbus',
'OUI',
'ieee1394',
'dmi',
}
def upperhex_word(length):
@@ -147,6 +148,7 @@ def property_grammar():
mount_matrix = Group(mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
xkb_setting = Optional(Word(alphanums + '+-/@._'))
id_input_setting = Optional(Or((Literal('0'), Literal('1'))))
zero_one = Or((Literal('0'), Literal('1')))
# Although this set doesn't cover all of characters in database entries, it's enough for test targets.
name_literal = Word(printables + ' ')
@@ -156,13 +158,13 @@ def property_grammar():
('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
('ID_INPUT_3D_MOUSE', Or((Literal('0'), Literal('1')))),
('ID_AUTOSUSPEND', Or((Literal('0'), Literal('1')))),
('ID_INPUT_3D_MOUSE', zero_one),
('ID_AUTOSUSPEND', zero_one),
('ID_AUTOSUSPEND_DELAY_MS', INTEGER),
('ID_AV_PRODUCTION_CONTROLLER', Or((Literal('0'), Literal('1')))),
('ID_AV_LIGHTS', Or((Literal('0'), Literal('1')))),
('ID_PERSIST', Or((Literal('0'), Literal('1')))),
('ID_PDA', Or((Literal('0'), Literal('1')))),
('ID_AV_PRODUCTION_CONTROLLER', zero_one),
('ID_AV_LIGHTS', zero_one),
('ID_PERSIST', zero_one),
('ID_PDA', zero_one),
('ID_INPUT', id_input_setting),
('ID_INPUT_ACCELEROMETER', id_input_setting),
('ID_INPUT_JOYSTICK', id_input_setting),
@@ -176,12 +178,12 @@ def property_grammar():
('ID_INPUT_TOUCHPAD', id_input_setting),
('ID_INPUT_TOUCHSCREEN', id_input_setting),
('ID_INPUT_TRACKBALL', id_input_setting),
('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))),
('ID_MAKER_TOOL', Or((Literal('0'), Literal('1')))),
('ID_HARDWARE_WALLET', Or((Literal('0'), Literal('1')))),
('ID_SOFTWARE_RADIO', Or((Literal('0'), Literal('1')))),
('ID_MM_DEVICE_IGNORE', Or((Literal('0'), Literal('1')))),
('ID_NET_AUTO_LINK_LOCAL_ONLY', Or((Literal('0'), Literal('1')))),
('ID_SIGNAL_ANALYZER', zero_one),
('ID_MAKER_TOOL', zero_one),
('ID_HARDWARE_WALLET', zero_one),
('ID_SOFTWARE_RADIO', zero_one),
('ID_MM_DEVICE_IGNORE', zero_one),
('ID_NET_AUTO_LINK_LOCAL_ONLY', zero_one),
('POINTINGSTICK_SENSITIVITY', INTEGER),
('ID_INTEGRATION', Or(('internal', 'external'))),
('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
@@ -193,25 +195,26 @@ def property_grammar():
('ACCEL_MOUNT_MATRIX', mount_matrix),
('ACCEL_LOCATION', Or(('display', 'base'))),
('PROXIMITY_NEAR_LEVEL', INTEGER),
('IEEE1394_UNIT_FUNCTION_MIDI', Or((Literal('0'), Literal('1')))),
('IEEE1394_UNIT_FUNCTION_AUDIO', Or((Literal('0'), Literal('1')))),
('IEEE1394_UNIT_FUNCTION_VIDEO', Or((Literal('0'), Literal('1')))),
('IEEE1394_UNIT_FUNCTION_MIDI', zero_one),
('IEEE1394_UNIT_FUNCTION_AUDIO', zero_one),
('IEEE1394_UNIT_FUNCTION_VIDEO', zero_one),
('ID_VENDOR_FROM_DATABASE', name_literal),
('ID_MODEL_FROM_DATABASE', name_literal),
('ID_TAG_MASTER_OF_SEAT', Literal('1')),
('ID_INFRARED_CAMERA', Or((Literal('0'), Literal('1')))),
('ID_INFRARED_CAMERA', zero_one),
('ID_CAMERA_DIRECTION', Or(('front', 'rear'))),
('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))),
('ID_SYS_VENDOR_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_PRODUCT_NAME_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_PRODUCT_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_BOARD_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_PRODUCT_SKU_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', Or((Literal('0'), Literal('1')))),
('ID_SYS_VENDOR_IS_RUBBISH', zero_one),
('ID_PRODUCT_NAME_IS_RUBBISH', zero_one),
('ID_PRODUCT_VERSION_IS_RUBBISH', zero_one),
('ID_BOARD_VERSION_IS_RUBBISH', zero_one),
('ID_PRODUCT_SKU_IS_RUBBISH', zero_one),
('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', zero_one),
('ID_CHASSIS', name_literal),
('ID_SYSFS_ATTRIBUTE_MODEL', name_literal),
('ID_NET_NAME_FROM_DATABASE', name_literal),
('ID_NET_NAME_INCLUDE_DOMAIN', Or((Literal('0'), Literal('1')))),
('ID_NET_NAME_INCLUDE_DOMAIN', zero_one),
('TPM2_BROKEN_NVPCR', zero_one),
)
fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
for name, val in props]
+26
View File
@@ -195,6 +195,11 @@
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">has-tpm2</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">identify-tpm2</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
@@ -1028,6 +1033,27 @@ default ignore - -</programlisting>
<xi:include href="version-info.xml" xpointer="v257"/>
</refsect2>
<refsect2>
<title><command>systemd-analyze identify-tpm2</command></title>
<para>Shows vendor information about the TPM 2.0 device discovered.</para>
<example>
<title>Example Output</title>
<programlisting> Family Indicator: 2.0
Level: 0
Revision: 1.59
Specification Date: Mon 2023-01-09
Manufacturer: STM
Vendor String: ST33KTPM2XSPI
Firmware Version: 9.258
Modalias String: fi2.0:lv0:rv1.59:sy2023:sd9:mfSTM:vsST33KTPM2XSPI:ty0:fw9.258.0:</programlisting>
</example>
<xi:include href="version-info.xml" xpointer="v260"/>
</refsect2>
<refsect2>
<title><command>systemd-analyze pcrs <optional><replaceable>PCR</replaceable>…</optional></command></title>
+10
View File
@@ -0,0 +1,10 @@
# do not edit this file, it will be overwritten on update
ACTION=="remove", GOTO="tpm2_id_end"
SUBSYSTEM!="tpmrm", GOTO="tpm2_id_end"
KERNEL!="tpmrm[0-9]*", GOTO="tpm2_id_end"
IMPORT{program}="tpm2_id identify"
ENV{ID_TPM2_MODALIAS}!="", IMPORT{builtin}="hwdb 'tpm2:$env{ID_TPM2_MODALIAS}'"
LABEL="tpm2_id_end"
+1
View File
@@ -23,6 +23,7 @@ rules = [
'60-persistent-v4l.rules',
'60-sensor.rules',
'60-serial.rules',
'60-tpm2-id.rules',
'65-integration.rules',
'70-camera.rules',
'70-joystick.rules',
+1 -1
View File
@@ -77,7 +77,7 @@ _systemd_analyze() {
)
local -A VERBS=(
[STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 smbios11 chid image-policy'
[STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 identify-tpm2 smbios11 chid image-policy'
[CRITICAL_CHAIN]='critical-chain'
[DOT]='dot'
[DUMP]='dump'
+133
View File
@@ -2,8 +2,141 @@
#include "analyze.h"
#include "analyze-has-tpm2.h"
#include "format-table.h"
#include "log.h"
#include "string-util.h"
#include "time-util.h"
#include "tpm2-util.h"
int verb_has_tpm2(int argc, char **argv, void *userdata) {
return verb_has_tpm2_generic(arg_quiet);
}
int verb_identify_tpm2(int argc, char **argv, void *userdata) {
#if HAVE_TPM2
int r;
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
r = tpm2_context_new_or_warn(/* device= */ NULL, &c);
if (r < 0)
return r;
Tpm2VendorInfo info;
r = tpm2_get_vendor_info(c, &info);
if (r < 0)
return log_error_errno(r, "Failed to acquire TPM2 vendor information: %m");
_cleanup_(table_unrefp) Table *table = table_new_vertical();
if (!table)
return log_oom();
if (!isempty(info.family_indicator)) {
r = table_add_many(
table,
TABLE_FIELD, "Family Indicator",
TABLE_STRING, info.family_indicator);
if (r < 0)
return table_log_add_error(r);
}
_cleanup_free_ char *rv = NULL;
if (asprintf(&rv, "%" PRIu32 ".%" PRIu32,
info.revision_major,
info.revision_minor) < 0)
return log_oom();
r = table_add_many(
table,
TABLE_FIELD, "Level",
TABLE_UINT32, info.level,
TABLE_FIELD, "Revision",
TABLE_STRING, rv);
if (r < 0)
return table_log_add_error(r);
if (info.year >= 1900) {
struct tm tm = {
.tm_year = info.year - 1900,
.tm_mon = 0, /* january */
.tm_mday = info.day_of_year, /* timegm() will normalize this */
};
usec_t ts;
r = mktime_or_timegm_usec(&tm, /* utc= */ true, &ts);
if (r < 0)
log_debug_errno(r, "Failed to convert the specification date, ignoring.");
else {
r = table_add_many(
table,
TABLE_FIELD, "Specification Date",
TABLE_TIMESTAMP_DATE, ts);
if (r < 0)
return table_log_add_error(r);
}
}
if (!isempty(info.manufacturer)) {
r = table_add_many(
table,
TABLE_FIELD, "Manufacturer",
TABLE_STRING, info.manufacturer);
if (r < 0)
return table_log_add_error(r);
}
if (!isempty(info.vendor_string)) {
r = table_add_many(
table,
TABLE_FIELD, "Vendor String",
TABLE_STRING, info.vendor_string);
if (r < 0)
return table_log_add_error(r);
}
if (info.vendor_tpm_type != 0) {
r = table_add_many(
table,
TABLE_FIELD, "Vendor TPM Type",
TABLE_UINT32_HEX_0x, info.vendor_tpm_type);
if (r < 0)
return table_log_add_error(r);
}
/* Show the first two 16bit words of the firmware version as major/minor */
_cleanup_free_ char *fw = NULL;
if (asprintf(&fw, "%" PRIu16 ".%" PRIu16,
info.firmware_version_major,
info.firmware_version_minor) < 0)
return log_oom();
/* Show the second 32bit as a single value, if non-zero */
if (info.firmware_version2 != 0 && strextendf(&fw, ".%" PRIu32, info.firmware_version2) < 0)
return log_oom();
r = table_add_many(
table,
TABLE_FIELD, "Firmware Version",
TABLE_STRING, fw);
if (r < 0)
return table_log_add_error(r);
_cleanup_free_ char *m = NULL;
if (tpm2_vendor_info_to_modalias(&info, &m) < 0)
return log_oom();
r = table_add_many(
table,
TABLE_FIELD, "Modalias String",
TABLE_STRING, m);
if (r < 0)
return table_log_add_error(r);
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
if (r < 0)
return r;
return EXIT_SUCCESS;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not enabled at build time.");
#endif
}
+1
View File
@@ -2,3 +2,4 @@
#pragma once
int verb_has_tpm2(int argc, char *argv[], void *userdata);
int verb_identify_tpm2(int argc, char *argv[], void *userdata);
+2
View File
@@ -258,6 +258,7 @@ static int help(int argc, char *argv[], void *userdata) {
" dlopen-metadata FILE Parse and print ELF dlopen metadata\n"
"\n%3$sTPM Operations:%4$s\n"
" has-tpm2 Report whether TPM2 support is available\n"
" identify-tpm2 Show TPM2 vendor information\n"
" pcrs [PCR...] Show TPM2 PCRs and their names\n"
" nvpcrs [NVPCR...] Show additional TPM2 PCRs stored in NV indexes\n"
" srk [>FILE] Write TPM2 SRK (to FILE)\n"
@@ -810,6 +811,7 @@ static int run(int argc, char *argv[]) {
{ "fdstore", 2, VERB_ANY, 0, verb_fdstore },
{ "image-policy", 2, 2, 0, verb_image_policy },
{ "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 },
{ "identify-tpm2", VERB_ANY, 1, 0, verb_identify_tpm2 },
{ "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs },
{ "nvpcrs", VERB_ANY, VERB_ANY, 0, verb_nvpcrs },
{ "srk", VERB_ANY, 1, 0, verb_srk },
+19 -1
View File
@@ -47,9 +47,11 @@ int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) {
return r;
}
int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) {
int dlopen_verbose(void **dlp, const char *filename) {
int r;
assert(dlp);
if (*dlp)
return 0; /* Already loaded */
@@ -62,6 +64,22 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l
}
log_debug("Loaded shared library '%s' via dlopen().", filename);
*dlp = TAKE_PTR(dl);
return 1;
}
int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) {
int r;
assert(dlp);
if (*dlp)
return 0; /* Already loaded */
_cleanup_(dlclosep) void *dl = NULL;
r = dlopen_verbose(&dl, filename);
if (r < 0)
return r;
va_list ap;
va_start(ap, log_level);
+1
View File
@@ -11,6 +11,7 @@ static inline void dlclosep(void **dlp) {
safe_dlclose(*dlp);
}
int dlopen_verbose(void **dlp, const char *filename);
int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_;
int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_;
+236 -5
View File
@@ -3,6 +3,8 @@
#include <sys/file.h>
#include <unistd.h>
#include "sd-device.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "bitfield.h"
@@ -11,6 +13,8 @@
#include "constants.h"
#include "creds-util.h"
#include "cryptsetup-util.h"
#include "device-private.h"
#include "device-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "efi-api.h"
@@ -43,6 +47,7 @@
#include "time-util.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
#include "unaligned.h"
#include "virt.h"
#if HAVE_OPENSSL
@@ -53,6 +58,7 @@
static void *libtss2_esys_dl = NULL;
static void *libtss2_rc_dl = NULL;
static void *libtss2_mu_dl = NULL;
static void *libtss2_tcti_device_dl = NULL;
static DLSYM_PROTOTYPE(Esys_Create) = NULL;
static DLSYM_PROTOTYPE(Esys_CreateLoaded) = NULL;
@@ -226,6 +232,20 @@ static int dlopen_tpm2_mu(void) {
DLSYM_ARG(Tss2_MU_UINT32_Marshal));
}
static int dlopen_tpm2_tcti_device(void) {
/* The "device" TCTI is the most relevant one, let's also load it explicitly on dlopen_tpm2(), even
* if we don't resolve any symbols here. */
ELF_NOTE_DLOPEN("tpm",
"Support for TPM",
ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
"libtss2-tcti-device.so.0");
return dlopen_verbose(
&libtss2_tcti_device_dl,
"libtss2-tcti-device.so.0");
}
#endif
int dlopen_tpm2(void) {
@@ -244,6 +264,10 @@ int dlopen_tpm2(void) {
if (r < 0)
return r;
r = dlopen_tpm2_tcti_device();
if (r < 0)
return r;
return 0;
#else
return -EOPNOTSUPP;
@@ -329,6 +353,151 @@ static int tpm2_get_capability(
return more == TPM2_YES;
}
int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret) {
_cleanup_free_ char *m = NULL;
assert(info);
assert(ret);
/* Closely inspired by kernel modalias strings, this distills information from the TPM vendor data
* into a string suitable for matching hwdb */
if (asprintf(&m,
"fi%s:"
"lv%" PRIu32 ":"
"rv%" PRIu32 ".%" PRIu32 ":"
"sy%" PRIu32 ":"
"sd%" PRIu32 ":"
"mf%s:"
"vs%s:"
"ty%" PRIx32 ":"
"fw%" PRIu16 ".%" PRIu16 ".%" PRIu32 ":",
info->family_indicator,
info->level,
info->revision_major,
info->revision_minor,
info->year,
info->day_of_year,
info->manufacturer,
info->vendor_string,
info->vendor_tpm_type,
info->firmware_version_major,
info->firmware_version_minor,
info->firmware_version2) < 0)
return -ENOMEM;
*ret = TAKE_PTR(m);
return 0;
}
static char *mangle_vendor_chars(char *c, size_t n) {
char *end = c;
assert(c || n == 0);
/* Suppress all control characters, whitespace and non-ASCII bytes */
for (char *x = c; x < c + n; x++) {
if (!IN_SET(*x, ' ', 0))
end = x + 1;
if ((unsigned char) *x <= (unsigned char) ' ' ||
(unsigned char) *x >= 127)
*x = '_';
}
/* Drop trailing spaces and NUL bytes */
*end = 0;
return c;
}
int tpm2_get_vendor_info(
Tpm2Context *c,
Tpm2VendorInfo *ret) {
int r;
assert(c);
assert(ret);
TPMU_CAPABILITIES capabilities = {};
r = tpm2_get_capability(
c,
TPM2_CAP_TPM_PROPERTIES,
TPM2_PT_FAMILY_INDICATOR,
TPM2_PT_FIRMWARE_VERSION_2 - TPM2_PT_FAMILY_INDICATOR + 1, /* get all relevant fields at once */
&capabilities);
if (r < 0)
return r;
Tpm2VendorInfo info = {};
for (uint32_t i = 0; i < capabilities.tpmProperties.count; i++) {
TPMS_TAGGED_PROPERTY *p = capabilities.tpmProperties.tpmProperty + i;
switch (p->property) {
case TPM2_PT_FAMILY_INDICATOR:
unaligned_write_be32(info.family_indicator, p->value);
mangle_vendor_chars(info.family_indicator, sizeof(info.family_indicator));
break;
case TPM2_PT_LEVEL:
info.level = p->value;
break;
case TPM2_PT_REVISION:
info.revision_major = p->value / 100;
info.revision_minor = p->value % 100;
break;
case TPM2_PT_DAY_OF_YEAR:
info.day_of_year = p->value;
break;
case TPM2_PT_YEAR:
info.year = p->value;
break;
case TPM2_PT_MANUFACTURER:
unaligned_write_be32(info.manufacturer, p->value);
mangle_vendor_chars(info.manufacturer, sizeof(info.manufacturer));
break;
case TPM2_PT_VENDOR_STRING_1:
unaligned_write_be32(info.vendor_string+0, p->value);
break;
case TPM2_PT_VENDOR_STRING_2:
unaligned_write_be32(info.vendor_string+4, p->value);
break;
case TPM2_PT_VENDOR_STRING_3:
unaligned_write_be32(info.vendor_string+8, p->value);
break;
case TPM2_PT_VENDOR_STRING_4:
unaligned_write_be32(info.vendor_string+12, p->value);
break;
case TPM2_PT_VENDOR_TPM_TYPE:
info.vendor_tpm_type = p->value;
break;
case TPM2_PT_FIRMWARE_VERSION_1:
info.firmware_version_major = p->value >> 16;
info.firmware_version_minor = p->value & 0xFFFFU;
break;
case TPM2_PT_FIRMWARE_VERSION_2:
info.firmware_version2 = p->value;
break;
}
}
mangle_vendor_chars(info.vendor_string, sizeof(info.vendor_string));
*ret = TAKE_STRUCT(info);
return 0;
}
#define TPMA_CC_TO_TPM2_CC(cca) (((cca) & TPMA_CC_COMMANDINDEX_MASK) >> TPMA_CC_COMMANDINDEX_SHIFT)
static int tpm2_cache_capabilities(Tpm2Context *c) {
@@ -671,6 +840,9 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) {
c->capability_commands = mfree(c->capability_commands);
c->capability_ecc_curves = mfree(c->capability_ecc_curves);
c->tcti_driver = mfree(c->tcti_driver);
c->tcti_param = mfree(c->tcti_param);
return mfree(c);
}
@@ -716,7 +888,8 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
}
if (device) {
const char *param, *driver, *fn;
_cleanup_free_ char *_driver = NULL;
const char *param, *driver;
const TSS2_TCTI_INFO* info;
TSS2_TCTI_INFO_FUNC func;
size_t sz = 0;
@@ -724,7 +897,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
param = strchr(device, ':');
if (param) {
/* Syntax #1: Pair of driver string and arbitrary parameter */
driver = strndupa_safe(device, param - device);
driver = _driver = strndup(device, param - device);
if (!driver)
return log_oom_debug();
if (isempty(driver))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing.");
@@ -738,7 +913,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
log_debug("Using TPM2 TCTI driver '%s' with device '%s'.", driver, param);
fn = strjoina("libtss2-tcti-", driver, ".so.0");
_cleanup_free_ char *fn = strjoin("libtss2-tcti-", driver, ".so.0");
if (!fn)
return log_oom_debug();
/* Better safe than sorry, let's refuse strings that cannot possibly be valid driver early, before going to disk. */
if (!filename_is_valid(fn))
@@ -778,6 +955,14 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
if (rc != TPM2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
context->tcti_driver = strdup(driver);
if (!context->tcti_driver)
return log_oom_debug();
context->tcti_param = strdup(param);
if (!context->tcti_param)
return log_oom_debug();
}
rc = sym_Esys_Initialize(&context->esys_context, context->tcti_context, NULL);
@@ -5976,7 +6161,8 @@ int tpm2_undefine_nv_index(
return 0;
}
int tpm2_define_nvpcr_nv_index(
#if HAVE_OPENSSL
static int tpm2_define_nvpcr_nv_index(
Tpm2Context *c,
const Tpm2Handle *session,
TPM2_HANDLE nv_index,
@@ -6095,7 +6281,7 @@ int tpm2_define_nvpcr_nv_index(
return 1;
}
int tpm2_extend_nvpcr_nv_index(
static int tpm2_extend_nvpcr_nv_index(
Tpm2Context *c,
TPM2_HANDLE nv_index,
const Tpm2Handle *nv_handle,
@@ -6136,6 +6322,7 @@ int tpm2_extend_nvpcr_nv_index(
return 0;
}
#endif
int tpm2_read_nv_index(
Tpm2Context *c,
@@ -7307,6 +7494,44 @@ int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary) {
#endif
}
#if HAVE_OPENSSL
static int tpm2_context_can_nvindex(Tpm2Context *c) {
int r;
assert(c);
if (!streq_ptr(c->tcti_driver, "device")) {
log_debug("Not checking udev database, because not using 'device' TCTI.");
return 1;
}
if (!c->tcti_param) {
log_debug("No device specified, not checking udev database.");
return 1;
}
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
r = sd_device_new_from_devname(&d, c->tcti_param);
if (r < 0)
return log_debug_errno(r, "Failed to acquire udev entry for device '%s': %m", c->tcti_param);
r = device_get_property_bool(d, "TPM2_BROKEN_NVPCR");
if (r == -ENOENT) {
log_device_debug_errno(d, r, "No TPM2_BROKEN_NVPCR property for '%s', assuming NvPCRs work.", c->tcti_param);
return 1;
}
if (r < 0)
return log_device_debug_errno(d, r, "Failed to query TPM2_BROKEN_NVPCR for '%s': %m", c->tcti_param);
if (r > 0) {
log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set, NvPCRs do not work.", c->tcti_param);
return 0;
}
log_device_debug(d, "TPM2_BROKEN_NVPCR property for '%s' explicitly set to false, hence NvPCRs work.", c->tcti_param);
return 1;
}
#endif
int tpm2_nvpcr_initialize(
Tpm2Context *c,
const Tpm2Handle *session,
@@ -7320,6 +7545,12 @@ int tpm2_nvpcr_initialize(
assert(c);
assert(name);
r = tpm2_context_can_nvindex(c);
if (r < 0)
return r;
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 device does not support NvPCRs, not initializing.");
_cleanup_(nvpcr_data_done) NvPCRData p = {};
r = nvpcr_data_load(name, &p);
if (r < 0)
+20 -2
View File
@@ -53,6 +53,8 @@ typedef struct Tpm2Context {
void *tcti_dl;
TSS2_TCTI_CONTEXT *tcti_context;
ESYS_CONTEXT *esys_context;
char *tcti_driver;
char *tcti_param;
/* Some selected cached capabilities of the TPM */
TPMS_ALG_PROPERTY *capability_algorithms;
@@ -195,6 +197,24 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) {
return tpm2_digest_many(alg, digest, NULL, 0, false);
}
typedef struct Tpm2VendorInfo {
uint32_t level;
uint32_t revision_major;
uint32_t revision_minor;
uint32_t day_of_year;
uint32_t year;
uint32_t vendor_tpm_type;
uint16_t firmware_version_major;
uint16_t firmware_version_minor;
uint32_t firmware_version2;
char family_indicator[4+1];
char manufacturer[4+1];
char vendor_string[4*4+1];
} Tpm2VendorInfo;
int tpm2_vendor_info_to_modalias(const Tpm2VendorInfo *info, char **ret);
int tpm2_get_vendor_info(Tpm2Context *c, Tpm2VendorInfo *ret);
void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg);
void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg);
void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg);
@@ -304,8 +324,6 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing
int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest);
int tpm2_define_nvpcr_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, Tpm2Handle **ret_nv_handle);
int tpm2_extend_nvpcr_nv_index(Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const struct iovec *digest);
int tpm2_undefine_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
int tpm2_read_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, struct iovec *ret_value);
+3
View File
@@ -309,6 +309,9 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED SD_ID128_MAKE(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94)
#define SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR SD_ID128_MAKE_STR(ab,98,4e,a0,08,96,4f,b8,8d,6e,38,9f,b5,13,fb,94)
#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED SD_ID128_MAKE(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed)
#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR SD_ID128_MAKE_STR(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed)
_SD_END_DECLARATIONS;
#endif
+26 -9
View File
@@ -2,6 +2,7 @@
#include <getopt.h>
#include <sys/stat.h>
#include <sysexits.h>
#include "sd-messages.h"
@@ -291,8 +292,8 @@ static int setup_srk(void) {
log_struct_errno(LOG_INFO, r,
LOG_MESSAGE("Insufficient permissions to access TPM, not generating SRK."),
LOG_MESSAGE_ID(SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR));
return 76; /* Special return value which means "Insufficient permissions to access TPM,
* cannot generate SRK". This isn't really an error when called at boot. */;
return EX_PROTOCOL; /* Special return value which means "Insufficient permissions to access TPM,
* cannot generate SRK". This isn't really an error when called at boot. */;
}
if (r < 0)
return r;
@@ -422,6 +423,12 @@ static int setup_nvpcr_one(
r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret);
}
if (r == -EOPNOTSUPP) {
c->n_failed++;
return log_struct_errno(LOG_ERR, r,
LOG_MESSAGE("The TPM does not correctly support NV indexes in NT_EXTEND mode, unable to allocate NvPCR '%s': %m", name),
LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR));
}
if (r == -ENOSPC) {
c->n_failed++;
return log_struct_errno(LOG_ERR, r,
@@ -446,7 +453,7 @@ static int setup_nvpcr_one(
static int setup_nvpcr(void) {
_cleanup_(setup_nvpcr_context_done) SetupNvPCRContext c = {};
int r = 0;
int r;
_cleanup_strv_free_ char **l = NULL;
r = conf_files_list_nulstr(
@@ -471,13 +478,11 @@ static int setup_nvpcr(void) {
RET_GATHER(ret, setup_nvpcr_one(&c, *i));
}
if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) {
if (c.n_already > 0 && c.n_anchored == 0 && !arg_early)
/* If we didn't anchor anything right now, but we anchored something earlier, then it might
* have happened in the initrd, and thus the anchor ID was not committed to /var/ or the ESP
* yet. Hence, let's explicitly do so now, to catch up. */
RET_GATHER(ret, tpm2_nvpcr_acquire_anchor_secret(/* ret= */ NULL, /* sync_secondary= */ true));
}
if (c.n_failed > 0)
log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed);
@@ -492,6 +497,13 @@ static int setup_nvpcr(void) {
else if (c.n_failed == 0)
log_debug("No NvPCRs defined, nothing initialized.");
/* Turn some errors into recognizable ones, which we can catch with
* SuccessExitStatus= in the service unit file. */
if (ret == -EOPNOTSUPP)
return EX_UNAVAILABLE; /* e.g. no NvPCR support in TPM */
if (ret == -ENOSPC)
return EX_CANTCREAT; /* NV index space on TPM exhausted */
return ret;
}
@@ -511,10 +523,15 @@ static int run(int argc, char *argv[]) {
umask(0022);
/* Execute both jobs, and then return unlisted errors preferably, and listed errors
* (i.e. EX_UNAVAILABLE, EX_CANTCREAT, EX_PROTOCOL) otherwise. */
r = setup_srk();
RET_GATHER(r, setup_nvpcr());
return r;
int k = setup_nvpcr();
if (r < 0)
return r;
if (k < 0)
return k;
return r != EXIT_SUCCESS ? r : k;
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
+4
View File
@@ -59,6 +59,10 @@ if conf.get('HAVE_ACL') == 1
udevadm_extract_sources += files('udev-builtin-uaccess.c')
endif
if conf.get('HAVE_TPM2') == 1
udevadm_extract_sources += files('udev-builtin-tpm2_id.c')
endif
############################################################
generate_keyboard_keys_list = files('generate-keyboard-keys-list.sh')
+60
View File
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "device-util.h"
#include "string-util.h"
#include "tpm2-util.h"
#include "udev-builtin.h"
static int builtin_tpm2_id(UdevEvent *event, int argc, char *argv[]) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
int r;
if (argc != 2 || !streq(argv[1], "identify"))
return log_device_error_errno(
dev, SYNTHETIC_ERRNO(EINVAL), "%s: expected: identify", argv[0]);
const char *dn;
r = sd_device_get_devname(dev, &dn);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to get device node for device: %m");
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
r = tpm2_context_new(dn, &c);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to open device node '%s': %m", dn);
Tpm2VendorInfo info;
r = tpm2_get_vendor_info(c, &info);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to acquire TPM2 vendor information: %m");
if (!isempty(info.manufacturer)) {
r = udev_builtin_add_property(event, "ID_TPM2_MANUFACTURER", info.manufacturer);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to set field: %m");
}
if (!isempty(info.vendor_string)) {
r = udev_builtin_add_property(event, "ID_TPM2_VENDOR_STRING", info.vendor_string);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to set field: %m");
}
_cleanup_free_ char *m = NULL;
r = tpm2_vendor_info_to_modalias(&info, &m);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to get modalias string for TPM2 device: %m");
r = udev_builtin_add_property(event, "ID_TPM2_MODALIAS", m);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to set field: %m");
return 0;
}
const UdevBuiltin udev_builtin_tpm2_id = {
.name = "tpm2_id",
.cmd = builtin_tpm2_id,
.help = "Identify TPM2 chips",
.run_once = true,
};
+3
View File
@@ -29,6 +29,9 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = {
[UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id,
[UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link,
[UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id,
#if HAVE_TPM2
[UDEV_BUILTIN_TPM2_ID] = &udev_builtin_tpm2_id,
#endif
#if HAVE_ACL
[UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess,
#endif
+3
View File
@@ -43,6 +43,9 @@ extern const UdevBuiltin udev_builtin_net_driver;
extern const UdevBuiltin udev_builtin_net_id;
extern const UdevBuiltin udev_builtin_net_setup_link;
extern const UdevBuiltin udev_builtin_path_id;
#if HAVE_TPM2
extern const UdevBuiltin udev_builtin_tpm2_id;
#endif
#if HAVE_ACL
extern const UdevBuiltin udev_builtin_uaccess;
#endif
+3
View File
@@ -51,6 +51,9 @@ typedef enum UdevBuiltinCommand {
UDEV_BUILTIN_NET_ID,
UDEV_BUILTIN_NET_LINK,
UDEV_BUILTIN_PATH_ID,
#if HAVE_TPM2
UDEV_BUILTIN_TPM2_ID,
#endif
#if HAVE_ACL
UDEV_BUILTIN_UACCESS,
#endif
+2
View File
@@ -19,6 +19,7 @@
#include "process-util.h"
#include "rlimit-util.h"
#include "terminal-util.h"
#include "tpm2-util.h"
#include "udev-config.h"
#include "udev-manager.h"
#include "udevd.h"
@@ -61,6 +62,7 @@ int run_udevd(int argc, char *argv[]) {
(void) dlopen_libblkid();
(void) dlopen_libkmod();
(void) dlopen_libmount();
(void) dlopen_tpm2();
if (arg_daemonize) {
pid_t pid;
+3
View File
@@ -103,3 +103,6 @@ systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/nvp
set +o pipefail
diff /tmp/nvpcr/log-before /run/log/systemd/tpm2-measure.log | grep -F '"content":{"nvIndexName":"verity","string":"verity:'
systemd-analyze identify-tpm2
udevadm test-builtin 'tpm2_id identify' /dev/tpmrm0
+6 -2
View File
@@ -22,5 +22,9 @@ Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --early=yes --graceful
# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK.
SuccessExitStatus=76
# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK.
SuccessExitStatus=PROTOCOL
# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device.
SuccessExitStatus=UNAVAILABLE
# The tool returns EX_CANTCREAT if the NV index space is exhausted.
SuccessExitStatus=CANTCREAT
+6 -2
View File
@@ -23,5 +23,9 @@ Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-tpm2-setup --graceful
# The tool returns 76 if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK.
SuccessExitStatus=76
# The tool returns EX_PROTOCOL if the TPM cannot be accessed due to an authorization failure and we can't generate an SRK.
SuccessExitStatus=PROTOCOL
# The tool returns EX_UNAVAILABLE if some key functionality (e.g. NvPCR) support is not available in the TPM device.
SuccessExitStatus=UNAVAILABLE
# The tool returns EX_CANTCREAT if the NV index space is exhausted.
SuccessExitStatus=CANTCREAT