1
0
mirror of https://github.com/systemd/systemd synced 2026-03-07 05:34:46 +01:00

Compare commits

...

16 Commits

Author SHA1 Message Date
David Santamaría Rogado
cad60201fc udev: rules: integration fix
ID_INTEGRATION is not being updated with hwdb entries, asign the new
value to it when hwdb has been imported.

We still need the 65-integration.rule assignment for devices that aren't
in hwdb.

While at it remove unneeded check in 70-touchpad.rules, as it was not
added for 70-joystick.rules with the statement if ID_INPUT_* is set and
ID_INPUT not, there is a bug elsewhere. And remove unneeded gotos in
both files.

Follow-up for a4381cae8bfacb1160967ac499c2919da7ff8c2b.
2026-02-18 11:51:02 +09:00
Daan De Meyer
10eaca4159 sysupdate: Use partition types for pending/partial partitions
Fixes #40658
2026-02-18 11:15:04 +09:00
Yu Watanabe
d8da141348 README: mention about musl requirements 2026-02-18 11:04:14 +09:00
Oblivionsage
02cab70acf pe-binary: fix missing le16toh() on NumberOfSections in pe_hash/uki_hash
pe_hash() and uki_hash() pass pe_header->pe.NumberOfSections directly
to typesafe_qsort() and FOREACH_ARRAY() without le16toh(). On
big-endian (s390x), NumberOfSections=3 gets read as 0x0300 (768),
while pe_load_sections() correctly converts it and only allocates 3
sections. This makes qsort process 768 elements on a 3-element
buffer, causing a heap-buffer-overflow (confirmed with ASAN on
native s390x).

Wrap all three raw usages with le16toh() to match pe_load_sections().
2026-02-18 08:25:54 +09:00
Lennart Poettering
a3af68b84d
verity: measure all root hashes as we activate to a new NvPCR (#40648) 2026-02-17 23:44:46 +01:00
Lennart Poettering
3821e4d8fb update TODO 2026-02-17 22:00:14 +01:00
Lennart Poettering
521a523ce0 ci: add simple test that ensures the verity nvpcr measurements are made 2026-02-17 22:00:14 +01:00
Lennart Poettering
a450fab9c6 gpt-auto-generator: enable nvpcr logic by default
Let's enable this kind of measurement by default if people buy into UKIs
and stuff, just like volume key measurement is now enabled by default.
2026-02-17 22:00:14 +01:00
Lennart Poettering
85d7fb2247 veritysetup: optionally measure Verity once we activated it
As in the previous commit, also enable the measurement logic when
activating Verity via veritsetup rather than the dissection logic.

The logic stays close to the interface of cryptsetup for measuring the
volume key.
2026-02-17 22:00:14 +01:00
Lennart Poettering
32f405074a dissect-image: measure Verity before making use of them
Let's hook up the dissection logic with the new measurement infra, and
issue the measurement after successfully unlock an image, but before
returning to the caller.

Note that ideally we'd do this measurement in the kernel, so that we can
place it after authenticating the root hash, but before activating the
medium. One day we should be able to do that via eBPF based on userspace
policies, but for now, this would require too much kernel rework.

Let's however make sure our measurements only contain data that the
kernel could know too, so that we hopefully can move these measurements
to the kernel without changing their formatting.
2026-02-17 22:00:14 +01:00
Lennart Poettering
71ca7532de pcrextend-util: add helpers for measuring roothash/signature of Verity volumes
This adds infrastructure for measuring Verity root hashes from
userspace, along with he issuer/serial of the signatures used to unlock
them.

We measure the triplet of volume name, root hash and issuer/serial. if
confext/sysext use different signing keys then this ensures the event
log carry information about the type of image measures.
2026-02-17 22:00:14 +01:00
Lennart Poettering
3f31c8ff46 pkcs7-util: add helpers for extracting signer info from PKCS7 signatures
Once we start measuring Verity volumes as we activate them we want to
include information about the signature keys used, so that we can have
distinct ones for confext and for sysext and ther purposes and thus have
a cryptograpically protected hint about the kind of image we have
activated in the event log.

Ideally we'd measure a fingerprint of the signing certificate here, but
we don't have that here typically (as PKCS7 signatures used here
typically do not embed that), hence use the next best thing: the issuer
name and the serial number.
2026-02-17 22:00:14 +01:00
Lennart Poettering
8485bba53d tpm2-setup: introduce nvpcr for measuring Verity images
I thnk it's crucial we start to measure Verity images as we activate
them, so that the event log has a full trace of the compisition of the
system. hence let's introduce a new NvPCR for this purpse, under the
name "verity".
2026-02-17 22:00:13 +01:00
Lennart Poettering
0a8f393c64 cryptsetup: extend HAVE_TPM2 conditioning to cover more
If TPM2 support is off, any check for TPM2 support will fail, hence we
can just suppress it.
2026-02-17 22:00:13 +01:00
Lennart Poettering
be36f69c90 pcrextend: allow access to the userspace event type field when measuring something
It think we should move most measurements out of the individual tools
requesting them and into the pcrextend service via Varlink, so that
fewer components require access to the TPM.

This only works however, if we can actually write full-blown event log
records via this mechanism, and for that we still were missing access to
the userspace event type we insert into the event log. Add that.
2026-02-17 22:00:13 +01:00
Lennart Poettering
d84d17853e cryptsetup: move default choice of nvpcr for keyslots from generator into cryptsetup
Let's pick the default NvPCR name to use inside of cryptsetup itself, instead
of in the generator. I think this is the better choice, since it means
the default can also be used if the regular verittab generator is used
instead of the gpt-auto generator.
2026-02-17 22:00:13 +01:00
31 changed files with 584 additions and 120 deletions

