summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/hexdecoct.c114
-rw-r--r--src/basic/special.h4
-rw-r--r--src/boot/efi/meson.build13
-rw-r--r--src/boot/efi/secure-boot.c59
-rw-r--r--src/boot/efi/ticks.c1
-rw-r--r--src/boot/efi/util.c17
-rw-r--r--src/boot/efi/util.h8
-rw-r--r--src/boot/efi/vmm.c156
-rw-r--r--src/boot/efi/vmm.h2
-rw-r--r--src/boot/pcrphase.c290
-rw-r--r--src/busctl/busctl.c30
-rw-r--r--src/coredump/coredump.c2
-rw-r--r--src/cryptsetup/cryptsetup.c235
-rw-r--r--src/fstab-generator/fstab-generator.c27
-rw-r--r--src/fundamental/memory-util-fundamental.h28
-rw-r--r--src/fundamental/tpm-pcr.h3
-rw-r--r--src/gpt-auto-generator/gpt-auto-generator.c42
-rw-r--r--src/home/homework-fscrypt.c58
-rw-r--r--src/libsystemd-network/dhcp6-internal.h1
-rw-r--r--src/libsystemd-network/dhcp6-option.c25
-rw-r--r--src/libsystemd-network/dhcp6-protocol.c1
-rw-r--r--src/libsystemd-network/dhcp6-protocol.h1
-rw-r--r--src/libsystemd-network/sd-dhcp-client.c38
-rw-r--r--src/libsystemd-network/sd-dhcp6-client.c83
-rw-r--r--src/libsystemd-network/sd-dhcp6-lease.c6
-rw-r--r--src/libsystemd-network/test-dhcp6-client.c82
-rw-r--r--src/libsystemd/sd-device/device-private.h1
-rw-r--r--src/libsystemd/sd-device/sd-device.c14
-rw-r--r--src/network/networkd-address.c6
-rw-r--r--src/network/networkd-dhcp4.c3
-rw-r--r--src/network/networkd-dhcp6.c6
-rw-r--r--src/network/networkd-ndisc.c3
-rw-r--r--src/network/networkd-network-gperf.gperf3
-rw-r--r--src/network/networkd-network.c1
-rw-r--r--src/network/networkd-network.h3
-rw-r--r--src/network/networkd-route.c6
-rw-r--r--src/shared/ask-password-api.c66
-rw-r--r--src/shared/creds-util.c60
-rw-r--r--src/shared/efi-loader.c39
-rw-r--r--src/shared/efi-loader.h2
-rw-r--r--src/shared/ethtool-util.c11
-rw-r--r--src/shared/generator.c209
-rw-r--r--src/shared/generator.h10
-rw-r--r--src/shared/tpm2-util.c144
-rw-r--r--src/shared/tpm2-util.h3
-rw-r--r--src/systemctl/systemctl-list-dependencies.c31
-rw-r--r--src/systemctl/systemctl-util.c29
-rw-r--r--src/systemctl/systemctl-util.h3
-rw-r--r--src/systemd/sd-dhcp6-client.h1
-rw-r--r--src/test/test-hexdecoct.c17
-rw-r--r--src/udev/udev-rules.c17
51 files changed, 1486 insertions, 528 deletions
diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c
index dc3b948d8e..898ed83f86 100644
--- a/src/basic/hexdecoct.c
+++ b/src/basic/hexdecoct.c
@@ -110,12 +110,17 @@ static int unhex_next(const char **p, size_t *l) {
return r;
}
-int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) {
+int unhexmem_full(
+ const char *p,
+ size_t l,
+ bool secure,
+ void **ret,
+ size_t *ret_len) {
+
_cleanup_free_ uint8_t *buf = NULL;
size_t buf_size;
const char *x;
uint8_t *z;
- int r;
assert(p || l == 0);
@@ -128,22 +133,20 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
if (!buf)
return -ENOMEM;
+ CLEANUP_ERASE_PTR(secure ? &buf : NULL, buf_size);
+
for (x = p, z = buf;;) {
int a, b;
a = unhex_next(&x, &l);
if (a == -EPIPE) /* End of string */
break;
- if (a < 0) {
- r = a;
- goto on_failure;
- }
+ if (a < 0)
+ return a;
b = unhex_next(&x, &l);
- if (b < 0) {
- r = b;
- goto on_failure;
- }
+ if (b < 0)
+ return b;
*(z++) = (uint8_t) a << 4 | (uint8_t) b;
}
@@ -156,12 +159,6 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
*ret = TAKE_PTR(buf);
return 0;
-
-on_failure:
- if (secure)
- explicit_bzero_safe(buf, buf_size);
-
- return r;
}
/* https://tools.ietf.org/html/rfc4648#section-6
@@ -765,12 +762,17 @@ static int unbase64_next(const char **p, size_t *l) {
return ret;
}
-int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) {
+int unbase64mem_full(
+ const char *p,
+ size_t l,
+ bool secure,
+ void **ret,
+ size_t *ret_size) {
+
_cleanup_free_ uint8_t *buf = NULL;
const char *x;
uint8_t *z;
size_t len;
- int r;
assert(p || l == 0);
@@ -785,60 +787,44 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
if (!buf)
return -ENOMEM;
+ CLEANUP_ERASE_PTR(secure ? &buf : NULL, len);
+
for (x = p, z = buf;;) {
int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
a = unbase64_next(&x, &l);
if (a == -EPIPE) /* End of string */
break;
- if (a < 0) {
- r = a;
- goto on_failure;
- }
- if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */
- r = -EINVAL;
- goto on_failure;
- }
+ if (a < 0)
+ return a;
+ if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
+ return -EINVAL;
b = unbase64_next(&x, &l);
- if (b < 0) {
- r = b;
- goto on_failure;
- }
- if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */
- r = -EINVAL;
- goto on_failure;
- }
+ if (b < 0)
+ return b;
+ if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
+ return -EINVAL;
c = unbase64_next(&x, &l);
- if (c < 0) {
- r = c;
- goto on_failure;
- }
+ if (c < 0)
+ return c;
d = unbase64_next(&x, &l);
- if (d < 0) {
- r = d;
- goto on_failure;
- }
+ if (d < 0)
+ return d;
if (c == INT_MAX) { /* Padding at the third character */
- if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
- r = -EINVAL;
- goto on_failure;
- }
+ if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+ return -EINVAL;
/* b == 00YY0000 */
- if (b & 15) {
- r = -EINVAL;
- goto on_failure;
- }
+ if (b & 15)
+ return -EINVAL;
- if (l > 0) { /* Trailing rubbish? */
- r = -ENAMETOOLONG;
- goto on_failure;
- }
+ if (l > 0) /* Trailing rubbish? */
+ return -ENAMETOOLONG;
*(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
break;
@@ -846,15 +832,11 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
if (d == INT_MAX) {
/* c == 00ZZZZ00 */
- if (c & 3) {
- r = -EINVAL;
- goto on_failure;
- }
+ if (c & 3)
+ return -EINVAL;
- if (l > 0) { /* Trailing rubbish? */
- r = -ENAMETOOLONG;
- goto on_failure;
- }
+ if (l > 0) /* Trailing rubbish? */
+ return -ENAMETOOLONG;
*(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
*(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
@@ -868,18 +850,14 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
*z = 0;
+ assert((size_t) (z - buf) <= len);
+
if (ret_size)
*ret_size = (size_t) (z - buf);
if (ret)
*ret = TAKE_PTR(buf);
return 0;
-
-on_failure:
- if (secure)
- explicit_bzero_safe(buf, len);
-
- return r;
}
void hexdump(FILE *f, const void *p, size_t s) {
diff --git a/src/basic/special.h b/src/basic/special.h
index 5d1111fd71..0e4342eb40 100644
--- a/src/basic/special.h
+++ b/src/basic/special.h
@@ -87,6 +87,10 @@
#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service"
#define SPECIAL_VOLATILE_ROOT_SERVICE "systemd-volatile-root.service"
#define SPECIAL_UDEVD_SERVICE "systemd-udevd.service"
+#define SPECIAL_GROWFS_SERVICE "systemd-growfs@.service"
+#define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service"
+#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service"
+#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service"
/* Services systemd relies on */
#define SPECIAL_DBUS_SERVICE "dbus.service"
diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build
index fb8ec26564..16342f891e 100644
--- a/src/boot/efi/meson.build
+++ b/src/boot/efi/meson.build
@@ -198,15 +198,6 @@ efi_cflags = [
]
)
-# Our code size has increased enough to possibly create overlapping PE sections
-# at sd-stub runtime, which will often enough prevent the image from booting.
-# This only happens because the usual instructions for assembling a unified
-# kernel image contain hardcoded addresses for section VMAs added in. Until a
-# proper solution is in place, we can at least compile with as least -O1 to
-# reduce the likelihood of this happening.
-# https://github.com/systemd/systemd/issues/24202
-efi_cflags += '-O1'
-
efi_cflags += cc.get_supported_arguments({
'ia32': ['-mno-sse', '-mno-mmx'],
'x86_64': ['-mno-red-zone', '-mno-sse', '-mno-mmx'],
@@ -373,6 +364,7 @@ common_sources = files(
'assert.c',
'console.c',
'devicetree.c',
+ 'drivers.c',
'disk.c',
'efi-string.c',
'graphics.c',
@@ -384,13 +376,12 @@ common_sources = files(
'secure-boot.c',
'ticks.c',
'util.c',
+ 'vmm.c',
)
systemd_boot_sources = files(
'boot.c',
- 'drivers.c',
'shim.c',
- 'vmm.c',
)
stub_sources = files(
diff --git a/src/boot/efi/secure-boot.c b/src/boot/efi/secure-boot.c
index 6212868134..571c3c3612 100644
--- a/src/boot/efi/secure-boot.c
+++ b/src/boot/efi/secure-boot.c
@@ -1,9 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "console.h"
#include "sbat.h"
#include "secure-boot.h"
-#include "console.h"
#include "util.h"
+#include "vmm.h"
bool secure_boot_enabled(void) {
bool secure = false; /* avoid false maybe-uninitialized warning */
@@ -43,34 +44,36 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path) {
clear_screen(COLOR_NORMAL);
- Print(L"Enrolling secure boot keys from directory: %s\n"
- L"Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n",
- path);
-
- unsigned timeout_sec = 15;
- for(;;) {
- /* Enrolling secure boot keys is safe to do in virtualized environments as there is nothing
- * we can brick there. */
- if (in_hypervisor())
- break;
-
- PrintAt(0, ST->ConOut->Mode->CursorRow, L"Enrolling in %2u s, press any key to abort.", timeout_sec);
-
- uint64_t key;
- err = console_key_read(&key, 1000 * 1000);
- if (err == EFI_NOT_READY)
- continue;
- if (err == EFI_TIMEOUT) {
- if (timeout_sec == 0) /* continue enrolling keys */
- break;
- timeout_sec--;
- continue;
+ Print(u"Enrolling secure boot keys from directory: %s\n", path);
+
+ /* Enrolling secure boot keys is safe to do in virtualized environments as there is nothing
+ * we can brick there. */
+ if (!in_hypervisor()) {
+ Print(u"Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n");
+
+ unsigned timeout_sec = 15;
+ for (;;) {
+ Print(u"\rEnrolling in %2u s, press any key to abort.", timeout_sec);
+
+ uint64_t key;
+ err = console_key_read(&key, 1000 * 1000);
+ if (err == EFI_NOT_READY)
+ continue;
+ if (err == EFI_TIMEOUT) {
+ if (timeout_sec == 0) /* continue enrolling keys */
+ break;
+ timeout_sec--;
+ continue;
+ }
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(
+ err,
+ L"Error waiting for user input to enroll Secure Boot keys: %r",
+ err);
+
+ /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
+ return EFI_SUCCESS;
}
- if (err != EFI_SUCCESS)
- return log_error_status_stall(err, L"Error waiting for user input to enroll Secure Boot keys: %r", err);
-
- /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
- return EFI_SUCCESS;
}
_cleanup_(file_closep) EFI_FILE *dir = NULL;
diff --git a/src/boot/efi/ticks.c b/src/boot/efi/ticks.c
index 1b74ba15d0..2f6ff878ca 100644
--- a/src/boot/efi/ticks.c
+++ b/src/boot/efi/ticks.c
@@ -5,6 +5,7 @@
#include "ticks.h"
#include "util.h"
+#include "vmm.h"
#ifdef __x86_64__
static uint64_t ticks_read(void) {
diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c
index b1c19a5c9b..202332c2d8 100644
--- a/src/boot/efi/util.c
+++ b/src/boot/efi/util.c
@@ -2,9 +2,6 @@
#include <efi.h>
#include <efilib.h>
-#if defined(__i386__) || defined(__x86_64__)
-# include <cpuid.h>
-#endif
#include "ticks.h"
#include "util.h"
@@ -748,20 +745,6 @@ EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret) {
return EFI_SUCCESS;
}
-#if defined(__i386__) || defined(__x86_64__)
-bool in_hypervisor(void) {
- uint32_t eax, ebx, ecx, edx;
-
- /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
- * environment. */
-
- if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
- return false;
-
- return !!(ecx & 0x80000000U);
-}
-#endif
-
void *find_configuration_table(const EFI_GUID *guid) {
for (UINTN i = 0; i < ST->NumberOfTableEntries; i++)
if (efi_guid_equal(&ST->ConfigurationTable[i].VendorGuid, guid))
diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h
index 836f223cc0..32ecea7d6a 100644
--- a/src/boot/efi/util.h
+++ b/src/boot/efi/util.h
@@ -210,14 +210,6 @@ EFI_STATUS open_volume(EFI_HANDLE device, EFI_FILE **ret_file);
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp);
EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret);
-#if defined(__i386__) || defined(__x86_64__)
-bool in_hypervisor(void);
-#else
-static inline bool in_hypervisor(void) {
- return false;
-}
-#endif
-
static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
return memcmp(a, b, sizeof(EFI_GUID)) == 0;
}
diff --git a/src/boot/efi/vmm.c b/src/boot/efi/vmm.c
index 10d4a75ab2..3dfa92b58d 100644
--- a/src/boot/efi/vmm.c
+++ b/src/boot/efi/vmm.c
@@ -3,6 +3,9 @@
#include <efi.h>
#include <efilib.h>
#include <stdbool.h>
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+#endif
#include "drivers.h"
#include "efi-string.h"
@@ -132,3 +135,156 @@ EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) {
}
assert_not_reached();
}
+
+static bool cpuid_in_hypervisor(void) {
+#if defined(__i386__) || defined(__x86_64__)
+ unsigned eax, ebx, ecx, edx;
+
+ /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
+ * environment. */
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
+ return false;
+
+ if (FLAGS_SET(ecx, 0x80000000U))
+ return true;
+#endif
+
+ return false;
+}
+
+typedef struct {
+ uint8_t anchor_string[4];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint16_t max_structure_size;
+ uint8_t entry_point_revision;
+ uint8_t formatted_area[5];
+ uint8_t intermediate_anchor_string[5];
+ uint8_t intermediate_checksum;
+ uint16_t table_length;
+ uint32_t table_address;
+ uint16_t number_of_smbios_structures;
+ uint8_t smbios_bcd_revision;
+} _packed_ SmbiosEntryPoint;
+
+typedef struct {
+ uint8_t anchor_string[5];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint8_t docrev;
+ uint8_t entry_point_revision;
+ uint8_t reserved;
+ uint32_t table_maximum_size;
+ uint64_t table_address;
+} _packed_ Smbios3EntryPoint;
+
+typedef struct {
+ uint8_t type;
+ uint8_t length;
+ uint8_t handle[2];
+} _packed_ SmbiosHeader;
+
+typedef struct {
+ SmbiosHeader header;
+ uint8_t vendor;
+ uint8_t bios_version;
+ uint16_t bios_segment;
+ uint8_t bios_release_date;
+ uint8_t bios_size;
+ uint64_t bios_characteristics;
+ uint8_t bios_characteristics_ext[2];
+} _packed_ SmbiosTableType0;
+
+static void *find_smbios_configuration_table(uint64_t *ret_size) {
+ assert(ret_size);
+
+ Smbios3EntryPoint *entry3 = find_configuration_table(&(EFI_GUID) SMBIOS3_TABLE_GUID);
+ if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
+ entry3->entry_point_length <= sizeof(*entry3)) {
+ *ret_size = entry3->table_maximum_size;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
+ }
+
+ SmbiosEntryPoint *entry = find_configuration_table(&(EFI_GUID) SMBIOS_TABLE_GUID);
+ if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
+ entry->entry_point_length <= sizeof(*entry)) {
+ *ret_size = entry->table_length;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry->table_address);
+ }
+
+ return NULL;
+}
+
+static SmbiosHeader *get_smbios_table(uint8_t type) {
+ uint64_t size = 0;
+ uint8_t *p = find_smbios_configuration_table(&size);
+ if (!p)
+ return false;
+
+ for (;;) {
+ if (size < sizeof(SmbiosHeader))
+ return NULL;
+
+ SmbiosHeader *header = (SmbiosHeader *) p;
+
+ /* End of table. */
+ if (header->type == 127)
+ return NULL;
+
+ if (size < header->length)
+ return NULL;
+
+ if (header->type == type)
+ return header; /* Yay! */
+
+ /* Skip over formatted area. */
+ size -= header->length;
+ p += header->length;
+
+ /* Skip over string table. */
+ for (;;) {
+ while (size > 0 && *p != '\0') {
+ p++;
+ size--;
+ }
+ if (size == 0)
+ return NULL;
+ p++;
+ size--;
+
+ /* Double NUL terminates string table. */
+ if (*p == '\0') {
+ if (size == 0)
+ return NULL;
+ p++;
+ break;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static bool smbios_in_hypervisor(void) {
+ /* Look up BIOS Information (Type 0). */
+ SmbiosTableType0 *type0 = (SmbiosTableType0 *) get_smbios_table(0);
+ if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
+ return false;
+
+ /* Bit 4 of 2nd BIOS characteristics extension bytes indicates virtualization. */
+ return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
+}
+
+bool in_hypervisor(void) {
+ static int cache = -1;
+ if (cache >= 0)
+ return cache;
+
+ cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
+ return cache;
+}
diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h
index 7bac1a324a..e98ec74af1 100644
--- a/src/boot/efi/vmm.h
+++ b/src/boot/efi/vmm.h
@@ -6,3 +6,5 @@
bool is_direct_boot(EFI_HANDLE device);
EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir);
+
+bool in_hypervisor(void);
diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c
index 14f79f23c0..003e0b8ad8 100644
--- a/src/boot/pcrphase.c
+++ b/src/boot/pcrphase.c
@@ -2,14 +2,21 @@
#include <getopt.h>
+#include <sd-device.h>
#include <sd-messages.h>
+#include "blkid-util.h"
+#include "blockdev-util.h"
#include "build.h"
+#include "chase-symlinks.h"
+#include "efi-loader.h"
#include "efivars.h"
-#include "env-util.h"
+#include "escape.h"
+#include "fd-util.h"
#include "main-func.h"
+#include "mountpoint-util.h"
#include "openssl-util.h"
-#include "parse-util.h"
+#include "parse-argument.h"
#include "pretty-print.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
@@ -17,9 +24,12 @@
static bool arg_graceful = false;
static char *arg_tpm2_device = NULL;
static char **arg_banks = NULL;
+static char *arg_file_system = NULL;
+static bool arg_machine_id = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
@@ -29,7 +39,9 @@ static int help(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_oom();
- printf("%1$s [OPTIONS...] WORD ...\n"
+ printf("%1$s [OPTIONS...] WORD\n"
+ "%1$s [OPTIONS...] --file-system=PATH\n"
+ "%1$s [OPTIONS...] --machine-id\n"
"\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
@@ -37,6 +49,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
" --graceful Exit gracefully if no TPM2 device is found\n"
+ " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
+ " --machine-id Measure machine ID into PCR 15\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -54,6 +68,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_BANK,
ARG_TPM2_DEVICE,
ARG_GRACEFUL,
+ ARG_FILE_SYSTEM,
+ ARG_MACHINE_ID,
};
static const struct option options[] = {
@@ -62,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) {
{ "bank", required_argument, NULL, ARG_BANK },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
+ { "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
+ { "machine-id", no_argument, NULL, ARG_MACHINE_ID },
{}
};
- int c;
+ int c, r;
assert(argc >= 0);
assert(argv);
@@ -113,6 +131,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_graceful = true;
break;
+ case ARG_FILE_SYSTEM:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_MACHINE_ID:
+ arg_machine_id = true;
+ break;
+
case '?':
return -EINVAL;
@@ -120,49 +149,101 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
+ if (arg_file_system && arg_machine_id)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
+
return 1;
}
-static int determine_banks(struct tpm2_context *c) {
- _cleanup_free_ TPMI_ALG_HASH *algs = NULL;
- int n_algs, r;
+static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
assert(c);
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
return 0;
- n_algs = tpm2_get_good_pcr_banks(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &algs);
- if (n_algs <= 0)
- return n_algs;
+ r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l);
+ if (r < 0)
+ return r;
+
+ strv_free_and_replace(arg_banks, l);
+ return 0;
+}
+
+static int get_file_system_word(
+ sd_device *d,
+ const char *prefix,
+ char **ret) {
+
+ int r;
+
+ assert(d);
+ assert(prefix);
+ assert(ret);
+
+ _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (block_fd < 0)
+ return block_fd;
+
+ _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe();
+ if (!b)
+ return -ENOMEM;
- for (int i = 0; i < n_algs; i++) {
- const EVP_MD *implementation;
- const char *salg;
+ errno = 0;
+ r = blkid_probe_set_device(b, block_fd, 0, 0);
+ if (r != 0)
+ return errno_or_else(ENOMEM);
- salg = tpm2_pcr_bank_to_string(algs[i]);
- if (!salg)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
+ (void) blkid_probe_enable_superblocks(b, 1);
+ (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID|BLKID_SUBLKS_LABEL);
+ (void) blkid_probe_enable_partitions(b, 1);
+ (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
- implementation = EVP_get_digestbyname(salg);
- if (!implementation)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (r == _BLKID_SAFEPROBE_ERROR)
+ return errno_or_else(EIO);
+ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND))
+ return -ENOPKG;
- r = strv_extend(&arg_banks, EVP_MD_name(implementation));
+ assert(r == _BLKID_SAFEPROBE_FOUND);
+
+ _cleanup_strv_free_ char **l = strv_new(prefix);
+ if (!l)
+ return log_oom();
+
+ FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") {
+ const char *v = NULL;
+
+ (void) blkid_probe_lookup_value(b, field, &v, NULL);
+
+ _cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */
+ if (!escaped)
+ return log_oom();
+
+ r = strv_consume(&l, TAKE_PTR(escaped));
if (r < 0)
return log_oom();
+
}
+ assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */
+
+ _cleanup_free_ char *word = strv_join(l, ":");
+ if (!word)
+ return log_oom();
+
+ *ret = TAKE_PTR(word);
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
- _cleanup_free_ char *joined = NULL, *pcr_string = NULL;
- const char *word;
- unsigned pcr_nr;
+ _cleanup_free_ char *joined = NULL, *word = NULL;
+ unsigned target_pcr_nr;
size_t length;
- TSS2_RC rc;
int r;
log_setup();
@@ -171,16 +252,79 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
- if (optind+1 != argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
+ if (arg_file_system) {
+ _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ _cleanup_close_ int dfd = -EBADF;
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
+
+ dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
+ if (dfd < 0)
+ return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
- word = argv[optind];
+ r = fd_is_mount_point(dfd, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized);
+
+ normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */
+ if (!normalized_escaped)
+ return log_oom();
- /* Refuse to measure an empty word. We want to be able to write the series of measured words
- * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
- * disallow an empty word to avoid ambiguities. */
- if (isempty(word))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
+ _cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped);
+ if (!prefix)
+ return log_oom();
+
+ r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
+ if (r < 0) {
+ log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system);
+
+ word = strjoin(prefix, "::::::");
+ if (!word)
+ return log_oom();
+ } else {
+ r = get_file_system_word(d, prefix, &word);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system);
+ }
+
+ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
+
+ } else if (arg_machine_id) {
+ sd_id128_t mid;
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire machine ID: %m");
+
+ word = strjoin("machine-id:", SD_ID128_TO_STRING(mid));
+ if (!word)
+ return log_oom();
+
+ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
+
+ } else {
+ if (optind+1 != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
+
+ word = strdup(argv[optind]);
+ if (!word)
+ return log_oom();
+
+ /* Refuse to measure an empty word. We want to be able to write the series of measured words
+ * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
+ * disallow an empty word to avoid ambiguities. */
+ if (isempty(word))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
+
+ target_pcr_nr = TPM_PCR_INDEX_KERNEL_IMAGE; /* → PCR 11 */
+ }
if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
@@ -189,32 +333,13 @@ static int run(int argc, char *argv[]) {
length = strlen(word);
- int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY");
- if (b < 0 && b != -ENXIO)
- log_warning_errno(b, "Unable to parse $SYSTEMD_PCRPHASE_STUB_VERIFY value, ignoring.");
-
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
- r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
- if (r == -ENOENT) {
- if (b != 0) {
- log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE);
- return EXIT_SUCCESS;
- } else
- log_notice("Kernel stub did not measure kernel image into PCR %u, but told to measure anyway, hence proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
- } else if (r < 0)
- return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m");
- else {
- /* Let's validate that the stub announced PCR 11 as we expected. */
- r = safe_atou(pcr_string, &pcr_nr);
- if (r < 0)
- return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
- if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) {
- if (b != 0)
- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
- else
- log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
- } else
- log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ r = efi_stub_measured();
+ if (r < 0)
+ return log_error_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m");
+ if (r == 0) {
+ log_info("Kernel stub did not measure kernel image into PCR %u, skipping userspace measurement, too.", TPM_PCR_INDEX_KERNEL_IMAGE);
+ return EXIT_SUCCESS;
}
r = dlopen_tpm2();
@@ -225,62 +350,27 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
- r = determine_banks(&c);
+ r = determine_banks(&c, target_pcr_nr);
if (r < 0)
return r;
if (strv_isempty(arg_banks)) /* Still none? */
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
- TPML_DIGEST_VALUES values = {};
- STRV_FOREACH(bank, arg_banks) {
- const EVP_MD *implementation;
- int id;
-
- assert_se(implementation = EVP_get_digestbyname(*bank));
-
- if (values.count >= ELEMENTSOF(values.digests))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
-
- if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
-
- id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
- if (id < 0)
- return log_error_errno(id, "Can't map hash name to TPM2.");
-
- values.digests[values.count].hashAlg = id;
-
- if (EVP_Digest(word, length, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word.");
-
- values.count++;
- }
-
joined = strv_join(arg_banks, ", ");
if (!joined)
return log_oom();
- log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined);
-
- rc = sym_Esys_PCR_Extend(
- c.esys_context,
- ESYS_TR_PCR0 + TPM_PCR_INDEX_KERNEL_IMAGE, /* → PCR 11 */
- ESYS_TR_PASSWORD,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &values);
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(
- SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to measure '%s': %s",
- word,
- sym_Tss2_RC_Decode(rc));
+ log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined);
+
+ r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0);
+ if (r < 0)
+ return r;
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
- LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined),
+ LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr, word, joined),
"MEASURING=%s", word,
- "PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE,
+ "PCR=%u", target_pcr_nr,
"BANKS=%s", joined);
return EXIT_SUCCESS;
diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c
index 2b5552889b..87becdbc7e 100644
--- a/src/busctl/busctl.c
+++ b/src/busctl/busctl.c
@@ -1022,17 +1022,16 @@ static int introspect(int argc, char **argv, void *userdata) {
return bus_log_parse_error(r);
for (;;) {
- Member *z;
- _cleanup_free_ char *buf = NULL, *signature = NULL;
_cleanup_fclose_ FILE *mf = NULL;
- size_t sz = 0;
+ _cleanup_free_ char *buf = NULL;
const char *name, *contents;
+ size_t sz = 0;
+ Member *z;
char type;
r = sd_bus_message_enter_container(reply, 'e', "sv");
if (r < 0)
return bus_log_parse_error(r);
-
if (r == 0)
break;
@@ -1040,24 +1039,15 @@ static int introspect(int argc, char **argv, void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
- r = sd_bus_message_enter_container(reply, 'v', NULL);
+ r = sd_bus_message_peek_type(reply, &type, &contents);
if (r < 0)
return bus_log_parse_error(r);
+ if (type != 'v')
+ return bus_log_parse_error(EINVAL);
- r = sd_bus_message_peek_type(reply, &type, &contents);
- if (r <= 0)
- return bus_log_parse_error(r == 0 ? EINVAL : r);
-
- if (type == SD_BUS_TYPE_STRUCT_BEGIN)
- signature = strjoin(CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END));
- else if (type == SD_BUS_TYPE_DICT_ENTRY_BEGIN)
- signature = strjoin(CHAR_TO_STR(SD_BUS_TYPE_DICT_ENTRY_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_DICT_ENTRY_END));
- else if (contents)
- signature = strjoin(CHAR_TO_STR(type), contents);
- else
- signature = strdup(CHAR_TO_STR(type));
- if (!signature)
- return log_oom();
+ r = sd_bus_message_enter_container(reply, 'v', contents);
+ if (r < 0)
+ return bus_log_parse_error(r);
mf = open_memstream_unlocked(&buf, &sz);
if (!mf)
@@ -1072,7 +1062,7 @@ static int introspect(int argc, char **argv, void *userdata) {
z = set_get(members, &((Member) {
.type = "property",
.interface = m->interface,
- .signature = signature,
+ .signature = (char*) contents,
.name = (char*) name }));
if (z)
free_and_replace(z->value, buf);
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index 192dc4c654..013ebb4c28 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -597,7 +597,7 @@ static int save_external_coredump(
/* tmpfs might get full quickly, so check the available space too.
* But don't worry about errors here, failing to access the storage
* location will be better logged when writing to it. */
- if (statvfs("/var/lib/systemd/coredump/", &sv) >= 0)
+ if (fstatvfs(fd, &sv) >= 0)
max_size = MIN((uint64_t)sv.f_frsize * (uint64_t)sv.f_bfree, max_size);
log_debug("Limiting core file size to %" PRIu64 " bytes due to cgroup memory limits.", max_size);
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index cd2065f480..2c9d416734 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -8,6 +8,7 @@
#include <unistd.h>
#include "sd-device.h"
+#include "sd-messages.h"
#include "alloc-util.h"
#include "ask-password-api.h"
@@ -18,6 +19,7 @@
#include "cryptsetup-util.h"
#include "device-util.h"
#include "efi-api.h"
+#include "efi-loader.h"
#include "env-util.h"
#include "escape.h"
#include "fileio.h"
@@ -38,6 +40,7 @@
#include "random-util.h"
#include "string-table.h"
#include "strv.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
/* internal helper */
@@ -89,13 +92,15 @@ static bool arg_fido2_device_auto = false;
static void *arg_fido2_cid = NULL;
static size_t arg_fido2_cid_size = 0;
static char *arg_fido2_rp_id = NULL;
-static char *arg_tpm2_device = NULL;
+static char *arg_tpm2_device = NULL; /* These and the following fields are about locking an encypted volume to the local TPM */
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
+static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
+static char **arg_tpm2_measure_banks = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
@@ -107,6 +112,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
@@ -420,6 +426,48 @@ static int parse_one_option(const char *option) {
arg_tpm2_pin = r;
+ } else if ((val = startswith(option, "tpm2-measure-pcr="))) {
+ unsigned pcr;
+
+ r = safe_atou(val, &pcr);
+ if (r < 0) {
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX;
+ } else if (pcr >= TPM2_PCRS_MAX) {
+ log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1);
+ return 0;
+ }
+
+ arg_tpm2_measure_pcr = pcr;
+
+ } else if ((val = startswith(option, "tpm2-measure-bank="))) {
+
+#if HAVE_OPENSSL
+ _cleanup_strv_free_ char **l = NULL;
+
+ l = strv_split(optarg, ":");
+ if (!l)
+ return log_oom();
+
+ STRV_FOREACH(i, l) {
+ const EVP_MD *implementation;
+
+ implementation = EVP_get_digestbyname(*i);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val);
+
+ if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0)
+ return log_oom();
+ }
+#else
+ log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option);
+#endif
+
} else if ((val = startswith(option, "try-empty-password="))) {
r = parse_boolean(val);
@@ -762,6 +810,157 @@ static int get_password(
return 0;
}
+static int measure_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(volume_key);
+ assert(volume_key_size > 0);
+
+ if (arg_tpm2_measure_pcr == UINT_MAX) {
+ log_debug("Not measuring volume key, deactivated.");
+ return 0;
+ }
+
+ r = efi_stub_measured();
+ if (r < 0)
+ return log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m");
+ if (r == 0) {
+ log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace measurement, too.");
+ return 0;
+ }
+
+#if HAVE_TPM2
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_error_errno(r, "Failed to load TPM2 libraries: %m");
+
+ _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+ r = tpm2_context_init(arg_tpm2_device, &c);
+ if (r < 0)
+ return r;
+
+ _cleanup_strv_free_ char **l = NULL;
+ if (strv_isempty(arg_tpm2_measure_banks)) {
+ r = tpm2_get_good_pcr_banks_strv(c.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l);
+ if (r < 0)
+ return r;
+ }
+
+ _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", ");
+ if (!joined)
+ return log_oom();
+
+ /* Note: we don't directly measure the volume key, it might be a security problem to send an
+ * unprotected direct hash of the secret volume key over the wire to the TPM. Hence let's instead
+ * send a HMAC signature instead. */
+
+ _cleanup_free_ char *escaped = NULL;
+ escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */
+ if (!escaped)
+ return log_oom();
+
+ _cleanup_free_ char *s = NULL;
+ s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd)));
+ if (!s)
+ return log_oom();
+
+ r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size);
+ if (r < 0)
+ return r;
+
+ log_struct(LOG_INFO,
+ "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
+ LOG_MESSAGE("Successfully extended PCR index %u with '%s' and volume key (banks %s).", arg_tpm2_measure_pcr, s, joined),
+ "MEASURING=%s", s,
+ "PCR=%u", arg_tpm2_measure_pcr,
+ "BANKS=%s", joined);
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
+#endif
+}
+
+static int measured_crypt_activate_by_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size,
+ uint32_t flags) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+
+ /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */
+
+ r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags);
+ if (r < 0)
+ return r;
+
+ if (volume_key_size == 0) {
+ log_debug("Not measuring volume key, none specified.");
+ return r;
+ }
+
+ (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */
+ return r;
+}
+
+static int measured_crypt_activate_by_passphrase(
+ struct crypt_device *cd,
+ const char *name,
+ int keyslot,
+ const char *passphrase,
+ size_t passphrase_size,
+ uint32_t flags) {
+
+ _cleanup_(erase_and_freep) void *vk = NULL;
+ size_t vks;
+ int r;
+
+ assert(cd);
+
+ /* A wrapper around crypt_activate_by_passphrase() which also measures to a PCR if that's
+ * requested. Note that we need the volume key for the measurement, and
+ * crypt_activate_by_passphrase() doesn't give us access to this. Hence, we operate indirectly, and
+ * retrieve the volume key first, and then activate through that. */
+
+ if (arg_tpm2_measure_pcr == UINT_MAX) {
+ log_debug("Not measuring volume key, deactivated.");
+ goto shortcut;
+ }
+
+ r = crypt_get_volume_key_size(cd);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("Not measuring volume key, none defined.");
+ goto shortcut;
+ }
+
+ vk = malloc(vks = r);
+ if (!vk)
+ return -ENOMEM;
+
+ r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size);
+ if (r < 0)
+ return r;
+
+ return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags);
+
+shortcut:
+ return crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);
+}
+
static int attach_tcrypt(
struct crypt_device *cd,
const char *name,
@@ -830,7 +1029,7 @@ static int attach_tcrypt(
return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd));
}
- r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
if (r < 0)
return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd));
@@ -928,6 +1127,14 @@ static int run_security_device_monitor(
}
static bool libcryptsetup_plugins_support(void) {
+
+#if HAVE_TPM2
+ /* Currently, there's no way for us to query the volume key when plugins are used. Hence don't use
+ * plugins, if measurement has been requested. */
+ if (arg_tpm2_measure_pcr != UINT_MAX)
+ return false;
+#endif
+
#if HAVE_LIBCRYPTSETUP_PLUGINS
int r;
@@ -1157,7 +1364,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
}
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1168,7 +1375,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)");
@@ -1305,7 +1512,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1322,7 +1529,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)");
@@ -1594,7 +1801,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1605,7 +1812,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)");
@@ -1632,9 +1839,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data(
assert(key_data);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate. (Key incorrect?)");
return -EAGAIN; /* Log actual error, but return EAGAIN */
@@ -1685,9 +1892,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_file(
return log_error_errno(r, "Failed to read key file '%s': %m", key_file);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file);
return -EAGAIN; /* Log actual error, but return EAGAIN */
@@ -1713,9 +1920,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase(
r = -EINVAL;
STRV_FOREACH(p, passwords) {
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
if (r >= 0)
break;
}
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index b0ea536eb2..ed34e0a32f 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -8,6 +8,7 @@
#include "bus-error.h"
#include "bus-locator.h"
#include "chase-symlinks.h"
+#include "efi-loader.h"
#include "fd-util.h"
#include "fileio.h"
#include "fstab-util.h"
@@ -40,6 +41,7 @@ typedef enum MountPointFlags {
MOUNT_MAKEFS = 1 << 3,
MOUNT_GROWFS = 1 << 4,
MOUNT_RW_ONLY = 1 << 5,
+ MOUNT_PCRFS = 1 << 6,
} MountPointFlags;
static bool arg_sysroot_check = false;
@@ -176,6 +178,8 @@ static int add_swap(
if (flags & MOUNT_GROWFS)
/* TODO: swap devices must be wiped and recreated */
log_warning("%s: growing swap devices is currently unsupported.", what);
+ if (flags & MOUNT_PCRFS)
+ log_warning("%s: measuring swap devices is currently unsupported.", what);
if (!(flags & MOUNT_NOAUTO)) {
r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET,
@@ -525,6 +529,19 @@ static int add_mount(
return r;
}
+ if (flags & MOUNT_PCRFS) {
+ r = efi_stub_measured();
+ if (r < 0)
+ log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled, assuming not: %m");
+ else if (r == 0)
+ log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too.");
+ else {
+ r = generator_hook_up_pcrfs(dest, where, target_unit);
+ if (r < 0)
+ return r;
+ }
+ }
+
if (!FLAGS_SET(flags, MOUNT_AUTOMOUNT)) {
if (!FLAGS_SET(flags, MOUNT_NOAUTO) && strv_isempty(wanted_by) && strv_isempty(required_by)) {
r = generator_add_symlink(dest, target_unit,
@@ -658,7 +675,7 @@ static int parse_fstab(bool initrd) {
while ((me = getmntent(f))) {
_cleanup_free_ char *where = NULL, *what = NULL, *canonical_where = NULL;
- bool makefs, growfs, noauto, nofail;
+ bool makefs, growfs, pcrfs, noauto, nofail;
MountPointFlags flags;
int k;
@@ -718,16 +735,18 @@ static int parse_fstab(bool initrd) {
makefs = fstab_test_option(me->mnt_opts, "x-systemd.makefs\0");
growfs = fstab_test_option(me->mnt_opts, "x-systemd.growfs\0");
+ pcrfs = fstab_test_option(me->mnt_opts, "x-systemd.pcrfs\0");
noauto = fstab_test_yes_no_option(me->mnt_opts, "noauto\0" "auto\0");
nofail = fstab_test_yes_no_option(me->mnt_opts, "nofail\0" "fail\0");
- log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s noauto=%s nofail=%s",
+ log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s",
what, where, me->mnt_type,
- yes_no(makefs), yes_no(growfs),
+ yes_no(makefs), yes_no(growfs), yes_no(pcrfs),
yes_no(noauto), yes_no(nofail));
flags = makefs * MOUNT_MAKEFS |
growfs * MOUNT_GROWFS |
+ pcrfs * MOUNT_PCRFS |
noauto * MOUNT_NOAUTO |
nofail * MOUNT_NOFAIL;
@@ -911,7 +930,7 @@ static int add_sysroot_mount(void) {
fstype,
opts,
is_device_path(what) ? 1 : 0, /* passno */
- flags, /* makefs, growfs off, noauto off, nofail off, automount off */
+ flags, /* makefs off, pcrfs off, noauto off, nofail off, automount off */
SPECIAL_INITRD_ROOT_FS_TARGET);
}
diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util-fundamental.h
index 67621fdb42..78e2dbec59 100644
--- a/src/fundamental/memory-util-fundamental.h
+++ b/src/fundamental/memory-util-fundamental.h
@@ -29,6 +29,8 @@ static inline void *explicit_bzero_safe(void *p, size_t l) {
#endif
struct VarEraser {
+ /* NB: This is a pointer to memory to erase in case of CLEANUP_ERASE(). Pointer to pointer to memory
+ * to erase in case of CLEANUP_ERASE_PTR() */
void *p;
size_t size;
};
@@ -38,5 +40,27 @@ static inline void erase_var(struct VarEraser *e) {
}
/* Mark var to be erased when leaving scope. */
-#define CLEANUP_ERASE(var) \
- _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { .p = &var, .size = sizeof(var) }
+#define CLEANUP_ERASE(var) \
+ _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+ .p = &(var), \
+ .size = sizeof(var), \
+ }
+
+static inline void erase_varp(struct VarEraser *e) {
+
+ /* Very similar to erase_var(), but assumes `p` is a pointer to a pointer whose memory shall be destructed. */
+ if (!e->p)
+ return;
+
+ explicit_bzero_safe(*(void**) e->p, e->size);
+}
+
+/* Mark pointer so that memory pointed to is erased when leaving scope. Note: this takes a pointer to the
+ * specified pointer, instead of just a copy of it. This is to allow callers to invalidate the pointer after
+ * use, if they like, disabling our automatic erasure (for example because they succeeded with whatever they
+ * wanted to do and now intend to return the allocated buffer to their caller without it being erased). */
+#define CLEANUP_ERASE_PTR(ptr, sz) \
+ _cleanup_(erase_varp) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+ .p = (ptr), \
+ .size = (sz), \
+ }
diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h
index d57291328d..e12b4ff607 100644
--- a/src/fundamental/tpm-pcr.h
+++ b/src/fundamental/tpm-pcr.h
@@ -17,6 +17,9 @@
/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */
#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U
+/* This TPM PCR is where we measure the root fs volume key (and maybe /var/'s) if it is split off */
+#define TPM_PCR_INDEX_VOLUME_KEY 15U
+
/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in
* which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */
typedef enum UnifiedSection {
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 98c0ca0810..9b11318017 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -52,10 +52,11 @@ static int add_cryptsetup(
const char *what,
bool rw,
bool require,
+ bool measure,
char **ret_device) {
#if HAVE_LIBCRYPTSETUP
- _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL;
+ _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL, *options = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
@@ -89,7 +90,28 @@ static int add_cryptsetup(
"After=%s\n",
d, d);
- r = generator_write_cryptsetup_service_section(f, id, what, NULL, rw ? NULL : "read-only");
+ if (!rw) {
+ options = strdup("read-only");
+ if (!options)
+ return log_oom();
+ }
+
+ if (measure) {
+ /* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a
+ * UKI), and sd-stub measured the UKI. We do this in order not to step into people's own PCR
+ * 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. */
+
+ r = efi_stub_measured();
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine whether booted via systemd-stub with measurements enabled, ignoring: %m");
+ else if (r == 0)
+ log_debug("Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id);
+ else if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes"))
+ return log_oom();
+ }
+
+ r = generator_write_cryptsetup_service_section(f, id, what, NULL, options);
if (r < 0)
return r;
@@ -144,6 +166,7 @@ static int add_mount(
const char *fstype,
bool rw,
bool growfs,
+ bool measure,
const char *options,
const char *description,
const char *post) {
@@ -164,7 +187,7 @@ static int add_mount(
log_debug("Adding %s: %s fstype=%s", where, what, fstype ?: "(any)");
if (streq_ptr(fstype, "crypto_LUKS")) {
- r = add_cryptsetup(id, what, rw, true, &crypto_what);
+ r = add_cryptsetup(id, what, rw, /* require= */ true, measure, &crypto_what);
if (r < 0)
return r;
@@ -236,6 +259,12 @@ static int add_mount(
return r;
}
+ if (measure) {
+ r = generator_hook_up_pcrfs(arg_dest, where, post);
+ if (r < 0)
+ return r;
+ }
+
if (post) {
r = generator_add_symlink(arg_dest, post, "requires", unit);
if (r < 0)
@@ -291,6 +320,7 @@ static int add_partition_mount(
p->fstype,
p->rw,
p->growfs,
+ /* measure= */ STR_IN_SET(id, "root", "var"), /* by default measure rootfs and /var, since they contain the "identity" of the system */
NULL,
description,
SPECIAL_LOCAL_FS_TARGET);
@@ -315,7 +345,7 @@ static int add_partition_swap(DissectedPartition *p) {
}
if (streq_ptr(p->fstype, "crypto_LUKS")) {
- r = add_cryptsetup("swap", p->node, true, true, &crypto_what);
+ r = add_cryptsetup("swap", p->node, /* rw= */ true, /* require= */ true, /* measure= */ false, &crypto_what);
if (r < 0)
return r;
what = crypto_what;
@@ -384,6 +414,7 @@ static int add_automount(
fstype,
rw,
growfs,
+ /* measure= */ false,
options,
description,
NULL);
@@ -592,7 +623,7 @@ static int add_root_cryptsetup(void) {
/* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which
* sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */
- return add_cryptsetup("root", "/dev/gpt-auto-root-luks", true, false, NULL);
+ return add_cryptsetup("root", "/dev/gpt-auto-root-luks", /* rw= */ true, /* require= */ false, /* measure= */ true, NULL);
#else
return 0;
#endif
@@ -639,6 +670,7 @@ static int add_root_mount(void) {
arg_root_fstype,
/* rw= */ arg_root_rw > 0,
/* growfs= */ false,
+ /* measure= */ true,
arg_root_options,
"Root Partition",
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c
index afe3447d62..8b7fdda5b1 100644
--- a/src/home/homework-fscrypt.c
+++ b/src/home/homework-fscrypt.c
@@ -58,10 +58,10 @@ static int fscrypt_upload_volume_key(
};
memcpy(key.raw, volume_key, volume_key_size);
+ CLEANUP_ERASE(key);
+
/* Upload to the kernel */
serial = add_key("logon", description, &key, sizeof(key), where);
- explicit_bzero_safe(&key, sizeof(key));
-
if (serial < 0)
return log_error_errno(errno, "Failed to install master key in keyring: %m");
@@ -124,20 +124,18 @@ static int fscrypt_slot_try_one(
* resulting hash.
*/
+ CLEANUP_ERASE(derived);
+
if (PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, salt_size,
0xFFFF, EVP_sha512(),
- sizeof(derived), derived) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
- goto finish;
- }
+ sizeof(derived), derived) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
context = EVP_CIPHER_CTX_new();
- if (!context) {
- r = log_oom();
- goto finish;
- }
+ if (!context)
+ return log_oom();
/* We use AES256 in counter mode */
assert_se(cc = EVP_aes_256_ctr());
@@ -145,13 +143,8 @@ static int fscrypt_slot_try_one(
/* We only use the first half of the derived key */
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
- if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
- goto finish;
- }
-
- /* Flush out the derived key now, we don't need it anymore */
- explicit_bzero_safe(derived, sizeof(derived));
+ if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2;
decrypted = malloc(decrypted_size);
@@ -184,10 +177,6 @@ static int fscrypt_slot_try_one(
*ret_decrypted_size = decrypted_size;
return 0;
-
-finish:
- explicit_bzero_safe(derived, sizeof(derived));
- return r;
}
static int fscrypt_slot_try_many(
@@ -413,20 +402,18 @@ static int fscrypt_slot_set(
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
+ CLEANUP_ERASE(derived);
+
if (PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, sizeof(salt),
0xFFFF, EVP_sha512(),
- sizeof(derived), derived) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
- goto finish;
- }
+ sizeof(derived), derived) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
context = EVP_CIPHER_CTX_new();
- if (!context) {
- r = log_oom();
- goto finish;
- }
+ if (!context)
+ return log_oom();
/* We use AES256 in counter mode */
cc = EVP_aes_256_ctr();
@@ -434,13 +421,8 @@ static int fscrypt_slot_set(
/* We only use the first half of the derived key */
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
- if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
- goto finish;
- }
-
- /* Flush out the derived key now, we don't need it anymore */
- explicit_bzero_safe(derived, sizeof(derived));
+ if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2;
encrypted = malloc(encrypted_size);
@@ -477,10 +459,6 @@ static int fscrypt_slot_set(
log_info("Written key slot %s.", label);
return 0;
-
-finish:
- explicit_bzero_safe(derived, sizeof(derived));
- return r;
}
int home_create_fscrypt(
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 2afcda3eec..fa43f28eb5 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -79,6 +79,7 @@ struct sd_dhcp6_client {
sd_dhcp6_client_callback_t callback;
void *userdata;
+ bool send_release;
/* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
bool test_mode;
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index 62873e0111..a6b74e07b2 100644
--- a/src/libsystemd-network/dhcp6-option.c
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -495,13 +495,18 @@ int dhcp6_option_parse(
}
int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+ DHCP6Status status;
+
assert(data || data_len == 0);
if (data_len < sizeof(uint16_t))
return -EBADMSG;
+ status = unaligned_read_be16(data);
+
if (ret_status_message) {
- char *msg;
+ _cleanup_free_ char *msg = NULL;
+ const char *s;
/* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
* Let's escape unsafe characters for safety. */
@@ -509,10 +514,14 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s
if (!msg)
return -ENOMEM;
- *ret_status_message = msg;
+ s = dhcp6_message_status_to_string(status);
+ if (s && !strextend_with_separator(&msg, ": ", s))
+ return -ENOMEM;
+
+ *ret_status_message = TAKE_PTR(msg);
}
- return unaligned_read_be16(data);
+ return status;
}
static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
@@ -538,9 +547,8 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Received an IA address or PD prefix option with non-zero status: %s%s%s",
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ "Received an IA address or PD prefix option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
if (r < 0)
/* Let's log but ignore the invalid status option. */
log_dhcp6_client_errno(client, r,
@@ -746,9 +754,8 @@ int dhcp6_option_parse_ia(
return r;
if (r > 0)
return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
- "Received an IA option with non-zero status: %s%s%s",
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ "Received an IA option with non-zero status%s%s",
+ isempty(msg) ? "." : ": ", strempty(msg));
if (r < 0)
log_dhcp6_client_errno(client, r,
"Received an IA option with an invalid status sub option, ignoring: %m");
diff --git a/src/libsystemd-network/dhcp6-protocol.c b/src/libsystemd-network/dhcp6-protocol.c
index f965ea40fe..be0f651f1a 100644
--- a/src/libsystemd-network/dhcp6-protocol.c
+++ b/src/libsystemd-network/dhcp6-protocol.c
@@ -11,6 +11,7 @@ static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
[DHCP6_STATE_BOUND] = "bound",
[DHCP6_STATE_RENEW] = "renew",
[DHCP6_STATE_REBIND] = "rebind",
+ [DHCP6_STATE_STOPPING] = "stopping",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index 18217691b7..c70f93203d 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -58,6 +58,7 @@ typedef enum DHCP6State {
DHCP6_STATE_BOUND,
DHCP6_STATE_RENEW,
DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING,
_DHCP6_STATE_MAX,
_DHCP6_STATE_INVALID = -EINVAL,
} DHCP6State;
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index f5abb1bf86..e482450cd4 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -195,35 +195,33 @@ int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
r = asprintf(&t, "DATA");
break;
case 1:
- if (len != sizeof_field(sd_dhcp_client_id, eth))
- return -EINVAL;
-
- r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
- client_id->eth.haddr[0],
- client_id->eth.haddr[1],
- client_id->eth.haddr[2],
- client_id->eth.haddr[3],
- client_id->eth.haddr[4],
- client_id->eth.haddr[5]);
+ if (len == sizeof_field(sd_dhcp_client_id, eth))
+ r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x",
+ client_id->eth.haddr[0],
+ client_id->eth.haddr[1],
+ client_id->eth.haddr[2],
+ client_id->eth.haddr[3],
+ client_id->eth.haddr[4],
+ client_id->eth.haddr[5]);
+ else
+ r = asprintf(&t, "ETHER");
break;
case 2 ... 254:
r = asprintf(&t, "ARP/LL");
break;
case 255:
- if (len < 6)
- return -EINVAL;
-
- uint32_t iaid = be32toh(client_id->ns.iaid);
- uint16_t duid_type = be16toh(client_id->ns.duid.type);
- if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0)
- return -EINVAL;
-
- r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ if (len < sizeof(uint32_t))
+ r = asprintf(&t, "IAID/DUID");
+ else {
+ uint32_t iaid = be32toh(client_id->ns.iaid);
+ /* TODO: check and stringify DUID */
+ r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+ }
break;
}
-
if (r < 0)
return -ENOMEM;
+
*ret = TAKE_PTR(t);
return 0;
}
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 29cd003506..40edfd3908 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -498,6 +498,14 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
return 0;
}
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->send_release = enable;
+ return 0;
+}
+
int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
assert_return(client, -EINVAL);
@@ -586,7 +594,8 @@ static int client_append_common_options_in_managed_mode(
DHCP6_STATE_SOLICITATION,
DHCP6_STATE_REQUEST,
DHCP6_STATE_RENEW,
- DHCP6_STATE_REBIND));
+ DHCP6_STATE_REBIND,
+ DHCP6_STATE_STOPPING));
assert(buf);
assert(*buf);
assert(offset);
@@ -603,9 +612,11 @@ static int client_append_common_options_in_managed_mode(
return r;
}
- r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
- if (r < 0)
- return r;
+ if (client->state != DHCP6_STATE_STOPPING) {
+ r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
+ if (r < 0)
+ return r;
+ }
r = dhcp6_option_append_user_class(buf, offset, client->user_class);
if (r < 0)
@@ -636,6 +647,8 @@ static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client)
return DHCP6_MESSAGE_RENEW;
case DHCP6_STATE_REBIND:
return DHCP6_MESSAGE_REBIND;
+ case DHCP6_STATE_STOPPING:
+ return DHCP6_MESSAGE_RELEASE;
default:
assert_not_reached();
}
@@ -679,6 +692,9 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
req_opts = p;
break;
+ case DHCP6_STATE_STOPPING:
+ return 0;
+
default:
n = client->n_req_opts;
req_opts = client->req_opts;
@@ -690,6 +706,22 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
}
+static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+ assert(client);
+ assert(buf);
+ assert(*buf);
+ assert(offset);
+
+ if (!client->mudurl)
+ return 0;
+
+ if (client->state == DHCP6_STATE_STOPPING)
+ return 0;
+
+ return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
+ strlen(client->mudurl), client->mudurl);
+}
+
int dhcp6_client_send_message(sd_dhcp6_client *client) {
_cleanup_free_ uint8_t *buf = NULL;
struct in6_addr all_servers =
@@ -735,7 +767,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
case DHCP6_STATE_REQUEST:
case DHCP6_STATE_RENEW:
-
+ case DHCP6_STATE_STOPPING:
r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
client->lease->serverid_len,
client->lease->serverid);
@@ -753,18 +785,15 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
return r;
break;
- case DHCP6_STATE_STOPPED:
case DHCP6_STATE_BOUND:
+ case DHCP6_STATE_STOPPED:
default:
assert_not_reached();
}
- if (client->mudurl) {
- r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_MUD_URL_V6,
- strlen(client->mudurl), client->mudurl);
- if (r < 0)
- return r;
- }
+ r = client_append_mudurl(client, &buf, &offset);
+ if (r < 0)
+ return r;
r = client_append_oro(client, &buf, &offset);
if (r < 0)
@@ -856,6 +885,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
break;
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
case DHCP6_STATE_BOUND:
default:
assert_not_reached();
@@ -911,6 +941,7 @@ static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
break;
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
case DHCP6_STATE_BOUND:
default:
assert_not_reached();
@@ -1319,6 +1350,7 @@ static int client_receive_message(
case DHCP6_STATE_BOUND:
case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_STOPPING:
default:
assert_not_reached();
}
@@ -1326,10 +1358,37 @@ static int client_receive_message(
return 0;
}
+static int client_send_release(sd_dhcp6_client *client) {
+ sd_dhcp6_lease *lease;
+
+ assert(client);
+
+ if (!client->send_release)
+ return 0;
+
+ if (sd_dhcp6_client_get_lease(client, &lease) < 0)
+ return 0;
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return 0;
+
+ client_set_state(client, DHCP6_STATE_STOPPING);
+ return dhcp6_client_send_message(client);
+}
+
int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+ int r;
+
if (!client)
return 0;
+ /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
+ engine is about to release its UDP socket inconditionally. */
+ r = client_send_release(client);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r,
+ "Failed to send DHCP6 release message, ignoring: %m");
+
client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
client->receive_message = sd_event_source_unref(client->receive_message);
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
index 3dcccf1729..d14c412c1f 100644
--- a/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -510,13 +510,11 @@ static int dhcp6_lease_parse_message(
r = dhcp6_option_parse_status(optval, optlen, &msg);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to parse status code: %m");
-
if (r > 0)
return log_dhcp6_client_errno(client, dhcp6_message_status_to_errno(r),
- "Received %s message with non-zero status: %s%s%s",
+ "Received %s message with non-zero status%s%s",
dhcp6_message_type_to_string(message->type),
- strempty(msg), isempty(msg) ? "" : ": ",
- dhcp6_message_status_to_string(r));
+ isempty(msg) ? "." : ": ", strempty(msg));
break;
}
case SD_DHCP6_OPTION_IA_NA: {
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index 3e1c52a306..00c11909c9 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -602,6 +602,62 @@ static const uint8_t msg_request[] = {
0x00, 0x00,
};
+/* RFC 3315 section 18.1.6. The DHCP6 Release message must include:
+ - transaction id
+ - server identifier
+ - client identifier
+ - all released IA with addresses included
+ - elapsed time (required for all messages).
+ All other options aren't required. */
+static const uint8_t msg_release[] = {
+ /* Message type */
+ DHCP6_MESSAGE_RELEASE,
+ /* Transaction ID */
+ 0x00, 0x00, 0x00,
+ /* Server ID */
+ 0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+ SERVER_ID_BYTES,
+ /* IA_NA */
+ 0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS1_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_NA (IAADDR suboption) */
+ 0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+ IA_NA_ADDRESS2_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ /* IA_PD */
+ 0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+ IA_ID_BYTES,
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+ 0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX1_BYTES,
+ /* IA_PD (IA_PD_PREFIX suboption) */
+ 0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+ 0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+ 0x40, /* prefixlen */
+ IA_PD_PREFIX2_BYTES,
+ /* Client ID */
+ 0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+ CLIENT_ID_BYTES,
+ /* Extra options */
+ /* Elapsed time */
+ 0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+ 0x00, 0x00,
+};
+
static const uint8_t msg_reply[] = {
/* Message type */
DHCP6_MESSAGE_REPLY,
@@ -775,13 +831,24 @@ static void test_client_verify_solicit(const DHCP6Message *msg, size_t len) {
assert_se(memcmp(msg, msg_solicit, len - sizeof(be16_t)) == 0);
}
+static void test_client_verify_release(const DHCP6Message *msg, size_t len) {
+ log_debug("/* %s */", __func__);
+
+ assert_se(len == sizeof(msg_release));
+ assert_se(msg->type == DHCP6_MESSAGE_RELEASE);
+ /* The transaction ID and elapsed time value are not deterministic. Skip them. */
+ assert_se(memcmp(msg->options, msg_release + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+}
+
static void test_client_verify_request(const DHCP6Message *msg, size_t len) {
log_debug("/* %s */", __func__);
assert_se(len == sizeof(msg_request));
assert_se(msg->type == DHCP6_MESSAGE_REQUEST);
/* The transaction ID and elapsed time value are not deterministic. Skip them. */
- assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options), len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+ assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options),
+ len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
}
static void test_lease_common(sd_dhcp6_client *client) {
@@ -905,7 +972,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd
case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
log_debug("/* %s (event=ip-acquire) */", __func__);
- assert_se(IN_SET(test_client_sent_message_count, 3, 4));
+ assert_se(IN_SET(test_client_sent_message_count, 3, 5));
test_lease_managed(client);
@@ -916,7 +983,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd
assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
break;
- case 4:
+ case 5:
assert_se(sd_event_exit(sd_dhcp6_client_get_event(client), 0) >= 0);
break;
@@ -974,6 +1041,12 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet,
break;
case 3:
+ test_client_verify_release(packet, len);
+ /* when stopping, dhcp6 client doesn't wait for release server reply */
+ assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+ break;
+
+ case 4:
test_client_verify_solicit(packet, len);
assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
break;
@@ -1010,6 +1083,7 @@ TEST(dhcp6_client) {
assert_se(sd_dhcp6_client_set_local_address(client, &local_address) >= 0);
assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") >= 0);
assert_se(sd_dhcp6_client_set_iaid(client, unaligned_read_be32((uint8_t[]) { IA_ID_BYTES })) >= 0);
+ assert_se(sd_dhcp6_client_set_send_release(client, true) >= 0);
dhcp6_client_set_test_mode(client, true);
assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0);
@@ -1028,7 +1102,7 @@ TEST(dhcp6_client) {
assert_se(sd_event_loop(e) >= 0);
- assert_se(test_client_sent_message_count == 4);
+ assert_se(test_client_sent_message_count == 5);
assert_se(!sd_dhcp6_client_unref(client_ref));
test_fd[1] = safe_close(test_fd[1]);
diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h
index d9a519a4d9..740c58438c 100644
--- a/src/libsystemd/sd-device/device-private.h
+++ b/src/libsystemd/sd-device/device-private.h
@@ -38,6 +38,7 @@ void device_set_db_persist(sd_device *device);
void device_set_devlink_priority(sd_device *device, int priority);
int device_ensure_usec_initialized(sd_device *device, sd_device *device_old);
int device_add_devlink(sd_device *device, const char *devlink);
+void device_remove_devlink(sd_device *device, const char *devlink);
bool device_has_devlink(sd_device *device, const char *devlink);
int device_add_property(sd_device *device, const char *property, const char *value);
int device_add_propertyf(sd_device *device, const char *key, const char *format, ...) _printf_(3, 4);
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
index c3160b04bb..8c65ee3469 100644
--- a/src/libsystemd/sd-device/sd-device.c
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -1489,6 +1489,20 @@ int device_add_devlink(sd_device *device, const char *devlink) {
return 0;
}
+void device_remove_devlink(sd_device *device, const char *devlink) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(device);
+ assert(devlink);
+
+ s = set_remove(device->devlinks, devlink);
+ if (!s)
+ return;
+
+ device->devlinks_generation++;
+ device->property_devlinks_outdated = true;
+}
+
bool device_has_devlink(sd_device *device, const char *devlink) {
assert(device);
assert(devlink);
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 5b3b7d128a..c691a5e057 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -1216,9 +1216,13 @@ int link_request_address(
(void) address_get(link, address, &existing);
- if (address->lifetime_valid_usec == 0)
+ if (address->lifetime_valid_usec == 0) {
+ if (consume_object)
+ address_free(address);
+
/* The requested address is outdated. Let's remove it. */
return address_remove_and_drop(existing);
+ }
if (!existing) {
_cleanup_(address_freep) Address *tmp = NULL;
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 43850e1e95..1d5e2975a8 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -183,6 +183,7 @@ static int dhcp4_request_route(Route *in, Link *link) {
assert(route);
assert(link);
+ assert(link->network);
assert(link->dhcp_lease);
r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &server);
@@ -200,6 +201,8 @@ static int dhcp4_request_route(Route *in, Link *link) {
route->table = link_get_dhcp4_route_table(link);
if (route->mtu == 0)
route->mtu = link->network->dhcp_route_mtu;
+ if (route->quickack < 0)
+ route->quickack = link->network->dhcp_quickack;
if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */
link->dhcp4_configured = false;
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index c44c37f3aa..43be988377 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -707,6 +707,12 @@ static int dhcp6_configure(Link *link) {
"DHCPv6 CLIENT: Failed to %s rapid commit: %m",
enable_disable(link->network->dhcp6_use_rapid_commit));
+ r = sd_dhcp6_client_set_send_release(client, link->network->dhcp6_send_release);
+ if (r < 0)
+ return log_link_debug_errno(link, r,
+ "DHCPv6 CLIENT: Failed to %s sending release message on stop: %m",
+ enable_disable(link->network->dhcp6_send_release));
+
link->dhcp6_client = TAKE_PTR(client);
return 0;
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index c7ed5fcfe1..ed9bb18599 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -173,6 +173,7 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
assert(route);
assert(link);
+ assert(link->network);
assert(rt);
r = sd_ndisc_router_get_address(rt, &router);
@@ -186,6 +187,8 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) {
ndisc_set_route_priority(link, route);
if (!route->protocol_set)
route->protocol = RTPROT_RA;
+ if (route->quickack < 0)
+ route->quickack = link->network->ipv6_accept_ra_quickack;
is_new = route_get(NULL, link, route, NULL) < 0;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index c205e56c62..716904cc34 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -221,6 +221,7 @@ DHCPv4.UseHostname, config_parse_bool,
DHCPv4.UseDomains, config_parse_dhcp_use_domains, AF_INET, 0
DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
DHCPv4.UseGateway, config_parse_tristate, 0, offsetof(Network, dhcp_use_gateway)
+DHCPv4.QuickAck, config_parse_bool, 0, offsetof(Network, dhcp_quickack)
DHCPv4.RequestOptions, config_parse_dhcp_request_options, AF_INET, 0
DHCPv4.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
DHCPv4.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
@@ -269,6 +270,7 @@ DHCPv6.DUIDType, config_parse_duid_type,
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
+DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@@ -279,6 +281,7 @@ IPv6AcceptRA.UseMTU, config_parse_bool,
IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0
IPv6AcceptRA.RouteMetric, config_parse_ipv6_accept_ra_route_metric, 0, 0
+IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_quickack)
IPv6AcceptRA.RouterAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_router)
IPv6AcceptRA.RouterDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_router)
IPv6AcceptRA.PrefixAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_prefix)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index d881889316..7c5d691afa 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -416,6 +416,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp6_use_rapid_commit = true,
.dhcp6_duid.type = _DUID_TYPE_INVALID,
.dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID,
+ .dhcp6_send_release = true,
.dhcp_pd = -1,
.dhcp_pd_announce = true,
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index fbeec6072e..7685c98f65 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -146,6 +146,7 @@ struct Network {
bool dhcp_use_mtu;
bool dhcp_use_routes;
int dhcp_use_gateway;
+ bool dhcp_quickack;
bool dhcp_use_timezone;
bool dhcp_use_hostname;
bool dhcp_use_6rd;
@@ -185,6 +186,7 @@ struct Network {
OrderedHashmap *dhcp6_client_send_vendor_options;
Set *dhcp6_request_options;
char *dhcp6_netlabel;
+ bool dhcp6_send_release;
/* DHCP Server Support */
bool dhcp_server;
@@ -312,6 +314,7 @@ struct Network {
bool ipv6_accept_ra_use_autonomous_prefix;
bool ipv6_accept_ra_use_onlink_prefix;
bool ipv6_accept_ra_use_mtu;
+ bool ipv6_accept_ra_quickack;
bool active_slave;
bool primary_slave;
DHCPUseDomains ipv6_accept_ra_use_domains;
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index d1f3bab092..5214a8ad2c 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -1437,9 +1437,13 @@ int link_request_route(
(void) route_get(link->manager, link, route, &existing);
- if (route->lifetime_usec == 0)
+ if (route->lifetime_usec == 0) {
+ if (consume_object)
+ route_free(route);
+
/* The requested route is outdated. Let's remove it. */
return route_remove_and_drop(existing);
+ }
if (!existing) {
_cleanup_(route_freep) Route *tmp = NULL;
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c
index dc3d70bf1f..fe06f41814 100644
--- a/src/shared/ask-password-api.c
+++ b/src/shared/ask-password-api.c
@@ -258,6 +258,8 @@ int ask_password_plymouth(
if (r < 0)
return r;
+ CLEANUP_ERASE(buffer);
+
pollfd[POLL_SOCKET].fd = fd;
pollfd[POLL_SOCKET].events = POLLIN;
pollfd[POLL_INOTIFY].fd = notify;
@@ -271,20 +273,16 @@ int ask_password_plymouth(
else
timeout = USEC_INFINITY;
- if (flag_file && access(flag_file, F_OK) < 0) {
- r = -errno;
- goto finish;
- }
+ if (flag_file && access(flag_file, F_OK) < 0)
+ return -errno;
r = ppoll_usec(pollfd, notify >= 0 ? 2 : 1, timeout);
if (r == -EINTR)
continue;
if (r < 0)
- goto finish;
- if (r == 0) {
- r = -ETIME;
- goto finish;
- }
+ return r;
+ if (r == 0)
+ return -ETIME;
if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
(void) flush_fd(notify);
@@ -297,13 +295,10 @@ int ask_password_plymouth(
if (ERRNO_IS_TRANSIENT(errno))
continue;
- r = -errno;
- goto finish;
- }
- if (k == 0) {
- r = -EIO;
- goto finish;
+ return -errno;
}
+ if (k == 0)
+ return -EIO;
p += k;
@@ -315,14 +310,12 @@ int ask_password_plymouth(
* with a normal password request */
packet = mfree(packet);
- if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
- r = -ENOMEM;
- goto finish;
- }
+ if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
+ return -ENOMEM;
r = loop_write(fd, packet, n+1, true);
if (r < 0)
- goto finish;
+ return r;
flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
p = 0;
@@ -330,8 +323,7 @@ int ask_password_plymouth(
}
/* No password, because UI not shown */
- r = -ENOENT;
- goto finish;
+ return -ENOENT;
} else if (IN_SET(buffer[0], 2, 9)) {
uint32_t size;
@@ -343,35 +335,25 @@ int ask_password_plymouth(
memcpy(&size, buffer+1, sizeof(size));
size = le32toh(size);
- if (size + 5 > sizeof(buffer)) {
- r = -EIO;
- goto finish;
- }
+ if (size + 5 > sizeof(buffer))
+ return -EIO;
if (p-5 < size)
continue;
l = strv_parse_nulstr(buffer + 5, size);
- if (!l) {
- r = -ENOMEM;
- goto finish;
- }
+ if (!l)
+ return -ENOMEM;
*ret = l;
break;
- } else {
+ } else
/* Unknown packet */
- r = -EIO;
- goto finish;
- }
+ return -EIO;
}
- r = 0;
-
-finish:
- explicit_bzero_safe(buffer, sizeof(buffer));
- return r;
+ return 0;
}
#define NO_ECHO "(no echo) "
@@ -433,6 +415,8 @@ int ask_password_tty(
return -errno;
}
+ CLEANUP_ERASE(passphrase);
+
/* If the caller didn't specify a TTY, then use the controlling tty, if we can. */
if (ttyfd < 0)
ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
@@ -636,7 +620,6 @@ int ask_password_tty(
}
x = strndup(passphrase, p);
- explicit_bzero_safe(passphrase, sizeof(passphrase));
if (!x) {
r = -ENOMEM;
goto finish;
@@ -896,6 +879,8 @@ int ask_password_agent(
goto finish;
}
+ CLEANUP_ERASE(passphrase);
+
cmsg_close_all(&msghdr);
if (n == 0) {
@@ -920,7 +905,6 @@ int ask_password_agent(
l = strv_new("");
else
l = strv_parse_nulstr(passphrase+1, n-1);
- explicit_bzero_safe(passphrase, n);
if (!l) {
r = -ENOMEM;
goto finish;
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index a68837b70b..b416e873af 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -9,6 +9,7 @@
#include "sd-id128.h"
#include "blockdev-util.h"
+#include "capability-util.h"
#include "chattr-util.h"
#include "constants.h"
#include "creds-util.h"
@@ -215,7 +216,6 @@ static int make_credential_host_secret(
void **ret_data,
size_t *ret_size) {
- struct credential_host_secret_format buf;
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
@@ -223,10 +223,15 @@ static int make_credential_host_secret(
assert(dfd >= 0);
assert(fn);
- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
+ /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
+ * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
+ * capability when it uses the AT_EMPTY_PATH flag. */
+ if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
+ fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
+ if (fd < 0)
+ log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
+ }
if (fd < 0) {
- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
-
if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
return -ENOMEM;
@@ -239,21 +244,23 @@ static int make_credential_host_secret(
if (r < 0)
log_debug_errno(r, "Failed to set file attributes for secrets file, ignoring: %m");
- buf = (struct credential_host_secret_format) {
+ struct credential_host_secret_format buf = {
.machine_id = machine_id,
};
+ CLEANUP_ERASE(buf);
+
r = crypto_random_bytes(buf.data, sizeof(buf.data));
if (r < 0)
- goto finish;
+ goto fail;
r = loop_write(fd, &buf, sizeof(buf), false);
if (r < 0)
- goto finish;
+ goto fail;
if (fsync(fd) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
warn_not_encrypted(fd, flags, dirname, fn);
@@ -261,17 +268,17 @@ static int make_credential_host_secret(
if (t) {
r = rename_noreplace(dfd, t, dfd, fn);
if (r < 0)
- goto finish;
+ goto fail;
t = mfree(t);
} else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
if (fsync(dfd) < 0) {
r = -errno;
- goto finish;
+ goto fail;
}
if (ret_data) {
@@ -280,7 +287,7 @@ static int make_credential_host_secret(
copy = memdup(buf.data, sizeof(buf.data));
if (!copy) {
r = -ENOMEM;
- goto finish;
+ goto fail;
}
*ret_data = copy;
@@ -289,13 +296,12 @@ static int make_credential_host_secret(
if (ret_size)
*ret_size = sizeof(buf.data);
- r = 0;
+ return 0;
-finish:
+fail:
if (t && unlinkat(dfd, t, 0) < 0)
log_debug_errno(errno, "Failed to remove temporary credential key: %m");
- explicit_bzero_safe(&buf, sizeof(buf));
return r;
}
@@ -652,24 +658,14 @@ int encrypt_credential_and_warn(
#if HAVE_TPM2
bool try_tpm2;
- if (sd_id128_equal(with_key, _CRED_AUTO)) {
- /* If automatic mode is selected and we are running in a container, let's not try TPM2. OTOH
- * if user picks TPM2 explicitly, let's always honour the request and try. */
-
- r = detect_container();
- if (r < 0)
- log_debug_errno(r, "Failed to determine whether we are running in a container, ignoring: %m");
- else if (r > 0)
- log_debug("Running in container, not attempting to use TPM2.");
-
- try_tpm2 = r <= 0;
- } else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) {
- /* If automatic mode for initrds is selected, we'll use the TPM2 key if the firmware does it,
- * otherwise we'll use a fixed key */
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a
+ * container tpm2_support will detect this, and will return a different flag combination of
+ * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
- try_tpm2 = efi_has_tpm2();
+ try_tpm2 = tpm2_support() == TPM2_SUPPORT_FULL;
if (!try_tpm2)
- log_debug("Firmware lacks TPM2 support, not attempting to use TPM2.");
+ log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2.");
} else
try_tpm2 = sd_id128_in_set(with_key,
CRED_AES256_GCM_BY_TPM2_HMAC,
@@ -710,7 +706,7 @@ int encrypt_credential_and_warn(
&tpm2_primary_alg);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
+ log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
else if (!sd_id128_equal(with_key, _CRED_AUTO))
return r;
diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c
index 1340412cda..621fa082ba 100644
--- a/src/shared/efi-loader.c
+++ b/src/shared/efi-loader.c
@@ -2,10 +2,12 @@
#include "alloc-util.h"
#include "efi-loader.h"
+#include "env-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "strv.h"
+#include "tpm-pcr.h"
#include "utf8.h"
#if ENABLE_EFI
@@ -236,6 +238,43 @@ int efi_stub_get_features(uint64_t *ret) {
return 0;
}
+int efi_stub_measured(void) {
+ _cleanup_free_ char *pcr_string = NULL;
+ unsigned pcr_nr;
+ int r;
+
+ /* Checks if we are booted on a kernel with sd-stub which measured the kernel into PCR 11. Or in
+ * other words, if we are running on a TPM enabled UKI.
+ *
+ * Returns == 0 and > 0 depending on the result of the test. Returns -EREMOTE if we detected a stub
+ * being used, but it measured things into a different PCR than we are configured for in
+ * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */
+
+ r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test,
+ * for debugging purposes */
+ if (r >= 0)
+ return r;
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_FORCE_MEASURE, ignoring: %m");
+
+ if (!is_efi_boot())
+ return 0;
+
+ r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = safe_atou(pcr_string, &pcr_nr);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
+ if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE)
+ return log_debug_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
+
+ return 1;
+}
+
int efi_loader_get_config_timeout_one_shot(usec_t *ret) {
_cleanup_free_ char *v = NULL;
static struct stat cache_stat = {};
diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h
index 84968869ab..56ccdee9c1 100644
--- a/src/shared/efi-loader.h
+++ b/src/shared/efi-loader.h
@@ -18,6 +18,8 @@ int efi_loader_get_entries(char ***ret);
int efi_loader_get_features(uint64_t *ret);
int efi_stub_get_features(uint64_t *ret);
+int efi_stub_measured(void);
+
int efi_loader_get_config_timeout_one_shot(usec_t *ret);
int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat);
diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c
index e2b2ca2352..e978b8bf7e 100644
--- a/src/shared/ethtool-util.c
+++ b/src/shared/ethtool-util.c
@@ -434,6 +434,8 @@ int ethtool_set_wol(
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
+ CLEANUP_ERASE(ecmd);
+
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
@@ -466,16 +468,11 @@ int ethtool_set_wol(
need_update = true;
}
- if (!need_update) {
- explicit_bzero_safe(&ecmd, sizeof(ecmd));
+ if (!need_update)
return 0;
- }
ecmd.cmd = ETHTOOL_SWOL;
- r = RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
-
- explicit_bzero_safe(&ecmd, sizeof(ecmd));
- return r;
+ return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
}
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) {
diff --git a/src/shared/generator.c b/src/shared/generator.c
index 6d95aa72f8..64f4a2741c 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -59,25 +59,111 @@ int generator_open_unit_file(
return 0;
}
-int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src) {
- _cleanup_free_ char *bn = NULL;
- const char *from, *to;
+
+int generator_add_symlink_full(
+ const char *dir,
+ const char *dst,
+ const char *dep_type,
+ const char *src,
+ const char *instance) {
+
+ _cleanup_free_ char *dn = NULL, *fn = NULL, *instantiated = NULL, *to = NULL, *from = NULL;
int r;
- /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute)
- * or ../<src> (otherwise). */
+ assert(dir);
+ assert(dst);
+ assert(dep_type);
+ assert(src);
- r = path_extract_filename(src, &bn);
+ /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
+ * <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
+
+ r = path_extract_directory(src, &dn);
+ if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
+ return log_error_errno(r, "Failed to extract directory name from '%s': %m", src);
+
+ r = path_extract_filename(src, &fn);
if (r < 0)
- return log_error_errno(r, "Failed to extract filename from '%s': %m", src);
+ return log_error_errno(r, "Failed to extract file name from '%s': %m", src);
+ if (r == O_DIRECTORY)
+ return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Expected path to regular file name, but got '%s', refusing.", src);
- from = path_is_absolute(src) ? src : strjoina("../", src);
- to = strjoina(dir, "/", dst, ".", dep_type, "/", bn);
+ if (instance) {
+ r = unit_name_replace_instance(fn, instance, &instantiated);
+ if (r < 0)
+ return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
+ }
+
+ from = path_join(dn ?: "..", fn);
+ if (!from)
+ return log_oom();
+
+ to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ if (!to)
+ return log_oom();
(void) mkdir_parents_label(to, 0755);
- if (symlink(from, to) < 0)
- if (errno != EEXIST)
- return log_error_errno(errno, "Failed to create symlink \"%s\": %m", to);
+
+ if (symlink(from, to) < 0 && errno != EEXIST)
+ return log_error_errno(errno, "Failed to create symlink \"%s\": %m", to);
+
+ return 0;
+}
+
+static int generator_add_ordering(
+ const char *dir,
+ const char *src,
+ const char *order,
+ const char *dst,
+ const char *instance) {
+
+ _cleanup_free_ char *instantiated = NULL, *p = NULL, *fn = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *to;
+ int r;
+
+ assert(dir);
+ assert(src);
+ assert(order);
+ assert(dst);
+
+ /* Adds in an explicit ordering dependency of type <order> from <src> to <dst>. If <instance> is
+ * specified, it is inserted into <dst>. */
+
+ if (instance) {
+ r = unit_name_replace_instance(dst, instance, &instantiated);
+ if (r < 0)
+ return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", dst, instance);
+
+ to = instantiated;
+ } else
+ to = dst;
+
+ fn = strjoin(src, ".d/50-order-", to, ".conf");
+ if (!fn)
+ return log_oom();
+
+ p = path_join(dir, fn);
+ if (!p)
+ return log_oom();
+
+ (void) mkdir_parents_label(p, 0755);
+
+ r = fopen_unlocked(p, "wxe", &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create '%s': %m", p);
+
+ fprintf(f,
+ "# Automatically generated by %s\n\n"
+ "[Unit]\n"
+ "%s=%s\n",
+ program_invocation_short_name,
+ order,
+ to);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write drop-in '%s': %m", p);
return 0;
}
@@ -532,66 +618,73 @@ int generator_hook_up_growfs(
const char *where,
const char *target) {
- _cleanup_free_ char *unit = NULL, *escaped = NULL, *where_unit = NULL, *unit_file = NULL;
- _cleanup_fclose_ FILE *f = NULL;
+ const char *growfs_unit, *growfs_unit_path;
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
int r;
assert(dir);
assert(where);
- escaped = cescape(where);
- if (!escaped)
- return log_oom();
-
- r = unit_name_from_path_instance("systemd-growfs", where, ".service", &unit);
- if (r < 0)
- return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m",
- where);
-
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
- return log_error_errno(r, "Failed to make unit name from path \"%s\": %m",
- where);
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
- unit_file = path_join(dir, unit);
- if (!unit_file)
- return log_oom();
+ if (empty_or_root(where)) {
+ growfs_unit = SPECIAL_GROWFS_ROOT_SERVICE;
+ growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_ROOT_SERVICE;
+ } else {
+ growfs_unit = SPECIAL_GROWFS_SERVICE;
+ growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_SERVICE;
- log_debug("Creating %s", unit_file);
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+ }
- f = fopen(unit_file, "wxe");
- if (!f)
- return log_error_errno(errno, "Failed to create unit file %s: %m",
- unit_file);
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", growfs_unit, instance);
+ if (r < 0)
+ return r;
+ }
- fprintf(f,
- "# Automatically generated by %s\n\n"
- "[Unit]\n"
- "Description=Grow File System on %%f\n"
- "Documentation=man:systemd-growfs@.service(8)\n"
- "DefaultDependencies=no\n"
- "BindsTo=%%i.mount\n"
- "Conflicts=shutdown.target\n"
- "After=systemd-repart.service %%i.mount\n"
- "Before=shutdown.target%s%s\n",
- program_invocation_short_name,
- target ? " " : "",
- strempty(target));
+ return generator_add_symlink_full(dir, where_unit, "wants", growfs_unit_path, instance);
+}
- if (empty_or_root(where)) /* Make sure the root fs is actually writable before we resize it */
- fprintf(f,
- "After=systemd-remount-fs.service\n");
+int generator_hook_up_pcrfs(
+ const char *dir,
+ const char *where,
+ const char *target) {
- fprintf(f,
- "\n"
- "[Service]\n"
- "Type=oneshot\n"
- "RemainAfterExit=yes\n"
- "ExecStart="SYSTEMD_GROWFS_PATH " %s\n"
- "TimeoutSec=0\n",
- escaped);
+ const char *pcrfs_unit, *pcrfs_unit_path;
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
+ int r;
+
+ assert(dir);
+ assert(where);
+
+ r = unit_name_from_path(where, ".mount", &where_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
+
+ if (empty_or_root(where)) {
+ pcrfs_unit = SPECIAL_PCRFS_ROOT_SERVICE;
+ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_ROOT_SERVICE;
+ } else {
+ pcrfs_unit = SPECIAL_PCRFS_SERVICE;
+ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_SERVICE;
+
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+ }
+
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", pcrfs_unit, instance);
+ if (r < 0)
+ return r;
+ }
- return generator_add_symlink(dir, where_unit, "wants", unit);
+ return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance);
}
int generator_enable_remount_fs_service(const char *dir) {
diff --git a/src/shared/generator.h b/src/shared/generator.h
index 1b4f36ac53..111900fd45 100644
--- a/src/shared/generator.h
+++ b/src/shared/generator.h
@@ -12,7 +12,11 @@ int generator_open_unit_file(
const char *name,
FILE **file);
-int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src);
+int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
+
+static inline int generator_add_symlink(const char *dir, const char *dst, const char *dep_type, const char *src) {
+ return generator_add_symlink_full(dir, dst, dep_type, src, NULL);
+}
int generator_write_fsck_deps(
FILE *f,
@@ -77,6 +81,10 @@ int generator_hook_up_growfs(
const char *dir,
const char *where,
const char *target);
+int generator_hook_up_pcrfs(
+ const char *dir,
+ const char *where,
+ const char *target);
int generator_enable_remount_fs_service(const char *dir);
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index ba8dfb041d..af62f03b0f 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -730,18 +730,62 @@ int tpm2_get_good_pcr_banks(
return 0;
}
+int tpm2_get_good_pcr_banks_strv(
+ ESYS_CONTEXT *c,
+ uint32_t pcr_mask,
+ char ***ret) {
+
+ _cleanup_free_ TPMI_ALG_HASH *algs = NULL;
+ _cleanup_strv_free_ char **l = NULL;
+ int n_algs;
+
+ assert(c);
+ assert(ret);
+
+ n_algs = tpm2_get_good_pcr_banks(c, pcr_mask, &algs);
+ if (n_algs < 0)
+ return n_algs;
+
+ for (int i = 0; i < n_algs; i++) {
+ _cleanup_free_ char *n = NULL;
+ const EVP_MD *implementation;
+ const char *salg;
+
+ salg = tpm2_pcr_bank_to_string(algs[i]);
+ if (!salg)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
+
+ implementation = EVP_get_digestbyname(salg);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
+
+ n = strdup(ASSERT_PTR(EVP_MD_name(implementation)));
+ if (!n)
+ return log_oom();
+
+ ascii_strlower(n); /* OpenSSL uses uppercase digest names, we prefer them lower case. */
+
+ if (strv_consume(&l, TAKE_PTR(n)) < 0)
+ return log_oom();
+ }
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) {
struct sha256_ctx hash;
assert(auth);
assert(pin);
+
auth->size = SHA256_DIGEST_SIZE;
+ CLEANUP_ERASE(hash);
+
sha256_init_ctx(&hash);
sha256_process_bytes(pin, len, &hash);
sha256_finish_ctx(&hash, auth->buffer);
-
- explicit_bzero_safe(&hash, sizeof(hash));
}
static int tpm2_make_encryption_session(
@@ -773,11 +817,11 @@ static int tpm2_make_encryption_session(
if (pin) {
TPM2B_AUTH auth = {};
+ CLEANUP_ERASE(auth);
+
hash_pin(pin, strlen(pin), &auth);
rc = sym_Esys_TR_SetAuth(c, bind_key, &auth);
- /* ESAPI knows about it, so clear it from our memory */
- explicit_bzero_safe(&auth, sizeof(auth));
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
@@ -1369,8 +1413,8 @@ int tpm2_seal(const char *device,
static const TPML_PCR_SELECTION creation_pcr = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
- TPM2B_SENSITIVE_CREATE hmac_sensitive;
ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE;
+ TPM2B_SENSITIVE_CREATE hmac_sensitive;
TPMI_ALG_PUBLIC primary_alg;
TPM2B_PUBLIC hmac_template;
TPMI_ALG_HASH pcr_bank;
@@ -1410,6 +1454,8 @@ int tpm2_seal(const char *device,
start = now(CLOCK_MONOTONIC);
+ CLEANUP_ERASE(hmac_sensitive);
+
r = tpm2_context_init(device, &c);
if (r < 0)
return r;
@@ -1450,7 +1496,7 @@ int tpm2_seal(const char *device,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
- .unique.keyedHash.size = 32,
+ .unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = *policy_digest,
},
};
@@ -1498,7 +1544,6 @@ int tpm2_seal(const char *device,
}
secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
- explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
if (!secret) {
r = log_oom();
goto finish;
@@ -1559,7 +1604,6 @@ int tpm2_seal(const char *device,
r = 0;
finish:
- explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive));
primary = tpm2_flush_context_verbose(c.esys_context, primary);
session = tpm2_flush_context_verbose(c.esys_context, session);
return r;
@@ -1875,6 +1919,90 @@ int tpm2_find_device_auto(
#endif
}
+#if HAVE_TPM2
+int tpm2_extend_bytes(
+ ESYS_CONTEXT *c,
+ char **banks,
+ unsigned pcr_index,
+ const void *data,
+ size_t data_size,
+ const void *secret,
+ size_t secret_size) {
+
+#if HAVE_OPENSSL
+ TPML_DIGEST_VALUES values = {};
+ TSS2_RC rc;
+
+ assert(c);
+ assert(data || data_size == 0);
+ assert(secret || secret_size == 0);
+
+ if (data_size == SIZE_MAX)
+ data_size = strlen(data);
+ if (secret_size == SIZE_MAX)
+ secret_size = strlen(secret);
+
+ if (pcr_index >= TPM2_PCRS_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index);
+
+ if (strv_isempty(banks))
+ return 0;
+
+ STRV_FOREACH(bank, banks) {
+ const EVP_MD *implementation;
+ int id;
+
+ assert_se(implementation = EVP_get_digestbyname(*bank));
+
+ if (values.count >= ELEMENTSOF(values.digests))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
+
+ if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
+
+ id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
+ if (id < 0)
+ return log_error_errno(id, "Can't map hash name to TPM2.");
+
+ values.digests[values.count].hashAlg = id;
+
+ /* So here's a twist: sometimes we want to measure secrets (e.g. root file system volume
+ * key), but we'd rather not leak a literal hash of the secret to the TPM (given that the
+ * wire is unprotected, and some other subsystem might use the simple, literal hash of the
+ * secret for other purposes, maybe because it needs a shorter secret derived from it for
+ * some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a
+ * private non-secret string instead. */
+ if (secret_size > 0) {
+ if (!HMAC(implementation, secret, secret_size, data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure.");
+ } else if (EVP_Digest(data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure.");
+
+ values.count++;
+ }
+
+ rc = sym_Esys_PCR_Extend(
+ c,
+ ESYS_TR_PCR0 + pcr_index,
+ ESYS_TR_PASSWORD,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &values);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(
+ SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to measure into PCR %u: %s",
+ pcr_index,
+ sym_Tss2_RC_Decode(rc));
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "OpenSSL not supported on this build.");
+#endif
+}
+#endif
+
int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
const char *p = ASSERT_PTR(s);
uint32_t mask = 0;
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index c240335ae6..96e6c31b0a 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -68,6 +68,9 @@ static inline void Esys_Freep(void *p) {
}
int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks);
+int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret);
+
+int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size);
#else
struct tpm2_context;
diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c
index 86d1c5b7c2..6878954268 100644
--- a/src/systemctl/systemctl-list-dependencies.c
+++ b/src/systemctl/systemctl-list-dependencies.c
@@ -80,24 +80,44 @@ static int list_dependencies_one(
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
STRV_FOREACH(c, deps) {
+ _cleanup_free_ char *load_state = NULL, *sub_state = NULL;
+ UnitActiveState active_state;
+
if (strv_contains(*units, *c)) {
if (!arg_plain) {
printf(" ");
- r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1);
+ r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), /* last = */ true);
if (r < 0)
return r;
}
continue;
}
+ if (arg_types && !strv_contains(arg_types, unit_type_suffix(*c)))
+ continue;
+
+ r = get_state_one_unit(bus, *c, &active_state);
+ if (r < 0)
+ return r;
+
+ if (arg_states) {
+ r = unit_load_state(bus, *c, &load_state);
+ if (r < 0)
+ return r;
+
+ r = get_sub_state_one_unit(bus, *c, &sub_state);
+ if (r < 0)
+ return r;
+
+ if (!strv_overlap(arg_states, STRV_MAKE(unit_active_state_to_string(active_state), load_state, sub_state)))
+ continue;
+ }
+
if (arg_plain)
printf(" ");
else {
- UnitActiveState active_state = _UNIT_ACTIVE_STATE_INVALID;
const char *on;
- (void) get_state_one_unit(bus, *c, &active_state);
-
switch (active_state) {
case UNIT_ACTIVE:
case UNIT_RELOADING:
@@ -141,6 +161,9 @@ int verb_list_dependencies(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r;
+ /* We won't be able to preserve the tree structure if --type= or --state= is used */
+ arg_plain = arg_plain || arg_types || arg_states;
+
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c
index b333850bec..bcad65f8dc 100644
--- a/src/systemctl/systemctl-util.c
+++ b/src/systemctl/systemctl-util.c
@@ -122,6 +122,7 @@ int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_activ
UnitActiveState state;
int r;
+ assert(bus);
assert(unit);
assert(ret_active_state);
@@ -148,6 +149,34 @@ int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_activ
return 0;
}
+int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_state) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *sub_state = NULL, *dbus_path = NULL;
+ int r;
+
+ assert(bus);
+ assert(unit);
+ assert(ret_sub_state);
+
+ dbus_path = unit_dbus_path_from_name(unit);
+ if (!dbus_path)
+ return log_oom();
+
+ r = sd_bus_get_property_string(
+ bus,
+ "org.freedesktop.systemd1",
+ dbus_path,
+ "org.freedesktop.systemd1.Unit",
+ "SubState",
+ &error,
+ &sub_state);
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve unit sub state: %s", bus_error_message(&error, r));
+
+ *ret_sub_state = TAKE_PTR(sub_state);
+ return 0;
+}
+
int get_unit_list(
sd_bus *bus,
const char *machine,
diff --git a/src/systemctl/systemctl-util.h b/src/systemctl/systemctl-util.h
index 6445bb4887..317bab75b7 100644
--- a/src/systemctl/systemctl-util.h
+++ b/src/systemctl/systemctl-util.h
@@ -21,7 +21,8 @@ void polkit_agent_open_maybe(void);
int translate_bus_error_to_exit_status(int r, const sd_bus_error *error);
-int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *ret_active_state);
+int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_active_state);
+int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_state);
int get_unit_list(sd_bus *bus, const char *machine, char **patterns, UnitInfo **unit_infos, int c, sd_bus_message **ret_reply);
int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded);
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index 497b2afb2f..a9fa78569d 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -264,6 +264,7 @@ int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
sd_dhcp6_option *v);
int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable);
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable);
int sd_dhcp6_client_get_lease(
sd_dhcp6_client *client,
diff --git a/src/test/test-hexdecoct.c b/src/test/test-hexdecoct.c
index afdc3b5436..9d71db6ae1 100644
--- a/src/test/test-hexdecoct.c
+++ b/src/test/test-hexdecoct.c
@@ -322,6 +322,13 @@ TEST(base64mem_linebreak) {
assert_se(decoded_size == n);
assert_se(memcmp(data, decoded, n) == 0);
+ /* Also try in secure mode */
+ decoded = mfree(decoded);
+ decoded_size = 0;
+ assert_se(unbase64mem_full(encoded, SIZE_MAX, /* secure= */ true, &decoded, &decoded_size) >= 0);
+ assert_se(decoded_size == n);
+ assert_se(memcmp(data, decoded, n) == 0);
+
for (size_t j = 0; j < (size_t) l; j++)
assert_se((encoded[j] == '\n') == (j % (m + 1) == m));
}
@@ -446,7 +453,17 @@ static void test_unbase64mem_one(const char *input, const char *output, int ret)
size_t size = 0;
assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret);
+ if (ret >= 0) {
+ assert_se(size == strlen(output));
+ assert_se(memcmp(buffer, output, size) == 0);
+ assert_se(((char*) buffer)[size] == 0);
+ }
+
+ /* also try in secure mode */
+ buffer = mfree(buffer);
+ size = 0;
+ assert_se(unbase64mem_full(input, SIZE_MAX, /* secure=*/ true, &buffer, &size) == ret);
if (ret >= 0) {
assert_se(size == strlen(output));
assert_se(memcmp(buffer, output, size) == 0);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index 52bf66ab0b..5bd09a64d1 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -579,9 +579,6 @@ static int parse_token(UdevRules *rules, const char *key, char *attr, UdevRuleOp
} else if (streq(key, "SYMLINK")) {
if (attr)
return log_token_invalid_attr(rules, key);
- if (op == OP_REMOVE)
- return log_token_invalid_op(rules, key);
-
if (!is_match) {
check_value_format_and_warn(rules, key, value, false);
r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL);
@@ -2313,11 +2310,17 @@ static int udev_rule_apply_token_to_event(
if (truncated)
continue;
- r = device_add_devlink(dev, filename);
- if (r < 0)
- return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
+ if (token->op == OP_REMOVE) {
+ device_remove_devlink(dev, filename);
+ log_rule_debug(dev, rules, "Dropped SYMLINK '%s'", p);
+ } else {
+ r = device_add_devlink(dev, filename);
+ if (r < 0)
+ return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename);
+
+ log_rule_debug(dev, rules, "Added SYMLINK '%s'", p);
+ }
- log_rule_debug(dev, rules, "LINK '%s'", p);
p = next;
}
break;