From: Lennart Poettering Date: Mon, 3 Jun 2024 19:44:50 +0000 (+0200) Subject: pcrextend: make use new nvindex-based PCRs X-Git-Tag: v259-rc1~183^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2b90bf173081758e75bdb2b8ff376731b0268ede;p=thirdparty%2Fsystemd.git pcrextend: make use new nvindex-based PCRs --- diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index cb1142b1940..4152112078c 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -743,6 +743,19 @@ PCRs ares extended with a different string, to ensure that security policies for TPM-bound secrets and other resources are limited to specific phases of the runtime. +-- 4c2e46d266a747c6ac1460aa54484fa7 +Subject: TPM NvPCR Extended +Defined-By: systemd +Support: %SUPPORT_URL% + +The Trusted Platform Module's (TPM) additional Platform Configuration Register +(PCR) stored in non-volatile indexes (NV Indexes) @NVPCR@, has been extended +with the string '@MEASURING@'. + +System state, configuration and properties are cryptographically measured into +the security chip in an irreversible way to provide local and remote +attestation of system state and identity. + -- f9b0be465ad540d0850ad32172d57c21 Subject: Memory Trimmed Defined-By: systemd diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml index e301435f8ea..a00eda08bb1 100644 --- a/man/systemd-pcrphase.service.xml +++ b/man/systemd-pcrphase.service.xml @@ -138,7 +138,8 @@ Options The /usr/lib/systemd/system-pcrextend executable may also be invoked from the - command line, where it expects the word to extend into PCR 11, as well as the following switches: + command line, where it expects the word to extend into a PCR or NvPCR, as well as the following + switches: @@ -155,11 +156,22 @@ Takes the index of the PCR to extend. If or - are specified defaults to 15, otherwise defaults to 11. + are specified defaults to 15, otherwise defaults to 11. May not be + combined with . + + + + Takes a name of an NvPCR to extend. NvPCRs are additional PCRs implemented via TPM NV + indexes. The name should be a short string such as hardware or + disk-encryption. May not be combined with . + + + + @@ -182,6 +194,17 @@ + + + + Selects early-boot mode. Specifically this means that the NvPCR anchor secret is not + attempted to be written into /var/lib/ and the boot loader partition in addition + to /run/. (Unlike the latter the former are generally not mounted and writable + during early boot or in the initrd.) + + + + @@ -230,6 +253,13 @@ + + + /usr/lib/nvpcr/*.nvpcr + + Definition files for NvPCRs. + + diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index cc50ad3ff79..68d90d4e4a3 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -28,11 +28,14 @@ static char **arg_banks = NULL; static char *arg_file_system = NULL; static bool arg_machine_id = false; static unsigned arg_pcr_index = UINT_MAX; +static char *arg_nvpcr_name = NULL; static bool arg_varlink = false; +static bool arg_early = false; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep); +STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep); #define EXTENSION_STRING_SAFE_LIMIT 1024 @@ -53,10 +56,12 @@ static int help(int argc, char *argv[], void *userdata) { " --version Print version\n" " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n" " --pcr=INDEX Select TPM PCR index (0…23)\n" + " --nvpcr=NAME Select TPM PCR mode nvindex name\n" " --tpm2-device=PATH Use specified TPM2 device\n" " --graceful Exit gracefully if no TPM2 device is found\n" " --file-system=PATH Measure UUID/labels of file system into PCR 15\n" " --machine-id Measure machine ID into PCR 15\n" + " --early Run in early boot mode, without access to /var/\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -73,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_BANK, ARG_PCR, + ARG_NVPCR, ARG_TPM2_DEVICE, ARG_GRACEFUL, ARG_FILE_SYSTEM, ARG_MACHINE_ID, + ARG_EARLY, }; static const struct option options[] = { @@ -84,10 +91,12 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "bank", required_argument, NULL, ARG_BANK }, { "pcr", required_argument, NULL, ARG_PCR }, + { "nvpcr", required_argument, NULL, ARG_NVPCR }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "graceful", no_argument, NULL, ARG_GRACEFUL }, { "file-system", required_argument, NULL, ARG_FILE_SYSTEM }, { "machine-id", no_argument, NULL, ARG_MACHINE_ID }, + { "early", no_argument, NULL, ARG_EARLY }, {} }; @@ -127,6 +136,15 @@ static int parse_argv(int argc, char *argv[]) { arg_pcr_index = r; break; + case ARG_NVPCR: + if (!tpm2_nvpcr_name_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", optarg); + + r = free_and_strdup_warn(&arg_nvpcr_name, optarg); + if (r < 0) + return r; + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -158,6 +176,10 @@ static int parse_argv(int argc, char *argv[]) { arg_machine_id = true; break; + case ARG_EARLY: + arg_early = true; + break; + case '?': return -EINVAL; @@ -165,15 +187,18 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - if (arg_file_system && arg_machine_id) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined."); + if (!!arg_file_system + arg_machine_id > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id may not be combined."); + + if (arg_pcr_index != UINT_MAX && arg_nvpcr_name) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined."); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); if (r > 0) arg_varlink = true; - else if (arg_pcr_index == UINT_MAX) + else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) arg_pcr_index = (arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */ TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */ @@ -198,7 +223,34 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { return 0; } -static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) { +static int escape_and_truncate_data(const void *data, size_t size, char **ret) { + _cleanup_free_ char *safe = NULL; + + assert(data || size == 0); + + if (size > EXTENSION_STRING_SAFE_LIMIT) { + safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT); + if (!safe) + return -ENOMEM; + + if (!strextend(&safe, "...")) + return -ENOMEM; + } else { + safe = cescape_length(data, size); + if (!safe) + return -ENOMEM; + } + + *ret = TAKE_PTR(safe); + return 0; +} + +static int extend_pcr_now( + unsigned pcr, + const void *data, + size_t size, + Tpm2UserspaceEventType event) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; @@ -218,18 +270,8 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace return log_oom(); _cleanup_free_ char *safe = NULL; - if (size > EXTENSION_STRING_SAFE_LIMIT) { - safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT); - if (!safe) - return log_oom(); - - if (!strextend(&safe, "...")) - return log_oom(); - } else { - safe = cescape_length(data, size); - if (!safe) - return log_oom(); - } + if (escape_and_truncate_data(data, size, &safe) < 0) + return log_oom(); log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks); @@ -247,8 +289,55 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace return 0; } +static int extend_nvpcr_now( + const char *name, + const void *data, + size_t size, + Tpm2UserspaceEventType event) { + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + int r; + + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); + if (r < 0) + return r; + + _cleanup_free_ char *safe = NULL; + if (escape_and_truncate_data(data, size, &safe) < 0) + return log_oom(); + + log_debug("Measuring '%s' into NvPCR index '%s'.", safe, name); + + r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); + if (r == -ENETDOWN) { + /* NvPCR is not initialized yet. Let's do this now. */ + + _cleanup_(iovec_done_erase) struct iovec anchor_secret = {}; + r = tpm2_nvpcr_acquire_anchor_secret(&anchor_secret, /* sync_secondary= */ !arg_early); + if (r < 0) + return r; + + r = tpm2_nvpcr_initialize(c, /* session= */ NULL, name, &anchor_secret); + if (r < 0) + return log_error_errno(r, "Failed to extend NvPCR index '%s' with anchor secret: %m", name); + + r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); + } + if (r < 0) + return log_error_errno(r, "Could not extend NvPCR: %m"); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR, + LOG_MESSAGE("Extended NvPCR index '%s' with '%s'.", name, safe), + "MEASURING=%s", safe, + "NVPCR=%u", name); + + return 0; +} + typedef struct MethodExtendParameters { unsigned pcr; + const char *nvpcr; const char *text; struct iovec data; } MethodExtendParameters; @@ -262,9 +351,10 @@ static void method_extend_parameters_done(MethodExtendParameters *p) { static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), SD_JSON_MANDATORY }, - { "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 }, - { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 }, + { "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), 0 }, + { "nvpcr", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, nvpcr), 0 }, + { "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 }, {} }; _cleanup_(method_extend_parameters_done) MethodExtendParameters p = { @@ -278,19 +368,38 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va if (r != 0) return r; - if (!TPM2_PCR_INDEX_VALID(p.pcr)) + if (p.nvpcr) { + /* Specifying both nvpcr name and pcr doesn't make sense */ + if (p.pcr != UINT_MAX) + return sd_varlink_error_invalid_parameter_name(link, "nvpcr"); + + if (!tpm2_nvpcr_name_is_valid(p.nvpcr)) + return sd_varlink_error_invalid_parameter_name(link, "nvpcr"); + + } else if (!TPM2_PCR_INDEX_VALID(p.pcr)) return sd_varlink_error_invalid_parameter_name(link, "pcr"); + struct iovec *extend_iovec, text_iovec; + if (p.text) { /* Specifying both the text string and the binary data is not allowed */ if (p.data.iov_base) return sd_varlink_error_invalid_parameter_name(link, "data"); - r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID); + text_iovec = IOVEC_MAKE_STRING(p.text); + extend_iovec = &text_iovec; + } else if (p.data.iov_base) - r = extend_now(p.pcr, p.data.iov_base, p.data.iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID); + extend_iovec = &p.data; else return sd_varlink_error_invalid_parameter_name(link, "text"); + + if (p.nvpcr) { + r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID); + if (r == -ENOENT) + return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL); + } else + r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID); if (r < 0) return r; @@ -354,7 +463,6 @@ static int run(int argc, char *argv[]) { return r; event = TPM2_EVENT_MACHINE_ID; - } else { if (optind+1 != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); @@ -386,9 +494,12 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = extend_now(arg_pcr_index, word, strlen(word), event); + if (arg_nvpcr_name) + r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event); + else + r = extend_pcr_now(arg_pcr_index, word, strlen(word), event); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; return EXIT_SUCCESS; } diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c index d104fc0853b..84ee2e731ba 100644 --- a/src/shared/varlink-io.systemd.PCRExtend.c +++ b/src/shared/varlink-io.systemd.PCRExtend.c @@ -6,14 +6,18 @@ static SD_VARLINK_DEFINE_METHOD( Extend, SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23"), SD_VARLINK_DEFINE_INPUT(pcr, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_INPUT(nvpcr, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Text string to measure. (Specify either this, or the 'data' field below, not both)"), SD_VARLINK_DEFINE_INPUT(text, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Binary data to measure, encoded in Base64. (Specify either this, or the 'text' field above, not both)"), SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_PCRExtend, "io.systemd.PCRExtend", SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"), SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"), - &vl_method_Extend); + &vl_method_Extend, + &vl_error_NoSuchNvPCR); diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 02edf9facf7..6dc7dd527a0 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -272,6 +272,8 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_TPM_PCR_EXTEND SD_ID128_MAKE(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab) #define SD_MESSAGE_TPM_PCR_EXTEND_STR SD_ID128_MAKE_STR(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab) +#define SD_MESSAGE_TPM_NVPCR_EXTEND SD_ID128_MAKE(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7) +#define SD_MESSAGE_TPM_NVPCR_EXTEND_STR SD_ID128_MAKE_STR(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7) #define SD_MESSAGE_MEMORY_TRIM SD_ID128_MAKE(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21) #define SD_MESSAGE_MEMORY_TRIM_STR SD_ID128_MAKE_STR(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21)