2
README
View File

@ -204,6 +204,8 @@ REQUIREMENTS:
CONFIG_MEMCG
glibc >= 2.34
musl >= 1.2.5 with fde29c04adbab9d5b081bf6717b5458188647f1c
(required when building systemd with -Dlibc=musl)
libxcrypt >= 4.4.0 (optional)
libmount >= 2.30 (from util-linux)
(util-linux *must* be built without --enable-libmount-support-mtab)

22
TODO
View File

@ -720,10 +720,6 @@ Features:
deleting entries for rotation, place an event that declares how many items
have been dropped, and what the hash before and after that.
* measure information about all DDIs as we activate them to an NvPCR. We
probably should measure the dm-verity root hash from the kernel side, but
DDI meta info from userspace.
* use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode
number) for identifying inodes, for example in copy.c when finding hard
links, or loop-util.c for tracking backing files, and other places.
@ -1299,9 +1295,9 @@ Features:
- If run on every boot, should it use the sysupdate config from the host on
subsequent boots?
* To mimic the new tpm2-measure-pcr= crypttab option add the same to veritytab
(measuring the root hash) and integritytab (measuring the HMAC key if one is
used)
* To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr=
veritytab option, add the same to integritytab (measuring the HMAC key if one
is used)
* We should start measuring all services, containers, and system extensions we
activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to
@ -1720,18 +1716,6 @@ Features:
keys of /etc/crypttab. That way people can store/provide the roothash
externally and provide to us on demand only.
* we probably should extend the root verity hash of the root fs into some PCR
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
it into PCR 12); Similar: we probably should extend the LUKS volume key of
the root fs into some PCR on boot. (i.e. maybe add a crypttab option
tpm2-measure=15 or so to measure it into PCR 15); once both are in place
update gpt-auto-discovery to generate these by default for the partitions it
discovers. Static vendor stuff should probably end up in PCR 12 (i.e. the
verity hash), with local keys in PCR 15 (i.e. the encryption volume
key). That way, we nicely distinguish resources supplied by the OS vendor
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately.
* rework recursive read-only remount to use new mount API
* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release

View File

@ -945,12 +945,12 @@
<term><option>tpm2-measure-keyslot-nvpcr=</option></term>
<listitem><para>Controls whether to measure information about the used LUKS unlock keyslot to a TPM2
non-volatile index (nvindex in PCR mode). If set to to an empty string (which is the default) no TPM2
nvindex extension is done, otherwise keyslot information is measured to an nvindex of the specified
name, which is allocated if needed. It is recommended to set this to <literal>cryptsetup</literal> to
enable this logic. The slot index and the used unlock mechanism (i.e. <literal>tpm2</literal>,
<literal>fido2</literal>, <literal>pkcs11</literal>) is measured along with the activated volume name
and its UUID.</para>
non-volatile index (nvindex in PCR mode). Takes a boolean argument, or an NvPCR name. If set to false
or an empty string (which is the default) no TPM2 nvindex extension is done, otherwise keyslot
information is measured to an nvindex of the specified name, which is allocated if needed. If set to
true the recommended default of <literal>cryptsetup</literal> is selected as NvPCR. The slot index
and the used unlock mechanism (i.e. <literal>tpm2</literal>, <literal>fido2</literal>,
<literal>pkcs11</literal>) are measured along with the activated volume name and its UUID.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>

View File

@ -272,9 +272,13 @@
<para>If the system was booted via
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> and the
stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and
<filename>/var/</filename> volume identifiers (and volume encryption key in case it is encrypted) will be
automatically measured into PCR 15 on activation, via
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<filename>/var/</filename> volume identifiers (and volume encryption keys, in case they are encrypted)
will be automatically measured into PCR 15 on activation, via
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. Moreover,
information about the LUKS key slot used to unlock the volume is measured into NvPCR
<literal>cryptsetup</literal>. Finally, if the root or <filename>/usr/</filename> partition is protected
via Verity its root hash and the serial/issuer of the key used for the provided root hash signature (if
any) are measured into the NvPCR <literal>verity</literal>.</para>
<para>Mount constraint metadata contained in the file systems is validated by pulling in
<citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>

View File

@ -298,6 +298,18 @@ This is based on crypttab(5).
</listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-measure-nvpcr=</option></term>
<listitem><para>Takes a boolean argument, or an NvPCR name as argument. May be used to enable
automatic measurement of the volume name and Verity root hash, as well as the serials and issuers of
the certificates used to generate the provided signatures (if any) will be measured. Passing false
disables the mechanism. Passing true enables measurement into the <literal>verity</literal> NvPCR. If
any other string is specified this selects the NvPCR to measure into by name.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist>
<para>At early boot and when the system manager configuration is

View File

@ -6,7 +6,11 @@ KERNEL!="event*", GOTO="joystick_end"
# joystick:<bustype>:v<vid>p<pid>:name:<name>:*
KERNELS=="input*", ENV{ID_BUS}!="", \
IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
GOTO="joystick_end"
IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'"
# Spread the hwdb override to ID_INTEGRATION, in the future we could remove the
# joystick hwdb entirely or retain it using the generic ID_INTEGRATION instead
# specific ID_INPUT_JOYSTICK_INTEGRATION.
ENV{ID_INPUT_JOYSTICK_INTEGRATION}!="", ENV{ID_INTEGRATION}="$env{ID_INPUT_JOYSTICK_INTEGRATION}"
LABEL="joystick_end"

View File

