diff --git a/NEWS b/NEWS
index 82b30951d2..792aee4761 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index 6f6ca9c4ea..68ef28d197 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -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.
diff --git a/hwdb.d/60-tpm2.hwdb b/hwdb.d/60-tpm2.hwdb
new file mode 100644
index 0000000000..2772bf3cde
--- /dev/null
+++ b/hwdb.d/60-tpm2.hwdb
@@ -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
diff --git a/hwdb.d/meson.build b/hwdb.d/meson.build
index 36a9937a60..9ba73b21d6 100644
--- a/hwdb.d/meson.build
+++ b/hwdb.d/meson.build
@@ -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',
diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py
index 1a56f40c46..e98510839b 100755
--- a/hwdb.d/parse_hwdb.py
+++ b/hwdb.d/parse_hwdb.py
@@ -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]
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index e64f9be57e..f3dfd7479f 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -195,6 +195,11 @@
OPTIONS
has-tpm2
+
+ systemd-analyze
+ OPTIONS
+ identify-tpm2
+
systemd-analyze
OPTIONS
@@ -1028,6 +1033,27 @@ default ignore - -
+
+ systemd-analyze identify-tpm2
+
+ Shows vendor information about the TPM 2.0 device discovered.
+
+
+ Example Output
+
+ 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:
+
+
+
+
+
systemd-analyze pcrs PCR…
diff --git a/rules.d/60-tpm2-id.rules b/rules.d/60-tpm2-id.rules
new file mode 100644
index 0000000000..1e08f3b8e5
--- /dev/null
+++ b/rules.d/60-tpm2-id.rules
@@ -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"
diff --git a/rules.d/meson.build b/rules.d/meson.build
index 839190658b..aa469ef7a7 100644
--- a/rules.d/meson.build
+++ b/rules.d/meson.build
@@ -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',
diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze
index a863af7aff..b194c74b63 100644
--- a/shell-completion/bash/systemd-analyze
+++ b/shell-completion/bash/systemd-analyze
@@ -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'
diff --git a/src/analyze/analyze-has-tpm2.c b/src/analyze/analyze-has-tpm2.c
index 3e13be9f16..2e7890cea1 100644
--- a/src/analyze/analyze-has-tpm2.c
+++ b/src/analyze/analyze-has-tpm2.c
@@ -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
+}
diff --git a/src/analyze/analyze-has-tpm2.h b/src/analyze/analyze-has-tpm2.h
index c7c639228d..b7d750090b 100644
--- a/src/analyze/analyze-has-tpm2.h
+++ b/src/analyze/analyze-has-tpm2.h
@@ -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);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index c69f03ec15..af456314db 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -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 },
diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c
index 8574e99546..23bd1f666e 100644
--- a/src/basic/dlfcn-util.c
+++ b/src/basic/dlfcn-util.c
@@ -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);
diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h
index 367c47ddb8..9760b31da4 100644
--- a/src/basic/dlfcn-util.h
+++ b/src/basic/dlfcn-util.h
@@ -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_;
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index e569436142..9e05c847ed 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -3,6 +3,8 @@
#include
#include
+#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)
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 68289bec48..51670e8b06 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -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);
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
index 865f7247ac..6aabe88196 100644
--- a/src/systemd/sd-messages.h
+++ b/src/systemd/sd-messages.h
@@ -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
diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c
index a4d323a70c..d243f199e9 100644
--- a/src/tpm2-setup/tpm2-setup.c
+++ b/src/tpm2-setup/tpm2-setup.c
@@ -2,6 +2,7 @@
#include
#include
+#include
#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);
diff --git a/src/udev/meson.build b/src/udev/meson.build
index 7df5b3de85..700a8cc8d5 100644
--- a/src/udev/meson.build
+++ b/src/udev/meson.build
@@ -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')
diff --git a/src/udev/udev-builtin-tpm2_id.c b/src/udev/udev-builtin-tpm2_id.c
new file mode 100644
index 0000000000..6edf618e11
--- /dev/null
+++ b/src/udev/udev-builtin-tpm2_id.c
@@ -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,
+};
diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c
index 5f559f5c9a..121694e688 100644
--- a/src/udev/udev-builtin.c
+++ b/src/udev/udev-builtin.c
@@ -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
diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h
index 3e830c77ea..504c4108c8 100644
--- a/src/udev/udev-builtin.h
+++ b/src/udev/udev-builtin.h
@@ -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
diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h
index 836dd70454..d5e02e50b6 100644
--- a/src/udev/udev-def.h
+++ b/src/udev/udev-def.h
@@ -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
diff --git a/src/udev/udevd.c b/src/udev/udevd.c
index ab3106334b..840febbd42 100644
--- a/src/udev/udevd.c
+++ b/src/udev/udevd.c
@@ -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;
diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh
index c5a6d1c213..29319e601a 100755
--- a/test/units/TEST-70-TPM2.nvpcr.sh
+++ b/test/units/TEST-70-TPM2.nvpcr.sh
@@ -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
diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in
index 7fdb99b53f..ce1ee94cc4 100644
--- a/units/systemd-tpm2-setup-early.service.in
+++ b/units/systemd-tpm2-setup-early.service.in
@@ -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
diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in
index 34404a24cb..dff516832d 100644
--- a/units/systemd-tpm2-setup.service.in
+++ b/units/systemd-tpm2-setup.service.in
@@ -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