diff options
Diffstat (limited to 'src')
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; |