@ -1,13 +1,16 @@
# do not edit this file, it will be overwritten on update
ACTION=="remove", GOTO="touchpad_end"
ENV{ID_INPUT}=="", GOTO="touchpad_end"
ENV{ID_INPUT_TOUCHPAD}=="", GOTO="touchpad_end"
KERNEL!="event*", GOTO="touchpad_end"
# touchpad:<subsystem>:v<vid>p<pid>:name:<name>:*
KERNELS=="input*", ENV{ID_BUS}!="", \
IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
GOTO="touchpad_end"
IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'"
# Spread the hwdb override to ID_INTEGRATION, in the future we could remove the
# touchpad hwdb entirely or retain it using the generic ID_INTEGRATION instead
# specific ID_INPUT_TOUCHPAD_INTEGRATION.
ENV{ID_INPUT_TOUCHPAD_INTEGRATION}!="", ENV{ID_INTEGRATION}="$env{ID_INPUT_TOUCHPAD_INTEGRATION}"
LABEL="touchpad_end"

View File

@ -557,12 +557,14 @@ static int parse_one_option(const char *option) {
} else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) {
if (isempty(val)) {
r = isempty(val) ? false : parse_boolean(val);
if (r == 0) {
arg_tpm2_measure_keyslot_nvpcr = mfree(arg_tpm2_measure_keyslot_nvpcr);
return 0;
}
if (!tpm2_nvpcr_name_is_valid(val)) {
if (r > 0)
val = "cryptsetup";
else if (!tpm2_nvpcr_name_is_valid(val)) {
log_warning("Invalid NvPCR name, ignoring: %s", option);
return 0;
}
@ -1095,8 +1097,9 @@ static int measure_keyslot(
const char *mechanism,
int keyslot) {
#if HAVE_TPM2
int r;
#endif
assert(cd);
assert(name);
@ -1105,6 +1108,7 @@ static int measure_keyslot(
return 0;
}
#if HAVE_TPM2
r = efi_measured_uki(LOG_WARNING);
if (r < 0)
return r;
@ -1113,7 +1117,6 @@ static int measure_keyslot(
return 0;
}
#if HAVE_TPM2
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)

View File

@ -127,7 +127,7 @@ static int add_cryptsetup(
* assignment, under the assumption that people who are fine to use sd-stub with its PCR
* assignments are also OK with our PCR 15 use here. */
if (r > 0)
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=cryptsetup"))
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes"))
return log_oom();
if (r == 0)
log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id);
@ -190,7 +190,8 @@ static int add_veritysetup(
const char *id,
const char *data_what,
const char *hash_what,
const char *mount_opts) {
const char *mount_opts,
MountPointFlags flags) {
#if HAVE_LIBCRYPTSETUP
int r;
@ -233,13 +234,26 @@ static int add_veritysetup(
"After=%1$s %2$s\n",
dd, dh);
_cleanup_free_ char *options =
strdup("root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
if (!options)
return log_oom();
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
r = efi_measured_uki(LOG_WARNING);
if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes"))
return log_oom();
if (r == 0)
log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id);
}
r = generator_write_veritysetup_service_section(
f,
id,
data_what,
hash_what,
/* roothash= */ NULL, /* NULL means: derive root hash from udev property ID_DISSECT_PART_ROOTHASH */
"root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
options);
if (r < 0)
return r;
@ -871,7 +885,8 @@ static int add_root_mount(void) {
"root",
"/dev/disk/by-designator/root-verity-data",
"/dev/disk/by-designator/root-verity",
arg_root_options);
arg_root_options,
MOUNT_MEASURE);
if (r < 0)
return r;
}
@ -952,7 +967,8 @@ static int add_usr_mount(void) {
"usr",
"/dev/disk/by-designator/usr-verity-data",
"/dev/disk/by-designator/usr-verity",
arg_usr_options);
arg_usr_options,
MOUNT_MEASURE);
if (r < 0)
return r;
}

View File

@ -369,6 +369,7 @@ typedef struct MethodExtendParameters {
const char *nvpcr;
const char *text;
struct iovec data;
Tpm2UserspaceEventType event_type;
} MethodExtendParameters;
static void method_extend_parameters_done(MethodExtendParameters *p) {
@ -377,17 +378,21 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
iovec_done(&p->data);
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2UserspaceEventType, tpm2_userspace_event_type_from_string);
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), 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 },
{ "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 },
{ "eventType", SD_JSON_VARIANT_STRING, json_dispatch_tpm2_userspace_event_type, offsetof(MethodExtendParameters, event_type), 0 },
{}
};
_cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
.pcr = UINT_MAX,
.event_type = _TPM2_USERSPACE_EVENT_TYPE_INVALID,
};
int r;
@ -424,11 +429,11 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
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);
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
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);
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
if (r < 0)
return r;

View File

