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 - - + + <command>systemd-analyze identify-tpm2</command> + + 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: + + + + + <command>systemd-analyze pcrs <optional><replaceable>PCR</replaceable>…</optional></command> 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