Merge pull request #31233 from poettering/pcrlock-varlink

pcrlock: add simple Varlink API + some varlinkctl tweaks
This commit is contained in:
Lennart Poettering 2024-02-12 15:48:03 +01:00 committed by GitHub
commit a85daa97d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 485 additions and 190 deletions

3
TODO
View file

@ -132,6 +132,8 @@ Deprecations and removals:
Features:
* varlink: extend varlink IDL macros to include documentation strings
* Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs
xattrs to convey info about invocation ids, logging settings and so on.
support for cgroupfs xattrs in the "trusted." namespace was added in linux
@ -335,7 +337,6 @@ Features:
- systemd-dissect
- systemd-sysupdate
- systemd-analyze
- systemd-pcrlock (to allow fwupd to relax policy)
- kernel-install
- systemd-mount (with PK so that desktop environments could use it to mount disks)

View file

@ -184,6 +184,15 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--collect</option></term>
<listitem><para>This is similar to <option>--more</option> but collects all responses in a JSON
array, and prints it, rather than in JSON_SEQ mode.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--oneway</option></term>

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);
@ -2412,6 +2415,75 @@ static int verb_show_log(int argc, char *argv[], void *userdata) {
return 0;
}
static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL;
JsonVariant *cd = NULL;
const char *ct = NULL;
int r;
assert(record);
assert(recnum);
assert(ret);
LIST_FOREACH(banks, bank, record->banks) {
r = json_variant_append_arrayb(
&ja, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size)));
if (r < 0)
return log_error_errno(r, "Failed to append CEL digest entry: %m");
}
if (!ja) {
r = json_variant_new_array(&ja, NULL, 0);
if (r < 0)
return log_error_errno(r, "Failed to allocate JSON array: %m");
}
if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) {
_cleanup_free_ char *et = NULL;
const char *z;
z = tpm2_log_event_type_to_string(record->firmware_event_type);
if (z) {
_cleanup_free_ char *b = NULL;
b = strreplace(z, "-", "_");
if (!b)
return log_oom();
et = strjoin("EV_", ascii_strupper(b));
if (!et)
return log_oom();
} else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0)
return log_oom();
r = json_build(&fj, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("event_type", et),
JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size)));
if (r < 0)
return log_error_errno(r, "Failed to build firmware event data: %m");
cd = fj;
ct = "pcclient_std";
} else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) {
cd = record->userspace_content;
ct = "systemd";
}
r = json_build(ret,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr),
JSON_BUILD_PAIR_UNSIGNED("recnum", ++(*recnum)),
JSON_BUILD_PAIR_VARIANT("digests", ja),
JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)),
JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd))));
if (r < 0)
return log_error_errno(r, "Failed to make CEL record: %m");
return 0;
}
static int verb_show_cel(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
_cleanup_(event_log_freep) EventLog *el = NULL;
@ -2429,64 +2501,13 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) {
/* Output the event log in TCG CEL-JSON. */
FOREACH_ARRAY(rr, el->records, el->n_records) {
_cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL;
EventLogRecord *record = *rr;
JsonVariant *cd = NULL;
const char *ct = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *cel = NULL;
LIST_FOREACH(banks, bank, record->banks) {
r = json_variant_append_arrayb(
&ja, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size)));
if (r < 0)
return log_error_errno(r, "Failed to append CEL digest entry: %m");
}
r = event_log_record_to_cel(*rr, &recnum, &cel);
if (r < 0)
return r;
if (!ja) {
r = json_variant_new_array(&ja, NULL, 0);
if (r < 0)
return log_error_errno(r, "Failed to allocate JSON array: %m");
}
if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) {
_cleanup_free_ char *et = NULL;
const char *z;
z = tpm2_log_event_type_to_string(record->firmware_event_type);
if (z) {
_cleanup_free_ char *b = NULL;
b = strreplace(z, "-", "_");
if (!b)
return log_oom();
et = strjoin("EV_", ascii_strupper(b));
if (!et)
return log_oom();
} else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0)
return log_oom();
r = json_build(&fj, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("event_type", et),
JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size)));
if (r < 0)
return log_error_errno(r, "Failed to build firmware event data: %m");
cd = fj;
ct = "pcclient_std";
} else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) {
cd = record->userspace_content;
ct = "systemd";
}
r = json_variant_append_arrayb(&array,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr),
JSON_BUILD_PAIR_UNSIGNED("recnum", ++recnum),
JSON_BUILD_PAIR_VARIANT("digests", ja),
JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)),
JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd))));
r = json_variant_append_array(&array, cel);
if (r < 0)
return log_error_errno(r, "Failed to append CEL record: %m");
}
@ -4292,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
@ -4367,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 */
}
}
@ -4416,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");
@ -4694,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(
@ -4750,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 = {};
@ -4789,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;
@ -5064,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;
}
@ -5106,17 +5143,125 @@ 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;
log_show_color(true);
log_parse_environment();
log_open();
log_setup();
r = parse_argv(argc, 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

@ -37,6 +37,7 @@
#define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC)
#define VARLINK_BUFFER_MAX (16U*1024U*1024U)
#define VARLINK_READ_SIZE (64U*1024U)
#define VARLINK_COLLECT_MAX 1024U
typedef enum VarlinkState {
/* Client side states */
@ -45,6 +46,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE,
VARLINK_CALLING,
VARLINK_CALLED,
VARLINK_COLLECTING,
VARLINK_COLLECTING_REPLY,
VARLINK_PROCESSING_REPLY,
/* Server side states */
@ -79,6 +82,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE, \
VARLINK_CALLING, \
VARLINK_CALLED, \
VARLINK_COLLECTING, \
VARLINK_COLLECTING_REPLY, \
VARLINK_PROCESSING_REPLY, \
VARLINK_IDLE_SERVER, \
VARLINK_PROCESSING_METHOD, \
@ -169,6 +174,8 @@ struct Varlink {
VarlinkReply reply_callback;
JsonVariant *current;
JsonVariant *current_collected;
VarlinkReplyFlags current_reply_flags;
VarlinkSymbol *current_method;
int peer_pidfd;
@ -243,18 +250,14 @@ struct VarlinkServer {
bool exit_on_idle;
};
typedef struct VarlinkCollectContext {
JsonVariant *parameters;
const char *error_id;
VarlinkReplyFlags flags;
} VarlinkCollectContext ;
static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
[VARLINK_IDLE_CLIENT] = "idle-client",
[VARLINK_AWAITING_REPLY] = "awaiting-reply",
[VARLINK_AWAITING_REPLY_MORE] = "awaiting-reply-more",
[VARLINK_CALLING] = "calling",
[VARLINK_CALLED] = "called",
[VARLINK_COLLECTING] = "collecting",
[VARLINK_COLLECTING_REPLY] = "collecting-reply",
[VARLINK_PROCESSING_REPLY] = "processing-reply",
[VARLINK_IDLE_SERVER] = "idle-server",
[VARLINK_PROCESSING_METHOD] = "processing-method",
@ -688,7 +691,9 @@ static void varlink_clear_current(Varlink *v) {
/* Clears the currently processed incoming message */
v->current = json_variant_unref(v->current);
v->current_collected = json_variant_unref(v->current_collected);
v->current_method = NULL;
v->current_reply_flags = 0;
close_many(v->input_fds, v->n_input_fds);
v->input_fds = mfree(v->input_fds);
@ -771,7 +776,7 @@ static int varlink_test_disconnect(Varlink *v) {
goto disconnect;
/* If we are waiting for incoming data but the read side is shut down, disconnect. */
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected)
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected)
goto disconnect;
/* Similar, if are a client that hasn't written anything yet but the write side is dead, also
@ -894,7 +899,7 @@ static int varlink_read(Varlink *v) {
assert(v);
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER))
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER))
return 0;
if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */
return 0;
@ -1090,7 +1095,7 @@ static int varlink_parse_message(Varlink *v) {
static int varlink_test_timeout(Varlink *v) {
assert(v);
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (v->timeout == USEC_INFINITY)
return 0;
@ -1180,7 +1185,7 @@ static int varlink_dispatch_reply(Varlink *v) {
assert(v);
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (!v->current)
return 0;
@ -1223,7 +1228,7 @@ static int varlink_dispatch_reply(Varlink *v) {
}
/* Replies with 'continue' set are only OK if we set 'more' when the method call was initiated */
if (v->state != VARLINK_AWAITING_REPLY_MORE && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
if (!IN_SET(v->state, VARLINK_AWAITING_REPLY_MORE, VARLINK_COLLECTING) && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
goto invalid;
/* An error is final */
@ -1234,6 +1239,8 @@ static int varlink_dispatch_reply(Varlink *v) {
if (r < 0)
goto invalid;
v->current_reply_flags = flags;
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE)) {
varlink_set_state(v, VARLINK_PROCESSING_REPLY);
@ -1246,7 +1253,6 @@ static int varlink_dispatch_reply(Varlink *v) {
varlink_clear_current(v);
if (v->state == VARLINK_PROCESSING_REPLY) {
assert(v->n_pending > 0);
if (!FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
@ -1256,7 +1262,9 @@ static int varlink_dispatch_reply(Varlink *v) {
FLAGS_SET(flags, VARLINK_REPLY_CONTINUES) ? VARLINK_AWAITING_REPLY_MORE :
v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY);
}
} else {
} else if (v->state == VARLINK_COLLECTING)
varlink_set_state(v, VARLINK_COLLECTING_REPLY);
else {
assert(v->state == VARLINK_CALLING);
varlink_set_state(v, VARLINK_CALLED);
}
@ -1734,7 +1742,7 @@ int varlink_get_events(Varlink *v) {
return EPOLLOUT;
if (!v->read_disconnected &&
IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) &&
IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) &&
!v->current &&
v->input_buffer_unscanned <= 0)
ret |= EPOLLIN;
@ -1752,7 +1760,7 @@ int varlink_get_timeout(Varlink *v, usec_t *ret) {
if (v->state == VARLINK_DISCONNECTED)
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING) &&
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) &&
v->timeout != USEC_INFINITY) {
if (ret)
*ret = usec_add(v->timestamp, v->timeout);
@ -2199,7 +2207,6 @@ int varlink_call_full(
v->timestamp = now(CLOCK_MONOTONIC);
while (v->state == VARLINK_CALLING) {
r = varlink_process(v);
if (r < 0)
return r;
@ -2232,7 +2239,7 @@ int varlink_call_full(
if (ret_error_id)
*ret_error_id = e ? json_variant_string(e) : NULL;
if (ret_flags)
*ret_flags = 0;
*ret_flags = v->current_reply_flags;
return 1;
}
@ -2318,44 +2325,7 @@ int varlink_callb_and_log(
return varlink_call_and_log(v, method, parameters, ret_parameters);
}
static void varlink_collect_context_free(VarlinkCollectContext *cc) {
assert(cc);
json_variant_unref(cc->parameters);
free((char *)cc->error_id);
}
static int collect_callback(
Varlink *v,
JsonVariant *parameters,
const char *error_id,
VarlinkReplyFlags flags,
void *userdata) {
VarlinkCollectContext *context = ASSERT_PTR(userdata);
int r;
assert(v);
context->flags = flags;
/* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */
if (error_id) {
context->error_id = error_id;
json_variant_unref(context->parameters);
context->parameters = json_variant_ref(parameters);
return 0;
}
r = json_variant_append_array(&context->parameters, parameters);
if (r < 0)
return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
return 1;
}
int varlink_collect(
int varlink_collect_full(
Varlink *v,
const char *method,
JsonVariant *parameters,
@ -2363,7 +2333,7 @@ int varlink_collect(
const char **ret_error_id,
VarlinkReplyFlags *ret_flags) {
_cleanup_(varlink_collect_context_free) VarlinkCollectContext context = {};
_cleanup_(json_variant_unrefp) JsonVariant *m = NULL, *collected = NULL;
int r;
assert_return(v, -EINVAL);
@ -2380,71 +2350,102 @@ int varlink_collect(
* that we can assign a new reply shortly. */
varlink_clear_current(v);
r = varlink_bind_reply(v, collect_callback);
r = varlink_sanitize_parameters(&parameters);
if (r < 0)
return varlink_log_errno(v, r, "Failed to bind collect callback");
return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
varlink_set_userdata(v, &context);
r = varlink_observe(v, method, parameters);
r = json_build(&m, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)),
JSON_BUILD_PAIR("more", JSON_BUILD_BOOLEAN(true))));
if (r < 0)
return varlink_log_errno(v, r, "Failed to collect varlink method: %m");
return varlink_log_errno(v, r, "Failed to build json message: %m");
while (v->state == VARLINK_AWAITING_REPLY_MORE) {
r = varlink_enqueue_json(v, m);
if (r < 0)
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
r = varlink_process(v);
if (r < 0)
return r;
varlink_set_state(v, VARLINK_COLLECTING);
v->n_pending++;
v->timestamp = now(CLOCK_MONOTONIC);
/* If we get an error from any of the replies, return immediately with just the error_id and flags*/
if (context.error_id) {
for (;;) {
while (v->state == VARLINK_COLLECTING) {
r = varlink_process(v);
if (r < 0)
return r;
if (r > 0)
continue;
/* If caller doesn't ask for the error string, then let's return an error code in case of failure */
if (!ret_error_id)
return varlink_error_to_errno(context.error_id, context.parameters);
if (ret_parameters)
*ret_parameters = TAKE_PTR(context.parameters);
if (ret_error_id)
*ret_error_id = TAKE_PTR(context.error_id);
if (ret_flags)
*ret_flags = context.flags;
return 0;
r = varlink_wait(v, USEC_INFINITY);
if (r < 0)
return r;
}
if (r > 0)
continue;
switch (v->state) {
r = varlink_wait(v, USEC_INFINITY);
if (r < 0)
return r;
case VARLINK_COLLECTING_REPLY: {
assert(v->current);
JsonVariant *e = json_variant_by_key(v->current, "error"),
*p = json_variant_by_key(v->current, "parameters");
if (e) {
if (!ret_error_id)
return varlink_error_to_errno(json_variant_string(e), p);
if (ret_parameters)
*ret_parameters = p;
if (ret_error_id)
*ret_error_id = e ? json_variant_string(e) : NULL;
if (ret_flags)
*ret_flags = v->current_reply_flags;
return 1;
}
if (json_variant_elements(collected) >= VARLINK_COLLECT_MAX)
return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", json_variant_elements(collected));
r = json_variant_append_array(&collected, p);
if (r < 0)
return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
if (FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) {
/* There's more to collect, continue */
varlink_clear_current(v);
varlink_set_state(v, VARLINK_COLLECTING);
continue;
}
varlink_set_state(v, VARLINK_IDLE_CLIENT);
assert(v->n_pending == 1);
v->n_pending--;
if (ret_parameters)
/* Install the collection array in the connection object, so that we can hand
* out a pointer to it without passing over ownership, to make it work more
* alike regular method call replies */
*ret_parameters = v->current_collected = TAKE_PTR(collected);
if (ret_error_id)
*ret_error_id = NULL;
if (ret_flags)
*ret_flags = v->current_reply_flags;
return 1;
}
case VARLINK_PENDING_DISCONNECT:
case VARLINK_DISCONNECTED:
return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
case VARLINK_PENDING_TIMEOUT:
return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
default:
assert_not_reached();
}
}
switch (v->state) {
case VARLINK_IDLE_CLIENT:
break;
case VARLINK_PENDING_DISCONNECT:
case VARLINK_DISCONNECTED:
return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
case VARLINK_PENDING_TIMEOUT:
return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
default:
assert_not_reached();
}
if (!ret_error_id && context.error_id)
return varlink_error_to_errno(context.error_id, context.parameters);
if (ret_parameters)
*ret_parameters = TAKE_PTR(context.parameters);
if (ret_error_id)
*ret_error_id = TAKE_PTR(context.error_id);
if (ret_flags)
*ret_flags = context.flags;
return 1;
}
int varlink_collectb(
@ -2452,7 +2453,7 @@ int varlink_collectb(
const char *method,
JsonVariant **ret_parameters,
const char **ret_error_id,
VarlinkReplyFlags *ret_flags, ...) {
...) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
va_list ap;
@ -2460,14 +2461,14 @@ int varlink_collectb(
assert_return(v, -EINVAL);
va_start(ap, ret_flags);
va_start(ap, ret_error_id);
r = json_buildv(&parameters, ap);
va_end(ap);
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
return varlink_collect(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
}
int varlink_reply(Varlink *v, JsonVariant *parameters) {

View file

@ -116,8 +116,11 @@ static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **re
int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...);
/* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */
int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
int varlink_collect_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
static inline int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
}
int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...);
/* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */
int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters);

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

@ -238,10 +238,9 @@ static void flood_test(const char *address) {
static void *thread(void *arg) {
_cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *i = NULL, *j = NULL;
JsonVariant *o = NULL, *k = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *i = NULL;
JsonVariant *o = NULL, *k = NULL, *j = NULL;
const char *error_id;
VarlinkReplyFlags flags = 0;
const char *e;
int x = 0;
@ -253,10 +252,9 @@ static void *thread(void *arg) {
assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0);
assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0);
assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id, &flags) >= 0);
assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id) >= 0);
assert_se(!error_id);
assert_se(!flags);
assert_se(json_variant_is_array(j) && !json_variant_is_blank_array(j));
JSON_VARIANT_ARRAY_FOREACH(k, j) {

View file

@ -19,6 +19,7 @@
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static VarlinkMethodFlags arg_method_flags = 0;
static bool arg_collect = false;
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -47,6 +48,7 @@ static int help(void) {
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --more Request multiple responses\n"
" --collect Collect multiple responses in a JSON array\n"
" --oneway Do not request response\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
@ -73,6 +75,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_MORE,
ARG_ONEWAY,
ARG_JSON,
ARG_COLLECT,
};
static const struct option options[] = {
@ -82,6 +85,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "more", no_argument, NULL, ARG_MORE },
{ "oneway", no_argument, NULL, ARG_ONEWAY },
{ "json", required_argument, NULL, ARG_JSON },
{ "collect", no_argument, NULL, ARG_COLLECT },
{},
};
@ -112,6 +116,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY;
break;
case ARG_COLLECT:
arg_collect = true;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
@ -371,7 +379,13 @@ static int verb_call(int argc, char *argv[], void *userdata) {
method = argv[2];
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
arg_json_format_flags &= ~JSON_FORMAT_OFF;
/* No JSON mode explicitly configured? Then default to the same as -j */
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
/* For pipeable text tools it's kinda customary to finish output off in a newline character, and not
* leave incomplete lines hanging around. */
arg_json_format_flags |= JSON_FORMAT_NEWLINE;
if (parameter) {
/* <argv[4]> is correct, as dispatch_verb() shifts arguments by one for the verb. */
@ -388,7 +402,26 @@ static int verb_call(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
if (arg_collect) {
JsonVariant *reply = NULL;
const char *error = NULL;
r = varlink_collect(vl, method, jp, &reply, &error);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);
if (error) {
/* Propagate the error we received via sd_notify() */
(void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
} else
r = 0;
pager_open(arg_pager_flags);
json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
return r;
} else if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
r = varlink_send(vl, method, jp);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);

View file

@ -348,10 +348,10 @@ fi
# Decrypt/encrypt via varlink
echo -n '{"data":"Zm9vYmFyCg=="}' > /tmp/vlcredsdata
echo '{"data":"Zm9vYmFyCg=="}' > /tmp/vlcredsdata
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "$(cat /tmp/vlcredsdata)" | \
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
cmp /tmp/vlcredsdata /tmp/vlcredsdata2
rm /tmp/vlcredsdata /tmp/vlcredsdata2

View file

@ -156,4 +156,20 @@ SYSTEMD_XBOOTLDR_PATH=/tmp/fakexbootldr SYSTEMD_RELAX_XBOOTLDR_CHECKS=1 "$SD_PCR
(! "$SD_PCRLOCK" lock-uki /bin/true)
(! "$SD_PCRLOCK" lock-file-system "")
# Excercise Varlink API a bit (but first turn off condition)
mkdir -p /run/systemd/system/systemd-pcrlock.socket.d
cat > /run/systemd/system/systemd-pcrlock.socket.d/50-no-condition.conf <<EOF
[Unit]
# Turn off all conditions
ConditionSecurity=
EOF
systemctl daemon-reload
systemctl restart systemd-pcrlock.socket
varlinkctl call /run/systemd/io.systemd.PCRLock io.systemd.PCRLock.RemovePolicy '{}'
varlinkctl call /run/systemd/io.systemd.PCRLock io.systemd.PCRLock.MakePolicy '{}'
varlinkctl call --collect --json=pretty /run/systemd/io.systemd.PCRLock io.systemd.PCRLock.ReadEventLog '{}'
rm "$img" /tmp/pcrlockpwd

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