From 15138e79804cb595829b51eeda3364ec1d70e813 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Feb 2024 15:17:09 +0100 Subject: [PATCH] pcrlock: add basic Varlink interface This can be used to make or delete a PCR policy via Varlink. It can also be used to query the current event log in CEL format. --- src/pcrlock/pcrlock.c | 141 +++++++++++++++++++++++- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.PCRLock.c | 24 ++++ src/shared/varlink-io.systemd.PCRLock.h | 6 + src/test/test-varlink-idl.c | 3 + units/meson.build | 9 ++ units/systemd-pcrlock.socket | 25 +++++ units/systemd-pcrlock@.service.in | 21 ++++ 8 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 src/shared/varlink-io.systemd.PCRLock.c create mode 100644 src/shared/varlink-io.systemd.PCRLock.h create mode 100644 units/systemd-pcrlock.socket create mode 100644 units/systemd-pcrlock@.service.in diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 016d73cc009..1dd3d86a73c 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -48,6 +48,8 @@ #include "unaligned.h" #include "unit-name.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.PCRLock.h" #include "verbs.h" static PagerFlags arg_pager_flags = 0; @@ -65,6 +67,7 @@ static char *arg_policy_path = NULL; static bool arg_force = false; static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; static char *arg_entry_token = NULL; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep); @@ -4310,7 +4313,7 @@ static int write_boot_policy_file(const char *json_text) { return 1; } -static int verb_make_policy(int argc, char *argv[], void *userdata) { +static int make_policy(bool force, bool recovery_pin) { int r; /* Here's how this all works: after predicting all possible PCR values for next boot (with @@ -4385,11 +4388,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); - if (!arg_force && + if (!force && old_policy.algorithm == el->primary_algorithm && tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { log_info("Prediction is identical to current policy, skipping update."); - return EXIT_SUCCESS; + return 0; /* NOP */ } } @@ -4434,7 +4437,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { /* Acquire a recovery PIN, either from the user, or create a randomized one */ _cleanup_(erase_and_freep) char *pin = NULL; - if (arg_recovery_pin) { + if (recovery_pin) { r = getenv_steal_erase("PIN", &pin); if (r < 0) return log_error_errno(r, "Failed to acquire PIN from environment: %m"); @@ -4712,7 +4715,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); - return 0; + return 1; /* installed new policy */ +} + +static int verb_make_policy(int argc, char *argv[], void *userdata) { + return make_policy(arg_force, arg_recovery_pin); } static int undefine_policy_nv_index( @@ -4768,7 +4775,7 @@ static int undefine_policy_nv_index( return 0; } -static int verb_remove_policy(int argc, char *argv[], void *userdata) { +static int remove_policy(void) { int ret = 0, r; _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; @@ -4807,6 +4814,10 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) { return ret; } +static int verb_remove_policy(int argc, char *argv[], void *userdata) { + return remove_policy(); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -5082,6 +5093,14 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } + 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; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -5124,6 +5143,88 @@ static int pcrlock_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +static int vl_method_read_event_log(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(event_log_freep) EventLog *el = NULL; + uint64_t recnum = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_load(el); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *rec_cel = NULL; + + FOREACH_ARRAY(rr, el->records, el->n_records) { + + if (rec_cel) { + r = varlink_notifyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("record", rec_cel))); + if (r < 0) + return r; + + rec_cel = json_variant_unref(rec_cel); + } + + r = event_log_record_to_cel(*rr, &recnum, &rec_cel); + if (r < 0) + return r; + } + + return varlink_replyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(rec_cel, "record", JSON_BUILD_VARIANT(rec_cel)))); +} + +typedef struct MethodMakePolicyParameters { + bool force; +} MethodMakePolicyParameters; + +static int vl_method_make_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMakePolicyParameters, force), 0 }, + {} + }; + MethodMakePolicyParameters p = {}; + int r; + + assert(link); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = make_policy(p.force, /* recovery_key= */ false); + if (r < 0) + return r; + if (r == 0) + return varlink_error(link, "io.systemd.PCRLock.NoChange", NULL); + + return varlink_reply(link, NULL); +} + +static int vl_method_remove_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = remove_policy(); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + static int run(int argc, char *argv[]) { int r; @@ -5133,6 +5234,34 @@ 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_PCRLock); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log, + "io.systemd.PCRLock.MakePolicy", vl_method_make_policy, + "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %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; + } + return pcrlock_main(argc, argv); } diff --git a/src/shared/meson.build b/src/shared/meson.build index dc9adeddc14..81de6708f01 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -180,6 +180,7 @@ shared_sources = files( 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Network.c', 'varlink-io.systemd.PCRExtend.c', + 'varlink-io.systemd.PCRLock.c', 'varlink-io.systemd.Resolve.c', 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.UserDatabase.c', diff --git a/src/shared/varlink-io.systemd.PCRLock.c b/src/shared/varlink-io.systemd.PCRLock.c new file mode 100644 index 00000000000..3b2c408bc9b --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.PCRLock.h" + +static VARLINK_DEFINE_METHOD( + ReadEventLog); + +static VARLINK_DEFINE_METHOD( + MakePolicy, + VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + RemovePolicy); + +VARLINK_DEFINE_ERROR( + NoChange); + +VARLINK_DEFINE_INTERFACE( + io_systemd_PCRLock, + "io.systemd.PCRLock", + &vl_method_ReadEventLog, + &vl_method_MakePolicy, + &vl_method_RemovePolicy, + &vl_error_NoChange); diff --git a/src/shared/varlink-io.systemd.PCRLock.h b/src/shared/varlink-io.systemd.PCRLock.h new file mode 100644 index 00000000000..687f09ef84b --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.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_PCRLock; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index d322c02c552..e5708b73b54 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -13,6 +13,7 @@ #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.PCRExtend.h" +#include "varlink-io.systemd.PCRLock.h" #include "varlink-io.systemd.Resolve.Monitor.h" #include "varlink-io.systemd.Resolve.h" #include "varlink-io.systemd.UserDatabase.h" @@ -143,6 +144,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_PCRExtend); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_PCRLock); + print_separator(); test_parse_format_one(&vl_interface_io_systemd_service); print_separator(); test_parse_format_one(&vl_interface_io_systemd_sysext); diff --git a/units/meson.build b/units/meson.build index efd2eac5835..acfd8d1dcbe 100644 --- a/units/meson.build +++ b/units/meson.build @@ -519,6 +519,15 @@ units = [ 'file' : 'systemd-pcrlock-firmware-config.service.in', 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], }, + { + 'file' : 'systemd-pcrlock@.service.in', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + }, + { + 'file' : 'systemd-pcrlock.socket', + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'], + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'systemd-portabled.service.in', 'conditions' : ['ENABLE_PORTABLED'], diff --git a/units/systemd-pcrlock.socket b/units/systemd-pcrlock.socket new file mode 100644 index 00000000000..21431478b88 --- /dev/null +++ b/units/systemd-pcrlock.socket @@ -0,0 +1,25 @@ +# 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=Make TPM2 PCR Policy (Varlink) +Documentation=man:systemd-pcrlock(8) +DefaultDependencies=no +After=tpm2.target +Before=sockets.target +ConditionSecurity=measured-uki + +[Socket] +ListenStream=/run/systemd/io.systemd.PCRLock +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-pcrlock@.service.in b/units/systemd-pcrlock@.service.in new file mode 100644 index 00000000000..50a0a3de8eb --- /dev/null +++ b/units/systemd-pcrlock@.service.in @@ -0,0 +1,21 @@ +# 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=Make TPM2 PCR Policy (Varlink) +Documentation=man:systemd-pcrlock(8) +DefaultDependencies=no +Conflicts=shutdown.target +After=systemd-tpm2-setup.service +Before=sysinit.target shutdown.target +After=systemd-remount-fs.service var.mount + +[Service] +Environment=LISTEN_FDNAMES=varlink +ExecStart={{LIBEXECDIR}}/systemd-pcrlock --location=770