diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 378d1b0e8f..c44e78c4ca 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -21,6 +21,8 @@ #include "pretty-print.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "varlink.h" +#include "varlink-io.systemd.PCRExtend.h" static bool arg_graceful = false; static char *arg_tpm2_device = NULL; @@ -28,11 +30,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 bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep); +#define EXTENSION_STRING_SAFE_LIMIT 1024 + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -165,7 +170,12 @@ static int parse_argv(int argc, char *argv[]) { 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_pcr_index == UINT_MAX) + r = varlink_invocation(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) arg_pcr_index = (arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */ TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */ @@ -257,10 +267,119 @@ static int get_file_system_word( return 0; } +static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + int r; + + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; + + r = determine_banks(c, pcr); + if (r < 0) + return r; + if (strv_isempty(arg_banks)) /* Still none? */ + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate."); + + _cleanup_free_ char *joined_banks = NULL; + joined_banks = strv_join(arg_banks, ", "); + if (!joined_banks) + 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(); + } + + log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks); + + r = tpm2_extend_bytes(c, arg_banks, pcr, data, size, /* secret= */ NULL, /* secret_size= */ 0, event, safe); + if (r < 0) + return log_error_errno(r, "Could not extend PCR: %m"); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, + LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks), + "MEASURING=%s", safe, + "PCR=%u", pcr, + "BANKS=%s", joined_banks); + + return 0; +} + +typedef struct MethodExtendParameters { + unsigned pcr; + const char *text; + void *data; + size_t data_size; +} MethodExtendParameters; + +static int json_dispatch_binary_data(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + MethodExtendParameters *p = ASSERT_PTR(userdata); + _cleanup_free_ void *d = NULL; + size_t l; + int r; + + r = json_variant_unbase64(variant, &d, &l); + if (r < 0) + return json_log(variant, flags, r, "JSON variant is not a base64 string."); + + free_and_replace(p->data, d); + p->data_size = l; + + return 0; +} + +static int vl_method_extend(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "pcr", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MethodExtendParameters, pcr), JSON_MANDATORY }, + { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 }, + { "data", JSON_VARIANT_STRING, json_dispatch_binary_data, 0, 0 }, + {} + }; + MethodExtendParameters p = { + .pcr = UINT_MAX, + }; + int r; + + assert(link); + + r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); + if (r < 0) + return r; + + if (!TPM2_PCR_INDEX_VALID(p.pcr)) + return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "pcr"))); + + if (p.text) { + /* Specifying both the text string and the binary data is not allowed */ + if (p.data) + return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "data"))); + + r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID); + } else if (p.data) + r = extend_now(p.pcr, p.data, p.data_size, _TPM2_USERSPACE_EVENT_TYPE_INVALID); + else + return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "text"))); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + static int run(int argc, char *argv[]) { - _cleanup_free_ char *joined = NULL, *word = NULL; + _cleanup_free_ char *word = NULL; Tpm2UserspaceEventType event; - size_t length; int r; log_setup(); @@ -269,6 +388,30 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + if (arg_file_system) { _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL; _cleanup_(sd_device_unrefp) sd_device *d = NULL; @@ -348,8 +491,6 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - length = strlen(word); - /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */ r = efi_measured_uki(LOG_ERR); if (r < 0) @@ -359,34 +500,10 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new(arg_tpm2_device, &c); + r = extend_now(arg_pcr_index, word, strlen(word), event); if (r < 0) return log_error_errno(r, "Failed to create TPM2 context: %m"); - r = determine_banks(c, arg_pcr_index); - if (r < 0) - return r; - if (strv_isempty(arg_banks)) /* Still none? */ - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate."); - - joined = strv_join(arg_banks, ", "); - if (!joined) - return log_oom(); - - log_debug("Measuring '%s' into PCR index %u, banks %s.", word, arg_pcr_index, joined); - - r = tpm2_extend_bytes(c, arg_banks, arg_pcr_index, word, length, NULL, 0, event, word); - if (r < 0) - return log_error_errno(r, "Could not extend PCR: %m"); - - log_struct(LOG_INFO, - "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, - LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", arg_pcr_index, word, joined), - "MEASURING=%s", word, - "PCR=%u", arg_pcr_index, - "BANKS=%s", joined); - return EXIT_SUCCESS; } diff --git a/src/shared/meson.build b/src/shared/meson.build index 9d29c32dff..569b865636 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -170,6 +170,7 @@ shared_sources = files( 'varlink-io.systemd.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.ManagedOOM.c', + 'varlink-io.systemd.PCRExtend.c', 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.UserDatabase.c', diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c new file mode 100644 index 0000000000..37d403f0ee --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRExtend.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.PCRExtend.h" + +static VARLINK_DEFINE_METHOD( + Extend, + VARLINK_DEFINE_INPUT(pcr, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_PCRExtend, + "io.systemd.PCRExtend", + &vl_method_Extend); diff --git a/src/shared/varlink-io.systemd.PCRExtend.h b/src/shared/varlink-io.systemd.PCRExtend.h new file mode 100644 index 0000000000..ffc075af2c --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRExtend.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_PCRExtend; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index cd85e2c106..a93d717f3f 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -10,6 +10,7 @@ #include "varlink-io.systemd.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.ManagedOOM.h" +#include "varlink-io.systemd.PCRExtend.h" #include "varlink-io.systemd.Resolve.Monitor.h" #include "varlink-io.systemd.Resolve.h" #include "varlink-io.systemd.UserDatabase.h" @@ -134,6 +135,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_PCRExtend); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } diff --git a/units/meson.build b/units/meson.build index e1de2b4a6d..b089cde811 100644 --- a/units/meson.build +++ b/units/meson.build @@ -430,6 +430,15 @@ units = [ 'file' : 'systemd-oomd.socket', 'conditions' : ['ENABLE_OOMD'], }, + { + 'file' : 'systemd-pcrextend@.service.in', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + }, + { + 'file' : 'systemd-pcrextend.socket', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'systemd-pcrfs-root.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], diff --git a/units/systemd-pcrextend.socket b/units/systemd-pcrextend.socket new file mode 100644 index 0000000000..6d7b8ff84c --- /dev/null +++ b/units/systemd-pcrextend.socket @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=TPM2 PCR Extension (Varlink) +Documentation=man:systemd-pcrextend(8) +DefaultDependencies=no +Before=sockets.target +ConditionSecurity=measured-uki + +[Socket] +ListenStream=/run/systemd/io.systemd.PCRExtend +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-pcrextend@.service.in b/units/systemd-pcrextend@.service.in new file mode 100644 index 0000000000..2305b1cd4c --- /dev/null +++ b/units/systemd-pcrextend@.service.in @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=TPM2 PCR Extension (Varlink) +Documentation=man:systemd-pcrphase.service(8) +DefaultDependencies=no +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target + +[Service] +Environment=LISTEN_FDNAMES=varlink +ExecStart=-{{LIBEXECDIR}}/systemd-pcrextend