@ -60,6 +60,7 @@
#include "openssl-util.h"
#include "os-util.h"
#include "path-util.h"
#include "pcrextend-util.h"
#include "pidref.h"
#include "proc-cmdline.h"
#include "process-util.h"
@ -3219,13 +3220,13 @@ static int do_crypt_activate_verity(
DissectImageFlags flags,
PartitionPolicyFlags policy_flags) {
bool check_signature;
int r, k;
int r;
assert(cd);
assert(name);
assert(verity);
bool check_signature;
if (iovec_is_set(&verity->root_hash_sig) && FLAGS_SET(policy_flags, PARTITION_POLICY_SIGNED)) {
r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE");
if (r < 0 && r != -ENXIO)
@ -3235,7 +3236,10 @@ static int do_crypt_activate_verity(
} else
check_signature = false;
bool measure_signature;
if (check_signature) {
int k;
/* First, if we have support for signed keys in the kernel, then try that first. */
r = sym_crypt_activate_by_signed_key(
cd,
@ -3247,7 +3251,8 @@ static int do_crypt_activate_verity(
CRYPT_ACTIVATE_READONLY);
if (r >= 0) {
log_debug("Verity activation via kernel signature logic worked.");
return 0;
measure_signature = true;
goto done;
}
log_debug_errno(r, "Validation of dm-verity signature failed via the kernel, trying userspace validation instead: %m");
@ -3280,9 +3285,13 @@ static int do_crypt_activate_verity(
/* Otherwise let's see what signature-less activation results in. */
measure_signature = true;
} else if (!FLAGS_SET(policy_flags, PARTITION_POLICY_VERITY))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
"No-signature activation of Verity volume not allowed by policy, refusing.");
else
measure_signature = false;
r = sym_crypt_activate_by_volume_key(
cd,
@ -3294,6 +3303,12 @@ static int do_crypt_activate_verity(
return log_debug_errno(r, "Activation of Verity via root hash failed: %m");
log_debug("Activation of Verity via root hash succeeded.");
done:
(void) pcrextend_verity_now(
name,
&verity->root_hash,
measure_signature ? &verity->root_hash_sig : NULL);
return 0;
}

View File

@ -152,6 +152,7 @@ shared_sources = files(
'pcre2-util.c',
'pcrextend-util.c',
'pe-binary.c',
'pkcs7-util.c',
'pkcs11-util.c',
'plymouth-util.c',
'polkit-agent.c',

View File

@ -2,6 +2,7 @@
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "blkid-util.h"
@ -10,10 +11,13 @@
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "id128-util.h"
#include "iovec-util.h"
#include "log.h"
#include "mountpoint-util.h"
#include "pcrextend-util.h"
#include "pkcs7-util.h"
#include "string-util.h"
#include "strv.h"
@ -180,3 +184,110 @@ int pcrextend_product_id_word(char **ret) {
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_verity_word(
const char *name,
const struct iovec *root_hash,
const struct iovec *root_hash_sig,
char **ret) {
int r;
assert(name);
assert(iovec_is_set(root_hash));
_cleanup_free_ char *name_escaped = xescape(name, ":"); /* Avoid ambiguity around ":" */
if (!name_escaped)
return log_oom();
_cleanup_free_ char *h = hexmem(root_hash->iov_base, root_hash->iov_len);
if (!h)
return log_oom();
_cleanup_free_ char *sigs = NULL;
if (iovec_is_set(root_hash_sig)) {
size_t n_signers = 0;
Signer *signers = NULL;
/* Let's extract the X.509 issuer + serial number from the PKCS#7 signature and include that
* in the measurement record. This is useful since it allows us to have different signing
* keys for confext + sysext + other types of DDIs, and by means of this information we can
* discern which kind it was. Ideally, we'd measure the fingerprint of the X.509 certificate,
* but typically that's not available in a PKCS#7 signature. */
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
r = pkcs7_extract_signers(root_hash_sig, &signers, &n_signers);
if (r < 0)
return r;
FOREACH_ARRAY(i, signers, n_signers) {
_cleanup_free_ char *serial = hexmem(i->serial.iov_base, i->serial.iov_len);
if (!serial)
return log_oom();
_cleanup_free_ char *issuer = NULL;
if (base64mem(i->issuer.iov_base, i->issuer.iov_len, &issuer) < 0)
return log_oom();
if (strextendf_with_separator(&sigs, ",", "%s/%s", serial, issuer) < 0)
return log_oom();
}
}
_cleanup_free_ char *word = strjoin("verity:", name_escaped, ":", h, ":", strempty(sigs));
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_verity_now(
const char *name,
const struct iovec *root_hash,
const struct iovec *root_hash_sig) {
#if HAVE_TPM2
int r;
_cleanup_free_ char *word = NULL;
r = pcrextend_verity_word(
name,
root_hash,
root_hash_sig,
&word);
if (r < 0)
return r;
_cleanup_free_ sd_varlink *vl = NULL;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
if (r < 0)
return r;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
const char *error_id = NULL;
r = sd_varlink_callbo(
vl,
"io.systemd.PCRExtend.Extend",
/* ret_reply= */ NULL,
&error_id,
SD_JSON_BUILD_PAIR_STRING("nvpcr", "verity"),
SD_JSON_BUILD_PAIR_STRING("text", word),
SD_JSON_BUILD_PAIR_STRING("eventType", "dm_verity"));
if (r < 0)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
if (error_id) {
r = sd_varlink_error_to_errno(error_id, reply);
if (r != -EBADR)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id);
}
log_debug("Measurement of '%s' into 'images' NvPCR completed.", word);
return 1;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
#endif
}

View File

@ -4,3 +4,6 @@
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig);

View File

@ -243,7 +243,7 @@ int pe_read_section_data_by_name(
assert(fd >= 0);
assert(pe_header);
assert(sections || pe_header->pe.NumberOfSections == 0);
assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
assert(name);
section = pe_header_find_section(pe_header, sections, name);
@ -408,9 +408,9 @@ int pe_hash(int fd,
return r;
/* Sort by location in file */
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
typesafe_qsort(sections, le16toh(pe_header->pe.NumberOfSections), section_offset_cmp);
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
if (r < 0)
return r;
@ -537,7 +537,7 @@ int uki_hash(int fd,
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
_cleanup_free_ char *n = NULL;
ssize_t i;

84
src/shared/pkcs7-util.c Normal file
View File

@ -0,0 +1,84 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "openssl-util.h"
#include "pkcs7-util.h"
#include "log.h"
#define SIGNERS_MAX 32
static void signer_done(Signer *signer) {
assert(signer);
iovec_done(&signer->issuer);
iovec_done(&signer->serial);
}
void signer_free_many(Signer *signers, size_t n) {
assert(signers || n == 0);
FOREACH_ARRAY(i, signers, n)
signer_done(i);
free(signers);
}
int pkcs7_extract_signers(
const struct iovec *sig,
Signer **ret_signers,
size_t *ret_n_signers) {
assert(ret_signers);
assert(ret_n_signers);
if (!iovec_is_set(sig))
return -EBADMSG;
#if HAVE_OPENSSL
const unsigned char *d = sig->iov_base;
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
p7 = d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len);
if (!p7)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse PKCS7 DER signature data.");
STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7);
if (!sinfos)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signature information in PKCS7 signature?");
int n = sk_PKCS7_SIGNER_INFO_num(sinfos);
if (n == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signatures in PKCS7 signature, refusing.");
if (n > SIGNERS_MAX) /* safety net, in case people send us weirdly complex signatures */
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Too many signatures, refusing.");
assert(n > 0);
size_t n_signers = 0;
Signer *signers = new(Signer, n);
if (!signers)
return log_oom_debug();
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
for (int i = 0; i < n; i++) {
PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), i);
if (!si)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get signer information.");
_cleanup_(signer_done) Signer signer = {};
_cleanup_free_ unsigned char *p = NULL;
int len = i2d_X509_NAME(si->issuer_and_serial->issuer, &p);
signer.issuer = IOVEC_MAKE(TAKE_PTR(p), len);
len = i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p);
signer.serial = IOVEC_MAKE(TAKE_PTR(p), len);
signers[n_signers++] = TAKE_STRUCT(signer);
}
*ret_signers = TAKE_PTR(signers);
*ret_n_signers = n_signers;
return n;
#else
return -EOPNOTSUPP;
#endif
}

16
src/shared/pkcs7-util.h Normal file
View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/uio.h>
typedef struct Signer {
struct iovec issuer;
struct iovec serial;
} Signer;
void signer_free_many(Signer *signers, size_t n);
int pkcs7_extract_signers(
const struct iovec *sig,
Signer **ret_signers,
size_t *ret_n_signers);

View File

@ -5441,7 +5441,7 @@ int tpm2_seal(Tpm2Context *c,
seal_key_handle);
primary_alg = primary_public->publicArea.type;
/* Propagate fixedTPM/fixedParent flags from sealing key to hmac key */
hmac_template.objectAttributes = (hmac_template.objectAttributes & ~(TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT)) |
(primary_public->publicArea.objectAttributes & (TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT));
@ -6478,6 +6478,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_KEYSLOT] = "keyslot",
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
[TPM2_EVENT_DM_VERITY] = "dm-verity",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);

View File

@ -145,6 +145,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_KEYSLOT,
TPM2_EVENT_NVPCR_INIT,
TPM2_EVENT_NVPCR_SEPARATOR,
TPM2_EVENT_DM_VERITY,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;

View File

@ -2,6 +2,18 @@
#include "varlink-io.systemd.PCRExtend.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
EventType,
SD_VARLINK_DEFINE_ENUM_VALUE(phase),
SD_VARLINK_DEFINE_ENUM_VALUE(filesystem),
SD_VARLINK_DEFINE_ENUM_VALUE(volume_key),
SD_VARLINK_DEFINE_ENUM_VALUE(machine_id),
SD_VARLINK_DEFINE_ENUM_VALUE(product_id),
SD_VARLINK_DEFINE_ENUM_VALUE(keyslot),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator),
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity));
static SD_VARLINK_DEFINE_METHOD(
Extend,
SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23. Either this or 'nvpcr' must be specified, not both, not neither."),
@ -11,7 +23,9 @@ static SD_VARLINK_DEFINE_METHOD(
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));
SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Event type to include in the (userspace) event log). This is optional, and mostly for debugging."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(eventType, EventType, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR);
@ -21,4 +35,6 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"),
SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"),
&vl_method_Extend,
SD_VARLINK_SYMBOL_COMMENT("Event type to store in event log"),
&vl_type_EventType,
&vl_error_NoSuchNvPCR);

