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.
This commit is contained in:
Lennart Poettering 2024-02-02 15:17:09 +01:00
parent 9fe15ce84d
commit 15138e7980
8 changed files with 224 additions and 6 deletions

View File

@ -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);
}

View File

@ -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',

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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'],

View File

@ -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

View File

@ -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