View File

@ -9,6 +9,20 @@
#include "string-util.h"
#include "sysupdate-partition.h"
/* App-specific IDs used as HMAC keys to derive "partial" and "pending" partition type UUIDs from the
* original partition type UUID. This way we can indicate sysupdate transfer state via a separate GPT
* partition type UUID instead of using a label prefix, saving precious label space. */
#define GPT_SYSUPDATE_PARTIAL_APP_ID SD_ID128_MAKE(ac,cf,a0,c2,da,24,46,0a,9f,c9,0b,b8,fc,78,52,19)
#define GPT_SYSUPDATE_PENDING_APP_ID SD_ID128_MAKE(80,f3,d6,1e,23,83,43,b9,81,f5,ce,37,93,f4,7d,4c)
int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret) {
return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PARTIAL_APP_ID, ret);
}
int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret) {
return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PENDING_APP_ID, ret);
}
void partition_info_destroy(PartitionInfo *p) {
assert(p);
@ -242,6 +256,22 @@ int patch_partition(
return log_error_errno(r, "Failed to update partition UUID: %m");
}
if (change & PARTITION_TYPE) {
_cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *pt = NULL;
pt = fdisk_new_parttype();
if (!pt)
return log_oom();
r = fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type));
if (r < 0)
return log_error_errno(r, "Failed to initialize partition type: %m");
r = fdisk_partition_set_type(pa, pt);
if (r < 0)
return log_error_errno(r, "Failed to update partition type: %m");
}
type = gpt_partition_type_from_uuid(info->type);
/* Tweak the read-only flag, but only if supported by the partition type */

View File

@ -12,7 +12,8 @@ typedef enum PartitionChange {
PARTITION_GROWFS = 1 << 3,
PARTITION_UUID = 1 << 4,
PARTITION_LABEL = 1 << 5,
_PARTITION_CHANGE_MAX = (1 << 6) - 1, /* all of the above */
PARTITION_TYPE = 1 << 6,
_PARTITION_CHANGE_MAX = (1 << 7) - 1, /* all of the above */
_PARTITION_CHANGE_INVALID = -EINVAL,
} PartitionChange;
@ -40,5 +41,8 @@ int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src);
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret);
int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret);
int find_suitable_partition(const char *device, uint64_t space, sd_id128_t *partition_type, PartitionInfo *ret);
int patch_partition(const char *device, const PartitionInfo *info, PartitionChange change);

View File

@ -31,6 +31,7 @@
#include "strv.h"
#include "sysupdate-cache.h"
#include "sysupdate-instance.h"
#include "sysupdate-partition.h"
#include "sysupdate-pattern.h"
#include "sysupdate-resource.h"
#include "time-util.h"
@ -244,9 +245,7 @@ static int resource_load_from_blockdev(Resource *rr) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
Instance *instance;
const char *pinfo_label_stripped;
bool is_partial = false, is_pending = false;
const char *stripped;
r = read_partition_info(c, t, i, &pinfo);
if (r < 0)
@ -254,9 +253,28 @@ static int resource_load_from_blockdev(Resource *rr) {
if (r == 0) /* not assigned */
continue;
/* Check if partition type matches */
if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
continue;
/* Check if partition type matches, either directly or via derived partial/pending type
* UUIDs. The derived UUIDs are computed from the configured partition type by hashing it
* with a fixed app-specific ID, so we can detect the state without relying on label
* prefixes. */
if (rr->partition_type_set) {
sd_id128_t partial_type, pending_type;
r = gpt_partition_type_uuid_for_sysupdate_partial(rr->partition_type.uuid, &partial_type);
if (r < 0)
return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
r = gpt_partition_type_uuid_for_sysupdate_pending(rr->partition_type.uuid, &pending_type);
if (r < 0)
return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
if (sd_id128_equal(pinfo.type, partial_type))
is_partial = true;
else if (sd_id128_equal(pinfo.type, pending_type))
is_pending = true;
else if (!sd_id128_equal(pinfo.type, rr->partition_type.uuid))
continue;
}
/* A label of "_empty" means "not used so far" for us */
if (streq_ptr(pinfo.label, "_empty")) {
@ -264,18 +282,7 @@ static int resource_load_from_blockdev(Resource *rr) {
continue;
}
/* Match the label with any partial/pending prefix removed so the users existing patterns
* match regardless of the instances state. */
if ((stripped = startswith(pinfo.label, "PRT#"))) {
pinfo_label_stripped = stripped;
is_partial = true;
} else if ((stripped = startswith(pinfo.label, "PND#"))) {
pinfo_label_stripped = stripped;
is_pending = true;
} else
pinfo_label_stripped = pinfo.label;
r = pattern_match_many(rr->patterns, pinfo_label_stripped, &extracted_fields);
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))

View File

@ -67,8 +67,6 @@ Transfer* transfer_free(Transfer *t) {
strv_free(t->appstream);
partition_info_destroy(&t->partition_info);
free(t->temporary_partial_partition_label);
free(t->temporary_pending_partition_label);
free(t->final_partition_label);
resource_destroy(&t->source);
@ -766,12 +764,21 @@ static int transfer_instance_vacuum(
case RESOURCE_PARTITION: {
PartitionInfo pinfo = instance->partition_info;
PartitionChange change = PARTITION_LABEL;
/* label "_empty" means "no contents" for our purposes */
pinfo.label = (char*) "_empty";
log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
/* If the partition had a derived partial/pending type UUID, restore the original
* partition type so that the slot is properly recognized as empty in subsequent
* scans. */
if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
pinfo.type = t->target.partition_type.uuid;
change |= PARTITION_TYPE;
}
log_debug("Resetting partition '%s' to empty.", pinfo.device);
r = patch_partition(t->target.path, &pinfo, change);
if (r < 0)
return r;
@ -1172,7 +1179,7 @@ static int run_callout(
* and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
* skipped). */
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
_cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
_cleanup_free_ char *formatted_pattern = NULL;
int r;
assert(t);
@ -1182,8 +1189,6 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
assert(!t->temporary_partial_path);
assert(!t->temporary_pending_path);
assert(!t->final_partition_label);
assert(!t->temporary_partial_partition_label);
assert(!t->temporary_pending_partition_label);
assert(!strv_isempty(t->target.patterns));
/* Format the target name using the first pattern specified */
@ -1234,25 +1239,18 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
return log_oom();
r = gpt_partition_label_valid(formatted_partial_pattern);
if (!t->target.partition_type_set)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition type must be set for partition targets.");
/* Derive temporary partition type UUIDs for partial/pending states from the configured
* partition type. This avoids the need for label prefixes. */
r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
return log_oom();
r = gpt_partition_label_valid(formatted_pending_pattern);
r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
if (r < 0)
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
t->final_partition_label = TAKE_PTR(formatted_pattern);
}
@ -1312,13 +1310,18 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
where = t->partition_info.device;
/* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
/* Set the partition label and change the partition type to the derived "partial" type UUID
* to indicate that a transfer to it is in progress. */
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL;
t->partition_info.type = t->partition_type_partial;
t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
t->partition_info.device,
t->partition_info.label,
SD_ID128_TO_UUID_STRING(t->partition_info.type));
r = patch_partition(
t->target.path,
&t->partition_info,
@ -1545,12 +1548,10 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
}
if (t->target.type == RESOURCE_PARTITION) {
/* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete
* and the partition is ready for install. */
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL;
/* Now change the partition type to the derived "pending" type UUID to indicate that the
* acquire is complete and the partition is ready for install. */
t->partition_info.type = t->partition_type_pending;
t->partition_change = PARTITION_TYPE;
if (f->partition_uuid_set) {
t->partition_info.uuid = f->partition_uuid;
@ -1577,7 +1578,9 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
t->partition_change |= PARTITION_GROWFS;
}
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
log_debug("Marking partition '%s' as pending (type=%s).",
t->partition_info.device,
SD_ID128_TO_UUID_STRING(t->partition_info.type));
r = patch_partition(
t->target.path,
&t->partition_info,
@ -1617,7 +1620,7 @@ int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
/* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
* already happened in the acquire phase, the target should already have that information and it
* should already have been claimed as `PND#`. */
* should already have been claimed with the pending partition type UUID. */
if (t->target.type == RESOURCE_PARTITION) {
assert(i->resource == &t->target);
assert(i->is_pending);
@ -1665,14 +1668,17 @@ int transfer_install_instance(
t->temporary_pending_path = mfree(t->temporary_pending_path);
}
if (t->temporary_pending_partition_label) {
if (t->final_partition_label) {
assert(t->target.type == RESOURCE_PARTITION);
assert(t->final_partition_label);
assert(t->target.partition_type_set);
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
if (r < 0)
return r;
t->partition_change = PARTITION_LABEL;
/* Restore the original partition type UUID now that the partition is fully installed. */
t->partition_info.type = t->target.partition_type.uuid;
t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
r = patch_partition(
t->target.path,

View File

@ -48,8 +48,11 @@ typedef struct Transfer {
PartitionInfo partition_info;
PartitionChange partition_change;
char *final_partition_label;
char *temporary_partial_partition_label;
char *temporary_pending_partition_label;
/* Derived partition type UUIDs used to indicate partial/pending state on the partition type level,
* instead of polluting the partition label with prefixes */
sd_id128_t partition_type_partial;
sd_id128_t partition_type_pending;
Context *context;
} Transfer;

View File

@ -405,6 +405,11 @@ executables += [
'dependencies' : libopenssl,
'conditions' : ['HAVE_OPENSSL'],
},
test_template + {
'sources' : files('test-pkcs7-util.c'),
'dependencies' : libopenssl,
'conditions' : ['HAVE_OPENSSL'],
},
test_template + {
'sources' : files('test-parse-util.c'),
'dependencies' : libm,

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "hexdecoct.h"
#include "iovec-util.h"
#include "pkcs7-util.h"
#include "tests.h"
TEST(pkcs7_extract_signers) {
const char tsig[] =
"MIIEgwYJKoZIhvcNAQcCoIIEdDCCBHACAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLp"
"MIIC5TCCAc2gAwIBAgIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UE"
"AwwQbWtvc2kgb2YgbGVubmFydDAeFw0yNTAyMDMxMTAwMjNaFw0yNzAyMDMxMTAwMjNaMBsxGTAXBgNV"
"BAMMEG1rb3NpIG9mIGxlbm5hcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCldQdmHVgU"
"m4saaSqEpF1EVRf9pcIxpVShROBGAbxpxs4BQ7vV7zCg5bXwEVaqaENlIyzqPAj+YQifQS3Dj6LQfH3i"
"War+ciKAv4PYcESG+pcdBb2kJOS8cVD6abZlO9rInvdhXK6PhYF7VohMwMPj/yJYdO50skwA5OQsCHO6"
"amowh0tVzNzpbaJZg6wWIyTf3+ZzZdnOl5EvBCaUqeUhbaxRV9SrAw51rzAOnndUhW1we/vVKEGAPk3c"
"SCb0LLPBG20XX2C00UXgnCWBkU5rkq6BnWSuInhFKCa48avstIet1ZFBA5T5J83ncU9UOVFksEYBXFoc"
"lzcmcbx/2/oFAgMBAAGjITAfMB0GA1UdDgQWBBQewILFCixwq2rejOvJqmZSug2BBDANBgkqhkiG9w0B"
"AQsFAAOCAQEAIvaeNPaJUoIUN5lQC/kcCiKeys96WNRGL2wbTp5PqdnRw14sbWY5iC2z13ih3dmTI9NF"
"TBa7C/ji+5BaAfiJF17LOV00Y8eP5V94fHz4isb0sv5RzLsE4h8X7QFk4JBdV5GiCDzPXjxQAx9kM2so"
"9RGtL8EhHpNygYDgyZ18YeiwcUPkCXT+xG2rM6s/Xlsji0s/18ycI4G8AC8dj5HycyS9BiZHgKrkgqTb"
"VPo4zHYzhZdh0Qrd0J4YpoaotzQ35bkH9PtIkF6C7mE1Z7uMSGFkGQASgJ0BDTpM8QPAf2HIR2xxEtJR"
"ZXkwxxdC+W9AJAzqJldmCHYGSrSR54J0rDGCAV4wggFaAgEBMDMwGzEZMBcGA1UEAwwQbWtvc2kgb2Yg"
"bGVubmFydAIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJYIZIAWUDBAIBBQAwDQYJKoZIhvcNAQEBBQAE"
"ggEAXccqvpiEWsz/xvuLhINVZKIOznVdqjkERbZSqCBK94BYESSd+cijaB4XbYaFUZ45Bb3uUDQ56Ojq"
"WoY1elEfqPyCb4vc887QoHmxI0BtdIaHhIDfCGBxhX8fwMknxqjgFa9YvONmDtv4QG4syTw+U3SEqBaa"
"Avftqaa4v4eLk4uZ0nMIgMkx4qOlaxknpP404/nyZPANkOIwDxviNtRBCN9zSiPSqo1zre1vqzaM57Ww"
"8zJASsPEzNR7OsPoLaIZv2OHXpowsRB78TuXGkQnm74T6xdG6DNs24jTYJuCPfGuYLHbrytdhXpFBS6m"
"Orz9715jK2NU5VvGhNVXX4chcw==";
_cleanup_free_ void *sig = NULL;
size_t siglen;
ASSERT_OK(unbase64mem(tsig, &sig, &siglen));
size_t n_signers = 0;
Signer *signers = NULL;
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
ASSERT_OK_EQ(pkcs7_extract_signers(&IOVEC_MAKE(sig, siglen), &signers, &n_signers), 1);
ASSERT_EQ(n_signers, 1U);
ASSERT_EQ(signers[0].issuer.iov_len, 29U);
ASSERT_EQ(signers[0].serial.iov_len, 22U);
_cleanup_free_ char *issuer = NULL;
ASSERT_OK(base64mem(signers[0].issuer.iov_base, signers[0].issuer.iov_len, &issuer));
_cleanup_free_ char *serial = NULL;
ASSERT_OK(base64mem(signers[0].serial.iov_base, signers[0].serial.iov_len, &serial));
ASSERT_STREQ(issuer, "MBsxGTAXBgNVBAMMEG1rb3NpIG9mIGxlbm5hcnQ=");
ASSERT_STREQ(serial, "AhRGW+WPlqTRmG81LyEQ00rBW/rTHQ==");
}
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -31,12 +31,12 @@ executables += [
'HAVE_TPM2',
],
},
]
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
nvpcrs = [ 'cryptsetup',
'hardware' ]
'hardware',
'verity']
foreach n : nvpcrs
custom_target(
input : 'nvpcr/' + n + '.nvpcr.in',

View File

@ -0,0 +1,5 @@
{
"name" : "verity",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 2}}
}

View File

@ -16,9 +16,11 @@
#include "main-func.h"
#include "parse-util.h"
#include "path-util.h"
#include "pcrextend-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-util.h"
#include "verbs.h"
static char *arg_hash = NULL; /* the hash algorithm */
@ -38,12 +40,14 @@ static uint64_t arg_fec_roots = 2;
static void *arg_root_hash_signature = NULL;
static size_t arg_root_hash_signature_size = 0;
static bool arg_root_hash_signature_auto = false;
static char *arg_tpm2_measure_nvpcr = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -142,7 +146,7 @@ static int parse_options(const char *options) {
for (;;) {
_cleanup_free_ char *word = NULL;
char *val;
const char *val;
r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
if (r < 0)
@ -290,6 +294,21 @@ static int parse_options(const char *options) {
if (r < 0)
return r;
} else if ((val = startswith(word, "tpm2-measure-nvpcr="))) {
r = isempty(val) ? 0 : parse_boolean(val);
if (r == 0) {
arg_tpm2_measure_nvpcr = mfree(arg_tpm2_measure_nvpcr);
return 0;
}
if (r > 0)
val = "verity";
else if (!tpm2_nvpcr_name_is_valid(val)) {
log_warning("Invalid NvPCR name, ignoring: %s", word);
return 0;
}
if (free_and_strdup(&arg_tpm2_measure_nvpcr, val) < 0)
return log_oom();
} else
log_warning("Encountered unknown option '%s', ignoring.", word);
}
@ -423,6 +442,11 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to set up verity device '%s': %m", volume);
(void) pcrextend_verity_now(
volume,
&IOVEC_MAKE(rh, rh_size),
&IOVEC_MAKE(arg_root_hash_signature, arg_root_hash_signature_size));
return 0;
}

View File

@ -20,7 +20,8 @@ at_exit() {
jq --seq --slurp </run/log/systemd/tpm2-measure.log
fi
rm -rf /run/nvpcr
rm -rf /run/nvpcr /tmp/nvpcr
rm -f /var/tmp/nvpcr.raw /run/verity.d/test-79-nvpcr.crt
}
trap at_exit EXIT
@ -52,3 +53,46 @@ test "$DIGEST_ACTUAL2" != "$DIGEST_EXPECTED"
DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2"
mkdir /tmp/nvpcr
OPENSSL_CONFIG="/tmp/nvpcr/opensslconfig"
# Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
cat >"${OPENSSL_CONFIG:?}" <<EOF
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
C = DE
ST = Test State
L = Test Locality
O = Org Name
OU = Org Unit Name
CN = Common Name
emailAddress = test@email.com
EOF
openssl req -config "$OPENSSL_CONFIG" -subj="/CN=waldo" \
-x509 -sha256 -nodes -days 365 -newkey rsa:4096 \
-keyout /tmp/nvpcr/test-70-nvpcr.key -out /tmp/nvpcr/test-70-nvpcr.crt
mkdir /tmp/nvpcr/tree
touch /tmp/nvpcr/tree/file
SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs \
systemd-repart -P \
-s /tmp/nvpcr/tree \
--certificate=/tmp/nvpcr/test-70-nvpcr.crt \
--private-key=/tmp/nvpcr/test-70-nvpcr.key \
/var/tmp/nvpcr.raw
mkdir -p /run/verity.d
cp /tmp/nvpcr/test-70-nvpcr.crt /run/verity.d/
cp /run/log/systemd/tpm2-measure.log /tmp/nvpcr/log-before
systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/nvpcr.raw
set +o pipefail
diff /tmp/nvpcr/log-before /run/log/systemd/tpm2-measure.log | grep -F '"content":{"nvIndexName":"verity","string":"verity:'