diff options
40 files changed, 890 insertions, 236 deletions
@@ -1019,10 +1019,6 @@ Features: * bootctl,sd-boot: actually honour the "architecture" key -* sd-boot: add service that automatically runs "bootctl update" on every boot, - in a graceful way, so that updated /usr trees automatically propagate into - updated boot loaders on reboot. - * bootctl: - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host diff --git a/man/bootctl.xml b/man/bootctl.xml index d05c3f34d07..a958cde7df8 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -233,8 +233,9 @@ <varlistentry> <term><option>--graceful</option></term> - <listitem><para>Ignore failure when the EFI System Partition cannot be found, or when EFI variables - cannot be written. Currently only applies to random seed operations.</para></listitem> + <listitem><para>Ignore failure when the EFI System Partition cannot be found, when EFI variables + cannot be written, or a different or newer boot loader is already installed. Currently only applies + to random seed and update operations.</para></listitem> </varlistentry> <varlistentry> diff --git a/meson.build b/meson.build index 27dcf956f3e..e08a7662a7f 100644 --- a/meson.build +++ b/meson.build @@ -722,6 +722,8 @@ if time_epoch == -1 endif conf.set('TIME_EPOCH', time_epoch) +conf.set('CLOCK_VALID_RANGE_USEC_MAX', get_option('clock-valid-range-usec-max')) + foreach tuple : [['system-alloc-uid-min', 'SYS_UID_MIN', 1], # Also see login.defs(5). ['system-uid-max', 'SYS_UID_MAX', 999], ['system-alloc-gid-min', 'SYS_GID_MIN', 1], diff --git a/meson_options.txt b/meson_options.txt index 0b01fb2cbf2..110a471b6be 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -208,6 +208,8 @@ option('status-unit-format-default', type : 'combo', description : 'use unit name or description in messages by default') option('time-epoch', type : 'integer', value : '-1', description : 'time epoch for time clients') +option('clock-valid-range-usec-max', type : 'integer', value : '473364000000000', # 15 years + description : 'maximum value in microseconds for the difference between RTC and epoch, exceeding which is considered an RTC error') option('system-alloc-uid-min', type : 'integer', value : '-1', description : 'minimum system UID used when allocating') diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index d26087445c9..8a1a08210cf 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -22,6 +22,7 @@ enable systemd-resolved.service enable systemd-homed.service enable systemd-userdbd.socket enable systemd-pstore.service +enable systemd-boot-update.service disable console-getty.service disable debug-shell.service diff --git a/src/basic/fileio.h b/src/basic/fileio.h index af797cfafdb..4295b84a85c 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -2,11 +2,11 @@ #pragma once #include <dirent.h> +#include <fcntl.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <sys/stat.h> -#include <sys/fcntl.h> #include <sys/types.h> #include "macro.h" diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index fd6b01cfaad..007e3a091ec 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -375,6 +375,9 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[", [SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,", [SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */ + [SPECIAL_GLYPH_RECYCLING] = "~", + [SPECIAL_GLYPH_DOWNLOAD] = "\\", + [SPECIAL_GLYPH_SPARKLES] = "*", }, /* UTF-8 */ @@ -421,7 +424,12 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_LOCK_AND_KEY] = "\360\237\224\220", /* ๐ (actually called: CLOSED LOCK WITH KEY) */ /* This emoji is a single character cell glyph in Unicode, and two in ASCII */ - [SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* ๐ (actually called: BACKHAND INDEX POINTING UP */ + [SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* ๐ (actually called: BACKHAND INDEX POINTING UP) */ + + /* These three emojis are single character cell glyphs in Unicode and also in ASCII. */ + [SPECIAL_GLYPH_RECYCLING] = "\u267B\uFE0F ", /* โป๏ธ (actually called: UNIVERSAL RECYCLNG SYMBOL) */ + [SPECIAL_GLYPH_DOWNLOAD] = "\u2935\uFE0F ", /* โคต๏ธ (actually called: RIGHT ARROW CURVING DOWN) */ + [SPECIAL_GLYPH_SPARKLES] = "\u2728", /* โจ */ }, }; diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index df259d1bbd9..3430eb6ee1f 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -69,6 +69,9 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_DEPRESSED_SMILEY, SPECIAL_GLYPH_LOCK_AND_KEY, SPECIAL_GLYPH_TOUCH, + SPECIAL_GLYPH_RECYCLING, + SPECIAL_GLYPH_DOWNLOAD, + SPECIAL_GLYPH_SPARKLES, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; @@ -95,3 +98,7 @@ static inline void locale_variables_freep(char*(*l)[_VARIABLE_LC_MAX]) { static inline const char *special_glyph_check_mark(bool b) { return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK); } + +static inline const char *special_glyph_check_mark_space(bool b) { + return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : " "; +} diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index b06b5ce7744..34b2b279189 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -265,7 +265,7 @@ int pop_pending_signal_internal(int sig, ...) { if (sigemptyset(&ss) < 0) return -errno; - /* Add first signal (if the signal is zero, we'll silently skip it, to make it easiert to build + /* Add first signal (if the signal is zero, we'll silently skip it, to make it easier to build * parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles * this.) */ if (sig > 0 && sigaddset(&ss, sig) < 0) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index fa8c6003218..5d126f4bea3 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -65,6 +65,7 @@ static const char *arg_dollar_boot_path(void) { static int acquire_esp( bool unprivileged_mode, + bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, @@ -80,10 +81,14 @@ static int acquire_esp( * this). */ r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); - if (r == -ENOKEY) + if (r == -ENOKEY) { + if (graceful) + return log_info_errno(r, "Couldn't find EFI system partition, skipping."); + return log_error_errno(r, "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n" "Alternatively, use --esp-path= to specify path to mount point."); + } if (r < 0) return r; @@ -500,7 +505,7 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t if (r < 0) return r; if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), "Source file \"%s\" does not carry version information!", from); @@ -508,12 +513,15 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t if (r < 0) return r; if (r == 0 || compare_product(a, b) != 0) - return log_notice_errno(SYNTHETIC_ERRNO(EEXIST), + return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), "Skipping \"%s\", since it's owned by another boot loader.", to); - if (compare_version(a, b) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since a newer boot loader version exists already.", to); + r = compare_version(a, b); + if (r < 0) + return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since newer boot loader version in place already.", to); + else if (r == 0) + return log_info_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since same boot loader version in place already.", to); return 0; } @@ -665,6 +673,10 @@ static int install_binaries(const char *esp_path, bool force) { continue; k = copy_one_file(esp_path, de->d_name, force); + /* Don't propagate an error code if no update necessary, installed version already equal or + * newer version, or other boot loader in place. */ + if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE)) + continue; if (k < 0 && r == 0) r = k; } @@ -1243,7 +1255,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; int r, k; - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid); + r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid); if (arg_print_esp_path) { if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only * error the find_esp_and_warn() won't log on its own) */ @@ -1254,7 +1266,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { puts(arg_esp_path); } - r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid); + r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, &xbootldr_uuid); if (arg_print_dollar_boot_path) { if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR location: %m"); @@ -1402,13 +1414,13 @@ static int verb_list(int argc, char *argv[], void *userdata) { * off logging about access errors and turn off potentially privileged device probing. Here we're interested in * the latter but not the former, hence request the mode, and log about EACCES. */ - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, NULL); + r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, NULL); if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ return log_error_errno(r, "Failed to determine ESP: %m"); if (r < 0) return r; - r = acquire_xbootldr(geteuid() != 0, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, NULL); if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); if (r < 0) @@ -1600,21 +1612,24 @@ static int verb_install(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; uint64_t pstart = 0, psize = 0; uint32_t part = 0; - bool install; + bool install, graceful; int r; - r = acquire_esp(false, &part, &pstart, &psize, &uuid); + install = streq(argv[0], "install"); + graceful = !install && arg_graceful; /* support graceful mode for updates */ + + r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid); + if (graceful && r == -ENOKEY) + return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */ if (r < 0) return r; - r = acquire_xbootldr(false, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL); if (r < 0) return r; settle_make_machine_id_directory(); - install = streq(argv[0], "install"); - RUN_WITH_UMASK(0002) { if (install) { /* Don't create any of these directories when we are just updating. When we update @@ -1663,11 +1678,11 @@ static int verb_remove(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; int r, q; - r = acquire_esp(false, NULL, NULL, NULL, &uuid); + r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid); if (r < 0) return r; - r = acquire_xbootldr(false, NULL); + r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL); if (r < 0) return r; @@ -1726,7 +1741,7 @@ static int verb_is_installed(int argc, char *argv[], void *userdata) { _cleanup_free_ char *p = NULL; int r; - r = acquire_esp(false, NULL, NULL, NULL, NULL); + r = acquire_esp(/* privileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, NULL); if (r < 0) return r; diff --git a/src/core/main.c b/src/core/main.c index 8920d70d5d7..473dc0920ea 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -83,6 +83,7 @@ #include "switch-root.h" #include "sysctl-util.h" #include "terminal-util.h" +#include "time-util.h" #include "umask-util.h" #include "user-util.h" #include "util.h" @@ -1598,11 +1599,18 @@ static void initialize_clock(void) { */ (void) clock_reset_timewarp(); - r = clock_apply_epoch(); - if (r < 0) - log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); - else if (r > 0) + ClockChangeDirection change_dir; + r = clock_apply_epoch(&change_dir); + if (r > 0 && change_dir == CLOCK_CHANGE_FORWARD) log_info("System time before build time, advancing clock."); + else if (r > 0 && change_dir == CLOCK_CHANGE_BACKWARD) + log_info("System time is further ahead than %s after build time, resetting clock to build time.", + FORMAT_TIMESPAN(CLOCK_VALID_RANGE_USEC_MAX, USEC_PER_DAY)); + else if (r < 0 && change_dir == CLOCK_CHANGE_FORWARD) + log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); + else if (r < 0 && change_dir == CLOCK_CHANGE_BACKWARD) + log_error_errno(r, "Current system time is further ahead %s after build time, but cannot correct: %m", + FORMAT_TIMESPAN(CLOCK_VALID_RANGE_USEC_MAX, USEC_PER_DAY)); } static void apply_clock_update(void) { diff --git a/src/coredump/stacktrace.c b/src/coredump/stacktrace.c index 0edb1b40a72..e46b324cdf7 100644 --- a/src/coredump/stacktrace.c +++ b/src/coredump/stacktrace.c @@ -153,6 +153,8 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e program_header->p_offset, program_header->p_filesz, ELF_T_NHDR); + if (!data) + continue; while (note_offset < data->d_size && (note_offset = gelf_getnote(data, note_offset, ¬e_header, &name_offset, &desc_offset)) > 0) { diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 9c1478c474e..697b4c2335b 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -65,6 +65,7 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_(erase_and_freep) char *base64_encoded = NULL; size_t secret_size, secret2_size, blob_size, hash_size; _cleanup_free_ void *blob = NULL, *hash = NULL; + uint16_t pcr_bank; const char *node; int r, keyslot; @@ -75,7 +76,7 @@ int enroll_tpm2(struct crypt_device *cd, assert_se(node = crypt_get_device_name(cd)); - r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size); + r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank); if (r < 0) return r; @@ -92,7 +93,7 @@ int enroll_tpm2(struct crypt_device *cd, /* Quick verification that everything is in order, we are not in a hurry after all. */ log_debug("Unsealing for verification..."); - r = tpm2_unseal(device, pcr_mask, blob, blob_size, hash, hash_size, &secret2, &secret2_size); + r = tpm2_unseal(device, pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &secret2, &secret2_size); if (r < 0) return r; @@ -118,7 +119,7 @@ int enroll_tpm2(struct crypt_device *cd, if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); - r = tpm2_make_luks2_json(keyslot, pcr_mask, blob, blob_size, hash, hash_size, &v); + r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 152b06b111a..d3aa092f6b8 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -57,6 +57,7 @@ _public_ int cryptsetup_token_open( const char *json; size_t blob_size, policy_hash_size, decrypted_key_size; uint32_t pcr_mask; + uint16_t pcr_bank; systemd_tpm2_plugin_params params = { .search_pcr_mask = UINT32_MAX }; @@ -77,7 +78,7 @@ _public_ int cryptsetup_token_open( if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; - r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &base64_blob, &hex_policy_hash); + r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash); if (r < 0) return log_debug_open_error(cd, r); @@ -93,6 +94,7 @@ _public_ int cryptsetup_token_open( r = acquire_luks2_key( pcr_mask, + pcr_bank, params.device, blob, blob_size, @@ -133,6 +135,7 @@ _public_ void cryptsetup_token_dump( int r; uint32_t i, pcr_mask; + uint16_t pcr_bank; size_t decoded_blob_size; _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL, *pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL; @@ -140,7 +143,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &base64_blob, &hex_policy_hash); + r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -162,7 +165,8 @@ _public_ void cryptsetup_token_dump( if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); - crypt_log(cd, "\ttpm2-pcrs: %s\n", pcrs_str ?: ""); + crypt_log(cd, "\ttpm2-pcrs: %s\n", strna(pcrs_str)); + crypt_log(cd, "\ttpm2-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank))); crypt_log(cd, "\ttmp2-blob: %s\n", blob_str); crypt_log(cd, "\ttmp2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 00540659266..a5571f31f6d 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -10,6 +10,7 @@ int acquire_luks2_key( uint32_t pcr_mask, + uint16_t pcr_bank, const char *device, const void *key_data, size_t key_data_size, @@ -34,7 +35,12 @@ int acquire_luks2_key( device = auto_device; } - return tpm2_unseal(device, pcr_mask, key_data, key_data_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); + return tpm2_unseal( + device, + pcr_mask, pcr_bank, + key_data, key_data_size, + policy_hash, policy_hash_size, + ret_decrypted_key, ret_decrypted_key_size); } /* this function expects valid "systemd-tpm2" in json */ @@ -42,19 +48,22 @@ int parse_luks2_tpm2_data( const char *json, uint32_t search_pcr_mask, uint32_t *ret_pcr_mask, + uint16_t *ret_pcr_bank, char **ret_base64_blob, char **ret_hex_policy_hash) { int r; JsonVariant *w, *e; uint32_t pcr_mask = 0; + uint16_t pcr_bank = UINT16_MAX; _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; assert(json); + assert(ret_pcr_mask); + assert(ret_pcr_bank); assert(ret_base64_blob); assert(ret_hex_policy_hash); - assert(ret_pcr_mask); r = json_parse(json, 0, &v, NULL, NULL); if (r < 0) @@ -81,6 +90,20 @@ int parse_luks2_tpm2_data( search_pcr_mask != pcr_mask) return -ENXIO; + w = json_variant_by_key(v, "tpm2-pcr-bank"); + if (w) { + /* The PCR bank field is optional */ + + if (!json_variant_is_string(w)) + return -EINVAL; + + r = tpm2_pcr_bank_from_string(json_variant_string(w)); + if (r < 0) + return r; + + pcr_bank = r; + } + w = json_variant_by_key(v, "tpm2-blob"); if (!w || !json_variant_is_string(w)) return -EINVAL; @@ -98,6 +121,7 @@ int parse_luks2_tpm2_data( return -ENOMEM; *ret_pcr_mask = pcr_mask; + *ret_pcr_bank = pcr_bank; *ret_base64_blob = TAKE_PTR(base64_blob); *ret_hex_policy_hash = TAKE_PTR(hex_policy_hash); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index d36623baf96..1a20f2cc1fd 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -6,6 +6,7 @@ struct crypt_device; int acquire_luks2_key( uint32_t pcr_mask, + uint16_t pcr_bank, const char *device, const void *key_data, size_t key_data_size, @@ -18,5 +19,6 @@ int parse_luks2_tpm2_data( const char *json, uint32_t search_pcr_mask, uint32_t *ret_pcr_mask, + uint16_t *ret_pcr_bank, char **ret_base64_blob, char **ret_hex_policy_hash); diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index 4757c5882d0..8da1880a35c 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -13,6 +13,7 @@ int acquire_tpm2_key( const char *volume_name, const char *device, uint32_t pcr_mask, + uint16_t pcr_bank, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -62,7 +63,7 @@ int acquire_tpm2_key( blob = loaded_blob; } - return tpm2_unseal(device, pcr_mask, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); + return tpm2_unseal(device, pcr_mask, pcr_bank, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); } int find_tpm2_auto_data( @@ -70,6 +71,7 @@ int find_tpm2_auto_data( uint32_t search_pcr_mask, int start_token, uint32_t *ret_pcr_mask, + uint16_t *ret_pcr_bank, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, @@ -81,6 +83,7 @@ int find_tpm2_auto_data( size_t blob_size = 0, policy_hash_size = 0; int r, keyslot = -1, token = -1; uint32_t pcr_mask = 0; + uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ assert(cd); @@ -119,6 +122,23 @@ int find_tpm2_auto_data( search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ continue; + /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded to SHA256 */ + assert(pcr_bank == UINT16_MAX); + w = json_variant_by_key(v, "tpm2-pcr-bank"); + if (w) { + /* The PCR bank field is optional */ + + if (!json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 PCR bank is not a string."); + + r = tpm2_pcr_bank_from_string(json_variant_string(w)); + if (r < 0) + return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w)); + + pcr_bank = r; + } + assert(!blob); w = json_variant_by_key(v, "tpm2-blob"); if (!w || !json_variant_is_string(w)) @@ -163,6 +183,7 @@ int find_tpm2_auto_data( *ret_policy_hash_size = policy_hash_size; *ret_keyslot = keyslot; *ret_token = token; + *ret_pcr_bank = pcr_bank; return 0; } diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h index 8ddf301a635..a82ecb45941 100644 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -13,6 +13,7 @@ int acquire_tpm2_key( const char *volume_name, const char *device, uint32_t pcr_mask, + uint16_t pcr_bank, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -28,6 +29,7 @@ int find_tpm2_auto_data( uint32_t search_pcr_mask, int start_token, uint32_t *ret_pcr_mask, + uint16_t *ret_pcr_bank, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, @@ -41,6 +43,7 @@ static inline int acquire_tpm2_key( const char *volume_name, const char *device, uint32_t pcr_mask, + uint16_t pcr_bank, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -60,6 +63,7 @@ static inline int find_tpm2_auto_data( uint32_t search_pcr_mask, int start_token, uint32_t *ret_pcr_mask, + uint16_t *ret_pcr_bank, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 48621ef5838..0d3ea9cda64 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1103,6 +1103,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( name, arg_tpm2_device, arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask, + UINT16_MAX, key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, NULL, 0, /* we don't know the policy hash */ @@ -1139,12 +1140,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( for (;;) { uint32_t pcr_mask; + uint16_t pcr_bank; r = find_tpm2_auto_data( cd, arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */ token, /* search for the token with this index, or any later index than this */ &pcr_mask, + &pcr_bank, &blob, &blob_size, &policy_hash, &policy_hash_size, &keyslot, @@ -1166,6 +1169,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( name, arg_tpm2_device, pcr_mask, + pcr_bank, NULL, 0, 0, /* no key file */ blob, blob_size, policy_hash, policy_hash_size, diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 009d283acc6..fc0b0f11ad9 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -297,7 +297,7 @@ static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid, char desc = mfree(desc); *ret_gid = converted_gid; - *ret_description = desc; + *ret_description = TAKE_PTR(desc); return 0; } diff --git a/src/partition/repart.c b/src/partition/repart.c index 6d535a3bab0..42a8260c2e2 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2624,9 +2624,10 @@ static int partition_encrypt( _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *blob = NULL, *hash = NULL; size_t secret_size, blob_size, hash_size; + uint16_t pcr_bank; int keyslot; - r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size); + r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); @@ -2648,7 +2649,7 @@ static int partition_encrypt( if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); - r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, blob, blob_size, hash, hash_size, &v); + r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index f4e291385be..00a6a37ff70 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1613,7 +1613,7 @@ int btrfs_subvol_snapshot_fd_full( return -EISDIR; r = btrfs_subvol_make(new_path); - if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { + if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */ if (mkdir(new_path, 0755) < 0) return -errno; @@ -1624,8 +1624,15 @@ int btrfs_subvol_snapshot_fd_full( r = copy_directory_fd_full( old_fd, new_path, - COPY_MERGE|COPY_REFLINK|COPY_SAME_MOUNT|COPY_HARDLINKS|(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0), - progress_path, progress_bytes, userdata); + COPY_MERGE_EMPTY| + COPY_REFLINK| + COPY_SAME_MOUNT| + COPY_HARDLINKS| + (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0)| + (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGTERM) ? COPY_SIGTERM : 0), + progress_path, + progress_bytes, + userdata); if (r < 0) goto fallback_fail; diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 7b18f57719a..b67a4c10fe8 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -35,6 +35,7 @@ typedef enum BtrfsSnapshotFlags { BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 1 << 4, /* If the destination doesn't support subvolumes, reflink/copy instead */ BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 1 << 5, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */ BTRFS_SNAPSHOT_SIGINT = 1 << 6, /* Check for SIGINT regularly, and return EINTR if seen */ + BTRFS_SNAPSHOT_SIGTERM = 1 << 7, /* Ditto, but for SIGTERM */ } BtrfsSnapshotFlags; typedef enum BtrfsRemoveFlags { diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c index b446daf5819..7c1c48d0690 100644 --- a/src/shared/clock-util.c +++ b/src/shared/clock-util.c @@ -139,10 +139,15 @@ int clock_reset_timewarp(void) { #define EPOCH_FILE "/usr/lib/clock-epoch" -int clock_apply_epoch(void) { +int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) { struct stat st; struct timespec ts; - usec_t epoch_usec; + usec_t epoch_usec, now_usec; + + /* NB: we update *ret_attempted_change in *all* cases, both + * on success and failure, to indicate what we intended to do! */ + + assert(ret_attempted_change); if (stat(EPOCH_FILE, &st) < 0) { if (errno != ENOENT) @@ -152,8 +157,15 @@ int clock_apply_epoch(void) { } else epoch_usec = timespec_load(&st.st_mtim); - if (now(CLOCK_REALTIME) >= epoch_usec) + now_usec = now(CLOCK_REALTIME); + if (now_usec < epoch_usec) + *ret_attempted_change = CLOCK_CHANGE_FORWARD; + else if (now_usec > usec_add(epoch_usec, CLOCK_VALID_RANGE_USEC_MAX)) + *ret_attempted_change = CLOCK_CHANGE_BACKWARD; + else { + *ret_attempted_change = CLOCK_CHANGE_NOOP; return 0; + } if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, epoch_usec)) < 0) return -errno; diff --git a/src/shared/clock-util.h b/src/shared/clock-util.h index 9e96d50d963..c8f6d1b1f1e 100644 --- a/src/shared/clock-util.h +++ b/src/shared/clock-util.h @@ -1,11 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <errno.h> #include <time.h> +typedef enum ClockChangeDirection { + CLOCK_CHANGE_NOOP, + CLOCK_CHANGE_FORWARD, + CLOCK_CHANGE_BACKWARD, + _CLOCK_CHANGE_MAX, + _CLOCK_CHANGE_INVALID = -EINVAL, +} ClockChangeDirection; + int clock_is_localtime(const char* adjtime_path); int clock_set_timezone(int *ret_minutesdelta); int clock_reset_timewarp(void); int clock_get_hwclock(struct tm *tm); int clock_set_hwclock(const struct tm *tm); -int clock_apply_epoch(void); +int clock_apply_epoch(ClockChangeDirection *ret_attempted_change); diff --git a/src/shared/copy.c b/src/shared/copy.c index 65d75a15d54..a01b6df1b22 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -88,6 +88,23 @@ static int fd_is_nonblock_pipe(int fd) { return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE; } +static int look_for_signals(CopyFlags copy_flags) { + int r; + + if ((copy_flags & (COPY_SIGINT|COPY_SIGTERM)) == 0) + return 0; + + r = pop_pending_signal(copy_flags & COPY_SIGINT ? SIGINT : 0, + copy_flags & COPY_SIGTERM ? SIGTERM : 0); + if (r < 0) + return r; + if (r != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINTR), + "Got %s, cancelling copy operation.", signal_to_string(r)); + + return 0; +} + int copy_bytes_full( int fdf, int fdt, uint64_t max_bytes, @@ -176,13 +193,9 @@ int copy_bytes_full( if (max_bytes <= 0) return 1; /* return > 0 if we hit the max_bytes limit */ - if (FLAGS_SET(copy_flags, COPY_SIGINT)) { - r = pop_pending_signal(SIGINT); - if (r < 0) - return r; - if (r > 0) - return -EINTR; - } + r = look_for_signals(copy_flags); + if (r < 0) + return r; if (max_bytes != UINT64_MAX && m > max_bytes) m = max_bytes; @@ -627,10 +640,8 @@ static int fd_copy_regular( return -errno; r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata); - if (r < 0) { - (void) unlinkat(dt, to, 0); - return r; - } + if (r < 0) + goto fail; if (fchown(fdt, uid_is_valid(override_uid) ? override_uid : st->st_uid, @@ -643,16 +654,25 @@ static int fd_copy_regular( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); (void) copy_xattr(fdf, fdt); - q = close(fdt); - fdt = -1; + if (copy_flags & COPY_FSYNC) { + if (fsync(fdt) < 0) { + r = -errno; + goto fail; + } + } + q = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ if (q < 0) { - r = -errno; - (void) unlinkat(dt, to, 0); + r = q; + goto fail; } (void) memorize_hardlink(hardlink_context, st, dt, to); return r; + +fail: + (void) unlinkat(dt, to, 0); + return r; } static int fd_copy_fifo( @@ -845,13 +865,9 @@ static int fd_copy_directory( if (dot_or_dot_dot(de->d_name)) continue; - if (FLAGS_SET(copy_flags, COPY_SIGINT)) { - r = pop_pending_signal(SIGINT); - if (r < 0) - return r; - if (r > 0) - return -EINTR; - } + r = look_for_signals(copy_flags); + if (r < 0) + return r; if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { r = -errno; @@ -912,7 +928,7 @@ static int fd_copy_directory( else q = -EOPNOTSUPP; - if (q == -EINTR) /* Propagate SIGINT up instantly */ + if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */ return q; if (q == -EEXIST && (copy_flags & COPY_MERGE)) q = 0; @@ -933,6 +949,11 @@ static int fd_copy_directory( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); } + if (copy_flags & COPY_FSYNC_FULL) { + if (fsync(fdt) < 0) + return -errno; + } + return r; } @@ -949,6 +970,7 @@ int copy_tree_at_full( void *userdata) { struct stat st; + int r; assert(from); assert(to); @@ -957,17 +979,47 @@ int copy_tree_at_full( return -errno; if (S_ISREG(st.st_mode)) - return fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata); + r = fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); + r = fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags); else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); + r = fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode)) - return fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); + r = fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL); else return -EOPNOTSUPP; + if (r < 0) + return r; + + if (S_ISDIR(st.st_mode) && (copy_flags & COPY_SYNCFS)) { + /* If the top-level inode is a directory run syncfs() now. */ + r = syncfs_path(fdt, to); + if (r < 0) + return r; + } else if ((copy_flags & (COPY_FSYNC_FULL|COPY_SYNCFS)) != 0) { + /* fsync() the parent dir of what we just copied if COPY_FSYNC_FULL is set. Also do this in + * case COPY_SYNCFS is set but the top-level inode wasn't actually a directory. We do this so that + * COPY_SYNCFS provides reasonable synchronization semantics on any kind of inode: when the + * copy operation is done the whole inode โ regardless of its type โ and all its children + * will be synchronized to disk. */ + r = fsync_parent_at(fdt, to); + if (r < 0) + return r; + } + + return 0; +} + +static int sync_dir_by_flags(const char *path, CopyFlags copy_flags) { + + if (copy_flags & COPY_SYNCFS) + return syncfs_path(AT_FDCWD, path); + if (copy_flags & COPY_FSYNC_FULL) + return fsync_parent_at(AT_FDCWD, path); + + return 0; } int copy_directory_fd_full( @@ -991,7 +1043,26 @@ int copy_directory_fd_full( if (r < 0) return r; - return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory( + dirfd, NULL, + &st, + AT_FDCWD, to, + st.st_dev, + COPY_DEPTH_MAX, + UID_INVALID, GID_INVALID, + copy_flags, + NULL, NULL, + progress_path, + progress_bytes, + userdata); + if (r < 0) + return r; + + r = sync_dir_by_flags(to, copy_flags); + if (r < 0) + return r; + + return 0; } int copy_directory_full( @@ -1015,7 +1086,26 @@ int copy_directory_full( if (r < 0) return r; - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata); + r = fd_copy_directory( + AT_FDCWD, from, + &st, + AT_FDCWD, to, + st.st_dev, + COPY_DEPTH_MAX, + UID_INVALID, GID_INVALID, + copy_flags, + NULL, NULL, + progress_path, + progress_bytes, + userdata); + if (r < 0) + return r; + + r = sync_dir_by_flags(to, copy_flags); + if (r < 0) + return r; + + return 0; } int copy_file_fd_full( @@ -1026,6 +1116,7 @@ int copy_file_fd_full( void *userdata) { _cleanup_close_ int fdf = -1; + struct stat st; int r; assert(from); @@ -1035,12 +1126,32 @@ int copy_file_fd_full( if (fdf < 0) return -errno; + r = fd_verify_regular(fdf); + if (r < 0) + return r; + + if (fstat(fdt, &st) < 0) + return -errno; + r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata); + if (r < 0) + return r; - (void) copy_times(fdf, fdt, copy_flags); - (void) copy_xattr(fdf, fdt); + if (S_ISREG(fdt)) { + (void) copy_times(fdf, fdt, copy_flags); + (void) copy_xattr(fdf, fdt); + } - return r; + if (copy_flags & COPY_FSYNC_FULL) { + r = fsync_full(fdt); + if (r < 0) + return r; + } else if (copy_flags & COPY_FSYNC) { + if (fsync(fdt) < 0) + return -errno; + } + + return 0; } int copy_file_full( @@ -1054,9 +1165,9 @@ int copy_file_full( copy_progress_bytes_t progress_bytes, void *userdata) { - _cleanup_close_ int fdf = -1; + _cleanup_close_ int fdf = -1, fdt = -1; struct stat st; - int r, fdt = -1; /* avoid false maybe-uninitialized warning */ + int r; assert(from); assert(to); @@ -1065,9 +1176,12 @@ int copy_file_full( if (fdf < 0) return -errno; - if (mode == MODE_INVALID) - if (fstat(fdf, &st) < 0) - return -errno; + if (fstat(fdf, &st) < 0) + return -errno; + + r = stat_verify_regular(&st); + if (r < 0) + return r; RUN_WITH_UMASK(0000) { if (copy_flags & COPY_MAC_CREATE) { @@ -1083,15 +1197,18 @@ int copy_file_full( return -errno; } + if (!FLAGS_SET(flags, O_EXCL)) { /* if O_EXCL was used we created the thing as regular file, no need to check again */ + r = fd_verify_regular(fdt); + if (r < 0) + goto fail; + } + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL); r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata); - if (r < 0) { - close(fdt); - (void) unlink(to); - return r; - } + if (r < 0) + goto fail; (void) copy_times(fdf, fdt, copy_flags); (void) copy_xattr(fdf, fdt); @@ -1099,12 +1216,31 @@ int copy_file_full( if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL); - if (close(fdt) < 0) { - unlink_noerrno(to); - return -errno; + if (copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL)) { + if (fsync(fdt) < 0) { + r = -errno; + goto fail; + } + } + + r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ + if (r < 0) + goto fail; + + if (copy_flags & COPY_FSYNC_FULL) { + r = fsync_parent_at(AT_FDCWD, to); + if (r < 0) + goto fail; } return 0; + +fail: + /* Only unlink if we definitely are the ones who created the file */ + if (FLAGS_SET(flags, O_EXCL)) + (void) unlink(to); + + return r; } int copy_file_atomic_full( @@ -1172,6 +1308,12 @@ int copy_file_atomic_full( if (fchmod(fdt, mode) < 0) return -errno; + if ((copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL))) { + /* Sync the file */ + if (fsync(fdt) < 0) + return -errno; + } + if (copy_flags & COPY_REPLACE) { if (renameat(AT_FDCWD, t, AT_FDCWD, to) < 0) return -errno; @@ -1181,11 +1323,27 @@ int copy_file_atomic_full( return r; } + t = mfree(t); + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL); - t = mfree(t); + r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */ + if (r < 0) + goto fail; + + if (copy_flags & COPY_FSYNC_FULL) { + /* Sync the parent directory */ + r = fsync_parent_at(AT_FDCWD, to); + if (r < 0) + goto fail; + } + return 0; + +fail: + (void) unlink(to); + return r; } int copy_times(int fdf, int fdt, CopyFlags flags) { diff --git a/src/shared/copy.h b/src/shared/copy.h index b36ddfcb015..f8992843e45 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -10,15 +10,19 @@ #include <sys/types.h> typedef enum CopyFlags { - COPY_REFLINK = 1 << 0, /* Try to reflink */ - COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */ - COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */ - COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */ - COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */ - COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */ - COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */ - COPY_MAC_CREATE = 1 << 7, /* Create files with the correct MAC label (currently SELinux only) */ - COPY_HARDLINKS = 1 << 8, /* Try to reproduce hard links */ + COPY_REFLINK = 1 << 0, /* Try to reflink */ + COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */ + COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */ + COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */ + COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */ + COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */ + COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */ + COPY_SIGTERM = 1 << 7, /* ditto, but for SIGTERM */ + COPY_MAC_CREATE = 1 << 8, /* Create files with the correct MAC label (currently SELinux only) */ + COPY_HARDLINKS = 1 << 9, /* Try to reproduce hard links */ + COPY_FSYNC = 1 << 10, /* fsync() after we are done */ + COPY_FSYNC_FULL = 1 << 11, /* fsync_full() after we are done */ + COPY_SYNCFS = 1 << 12, /* syncfs() the *top-level* dir after we are done */ } CopyFlags; typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 6c2d9dbc76c..ee279e0c9c1 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -369,7 +369,10 @@ struct _packed_ encrypted_credential_header { }; struct _packed_ tpm2_credential_header { - le64_t pcr_mask; + le64_t pcr_mask; /* Note that the spec for PC Clients only mandates 24 PCRs, and that's what systems + * generally have. But keep the door open for more. */ + le16_t pcr_bank; /* For now, either TPM2_ALG_SHA256 or TPM2_ALG_SHA1 */ + le16_t _zero; /* Filler to maintain 32bit alignment */ le32_t blob_size; le32_t policy_hash_size; uint8_t policy_hash_and_blob[]; @@ -439,6 +442,7 @@ int encrypt_credential_and_warn( struct encrypted_credential_header *h; int ksz, bsz, ivsz, tsz, added, r; uint8_t md[SHA256_DIGEST_LENGTH]; + uint16_t tpm2_pcr_bank = 0; const EVP_CIPHER *cc; #if HAVE_TPM2 bool try_tpm2 = false; @@ -505,7 +509,8 @@ int encrypt_credential_and_warn( &tpm2_blob, &tpm2_blob_size, &tpm2_policy_hash, - &tpm2_policy_hash_size); + &tpm2_policy_hash_size, + &tpm2_pcr_bank); if (r < 0) { if (!sd_id128_is_null(with_key)) return r; @@ -598,6 +603,7 @@ int encrypt_credential_and_warn( t = (struct tpm2_credential_header*) ((uint8_t*) output + p); t->pcr_mask = htole64(tpm2_pcr_mask); + t->pcr_bank = htole16(tpm2_pcr_bank); t->blob_size = htole32(tpm2_blob_size); t->policy_hash_size = htole32(tpm2_policy_hash_size); memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size); @@ -739,6 +745,10 @@ int decrypt_credential_and_warn( if (le64toh(t->pcr_mask) >= (UINT64_C(1) << TPM2_PCRS_MAX)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range."); + if (!tpm2_pcr_bank_supported(le16toh(t->pcr_bank))) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR bank invalid or not supported"); + if (le16toh(t->_zero) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 padding space not zero."); if (le32toh(t->blob_size) > CREDENTIAL_FIELD_SIZE_MAX) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 blob size."); if (le32toh(t->policy_hash_size) > CREDENTIAL_FIELD_SIZE_MAX) @@ -755,6 +765,7 @@ int decrypt_credential_and_warn( r = tpm2_unseal(tpm2_device, le64toh(t->pcr_mask), + le16toh(t->pcr_bank), t->policy_hash_and_blob, le32toh(t->blob_size), t->policy_hash_and_blob + le32toh(t->blob_size), diff --git a/src/shared/import-util.c b/src/shared/import-util.c index 2a30e40686f..bb1e5136a73 100644 --- a/src/shared/import-util.c +++ b/src/shared/import-util.c @@ -14,53 +14,114 @@ #include "string-table.h" #include "string-util.h" -int import_url_last_component(const char *url, char **ret) { - const char *e, *p; - char *s; +static const char *skip_protocol_and_hostname(const char *url) { + const char *d; + size_t n; + + /* A very very lenient implementation of RFC3986 Section 3.2 */ + + /* Find colon separating protocol and hostname */ + d = strchr(url, ':'); + if (!d || url == d) + return NULL; + d++; + + /* Skip slashes after colon */ + d += strspn(d, "/"); + + /* Skip everything till next slash or end */ + n = strcspn(d, "/?#"); + if (n == 0) + return NULL; + + return d + n; +} - e = strchrnul(url, '?'); +int import_url_last_component( + const char *url, + char **ret) { - while (e > url && e[-1] == '/') + const char *e, *p, *h; + + /* This extracts the last path component of the specified URI, i.e. the last non-empty substrings + * between two "/" characters. This ignores "Query" and "Fragment" suffixes (as per RFC3986). */ + + h = skip_protocol_and_hostname(url); + if (!h) + return -EINVAL; + + e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */ + + while (e > h && e[-1] == '/') /* Eat trailing slashes */ e--; p = e; - while (p > url && p[-1] != '/') + while (p > h && p[-1] != '/') /* Find component before that */ p--; - if (e <= p) - return -EINVAL; + if (e <= p) /* Empty component? */ + return -EADDRNOTAVAIL; - s = strndup(p, e - p); - if (!s) - return -ENOMEM; + if (ret) { + char *s; + + s = strndup(p, e - p); + if (!s) + return -ENOMEM; + + *ret = s; + } - *ret = s; return 0; } -int import_url_change_last_component(const char *url, const char *suffix, char **ret) { - const char *e; +int import_url_change_suffix( + const char *url, + size_t n_drop_components, + const char *suffix, + char **ret) { + + const char *e, *h; char *s; assert(url); assert(ret); - e = strchrnul(url, '?'); + /* This drops the specified number of path components of the specified URI, i.e. the specified number + * of non-empty substring between two "/" characters from the end of the string, and then append the + * specified suffix instead. Before doing all this it chops off the "Query" and "Fragment" suffixes + * (they are *not* readded to the final URL). Note that n_drop_components may be 0 (in which case the + * component are simply added to the end). The suffix may be specified as NULL or empty string in + * which case nothing is appended, only the specified number of components chopped off. Note that the + * function may be called with n_drop_components == 0 and suffix == NULL, in which case the "Query" + * and "Fragment" is chopped off, and ensured the URL ends in a single "/", and that's it. */ + + h = skip_protocol_and_hostname(url); + if (!h) + return -EINVAL; - while (e > url && e[-1] == '/') - e--; + e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */ - while (e > url && e[-1] != '/') + while (e > h && e[-1] == '/') /* Eat trailing slashes */ e--; - if (e <= url) - return -EINVAL; + /* Drop the specified number of components from the end. Note that this is pretty lenient: if there + * are less component we silently drop those and then append the suffix to the top. */ + while (n_drop_components > 0) { + while (e > h && e[-1] != '/') /* Eat last word (we don't mind if empty) */ + e--; + + while (e > h && e[-1] == '/') /* Eat slashes before the last word */ + e--; + + n_drop_components--; + } - s = new(char, (e - url) + strlen(suffix) + 1); + s = new(char, (e - url) + 1 + strlen_ptr(suffix) + 1); if (!s) return -ENOMEM; - strcpy(mempcpy(s, url, e - url), suffix); + strcpy(stpcpy(mempcpy(s, url, e - url), "/"), strempty(suffix)); *ret = s; return 0; } diff --git a/src/shared/import-util.h b/src/shared/import-util.h index c7ec3b4eabd..3b2425b9165 100644 --- a/src/shared/import-util.h +++ b/src/shared/import-util.h @@ -14,7 +14,16 @@ typedef enum ImportVerify { } ImportVerify; int import_url_last_component(const char *url, char **ret); -int import_url_change_last_component(const char *url, const char *suffix, char **ret); + +int import_url_change_suffix(const char *url, size_t n_drop_components, const char *suffix, char **ret); + +static inline int import_url_change_last_component(const char *url, const char *suffix, char **ret) { + return import_url_change_suffix(url, 1, suffix, ret); +} + +static inline int import_url_append_component(const char *url, const char *suffix, char **ret) { + return import_url_change_suffix(url, 0, suffix, ret); +} const char* import_verify_to_string(ImportVerify v) _const_; ImportVerify import_verify_from_string(const char *s) _pure_; diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c index dfc47c42344..19e16d93455 100644 --- a/src/shared/nscd-flush.c +++ b/src/shared/nscd-flush.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <sys/poll.h> +#include <poll.h> #include "fd-util.h" #include "io-util.h" diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 900a7fb5fff..dffb9cf6ee8 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -19,6 +19,9 @@ #include "stat-util.h" #include "string-util.h" +/* We treat tmpfs/ramfs + cgroupfs as non-physical file sytems. cgroupfs is similar to tmpfs in a way after + * all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it to remove + * those again. */ static bool is_physical_fs(const struct statfs *sfs) { return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs); } @@ -113,133 +116,145 @@ int fstatat_harder(int dfd, return 0; } -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int ret = 0, r; - struct statfs sfs; +static int rm_rf_children_inner( + int fd, + const char *fname, + int is_dir, + RemoveFlags flags, + const struct stat *root_dev) { - assert(fd >= 0); + struct stat st; + int r; - /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed - * fd, in all cases, including on failure.. */ + assert(fd >= 0); + assert(fname); - if (!(flags & REMOVE_PHYSICAL)) { + if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) { - r = fstatfs(fd, &sfs); - if (r < 0) { - safe_close(fd); - return -errno; - } + r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags); + if (r < 0) + return r; - if (is_physical_fs(&sfs)) { - /* We refuse to clean physical file systems with this call, - * unless explicitly requested. This is extra paranoia just - * to be sure we never ever remove non-state data. */ - _cleanup_free_ char *path = NULL; + is_dir = S_ISDIR(st.st_mode); + } - (void) fd_get_path(fd, &path); - log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.", - strna(path)); + if (is_dir) { + _cleanup_close_ int subdir_fd = -1; + int q; - safe_close(fd); - return -EPERM; - } - } + /* if root_dev is set, remove subdirectories only if device is same */ + if (root_dev && st.st_dev != root_dev->st_dev) + return 0; - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return errno == ENOENT ? 0 : -errno; - } + /* Stop at mount points */ + r = fd_is_mount_point(fd, fname, 0); + if (r < 0) + return r; + if (r > 0) + return 0; - FOREACH_DIRENT_ALL(de, d, return -errno) { - bool is_dir; - struct stat st; + if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { - if (dot_or_dot_dot(de->d_name)) - continue; + /* This could be a subvolume, try to remove it */ - if (de->d_type == DT_UNKNOWN || - (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { - r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags); + r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) { - if (ret == 0 && r != -ENOENT) - ret = r; - continue; - } + if (!IN_SET(r, -ENOTTY, -EINVAL)) + return r; - is_dir = S_ISDIR(st.st_mode); - } else - is_dir = de->d_type == DT_DIR; + /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */ + } else + /* It was a subvolume, done. */ + return 1; + } - if (is_dir) { - _cleanup_close_ int subdir_fd = -1; + subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) + return -errno; - /* if root_dev is set, remove subdirectories only if device is same */ - if (root_dev && st.st_dev != root_dev->st_dev) - continue; + /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type + * again for each directory */ + q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev); - subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } + r = unlinkat_harder(fd, fname, AT_REMOVEDIR, flags); + if (r < 0) + return r; + if (q < 0) + return q; - /* Stop at mount points */ - r = fd_is_mount_point(fd, de->d_name, 0); - if (r < 0) { - if (ret == 0 && r != -ENOENT) - ret = r; + return 1; - continue; - } - if (r > 0) - continue; + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + r = unlinkat_harder(fd, fname, 0, flags); + if (r < 0) + return r; - if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { + return 1; + } - /* This could be a subvolume, try to remove it */ + return 0; +} - r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r < 0) { - if (!IN_SET(r, -ENOTTY, -EINVAL)) { - if (ret == 0) - ret = r; +int rm_rf_children( + int fd, + RemoveFlags flags, + const struct stat *root_dev) { - continue; - } + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int ret = 0, r; - /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */ - } else - /* It was a subvolume, continue. */ - continue; - } + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed + * fd, in all cases, including on failure. */ + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } - /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file - * system type again for each directory */ - r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev); - if (r < 0 && ret == 0) - ret = r; + if (!(flags & REMOVE_PHYSICAL)) { + struct statfs sfs; - r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags); - if (r < 0 && r != -ENOENT && ret == 0) - ret = r; + if (fstatfs(dirfd(d), &sfs) < 0) + return -errno; + + if (is_physical_fs(&sfs)) { + /* We refuse to clean physical file systems with this call, unless explicitly + * requested. This is extra paranoia just to be sure we never ever remove non-state + * data. */ - } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + _cleanup_free_ char *path = NULL; - r = unlinkat_harder(fd, de->d_name, 0, flags); - if (r < 0 && r != -ENOENT && ret == 0) - ret = r; + (void) fd_get_path(fd, &path); + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Attempted to remove disk file system under \"%s\", and we can't allow that.", + strna(path)); } } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + int is_dir; + + if (dot_or_dot_dot(de->d_name)) + continue; + + is_dir = + de->d_type == DT_UNKNOWN ? -1 : + de->d_type == DT_DIR; + + r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev); + if (r < 0 && r != -ENOENT && ret == 0) + ret = r; + } + return ret; } int rm_rf(const char *path, RemoveFlags flags) { int fd, r; - struct statfs s; assert(path); @@ -284,9 +299,10 @@ int rm_rf(const char *path, RemoveFlags flags) { if (FLAGS_SET(flags, REMOVE_ROOT)) { if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { + struct statfs s; + if (statfs(path, &s) < 0) return -errno; - if (is_physical_fs(&s)) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Attempted to remove files from a disk file system under \"%s\", refusing.", @@ -314,3 +330,22 @@ int rm_rf(const char *path, RemoveFlags flags) { return r; } + +int rm_rf_child(int fd, const char *name, RemoveFlags flags) { + + /* Removes one specific child of the specified directory */ + + if (fd < 0) + return -EBADF; + + if (!filename_is_valid(name)) + return -EINVAL; + + if ((flags & (REMOVE_ROOT|REMOVE_MISSING_OK)) != 0) /* Doesn't really make sense here, we are not supposed to remove 'fd' anyway */ + return -EINVAL; + + if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME)) + return -EINVAL; + + return rm_rf_children_inner(fd, name, -1, flags, NULL); +} diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h index 40f0894c96d..577a2795e0f 100644 --- a/src/shared/rm-rf.h +++ b/src/shared/rm-rf.h @@ -23,7 +23,8 @@ int fstatat_harder(int dfd, int fstatat_flags, RemoveFlags remove_flags); -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev); +int rm_rf_child(int fd, const char *name, RemoveFlags flags); int rm_rf(const char *path, RemoveFlags flags); /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 79e879b4824..5cb6563b434 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -25,6 +25,7 @@ TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHand void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; void (*sym_Esys_Free)(void *ptr) = NULL; +TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData); TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; @@ -51,6 +52,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_Finalize), DLSYM_ARG(Esys_FlushContext), DLSYM_ARG(Esys_Free), + DLSYM_ARG(Esys_GetCapability), DLSYM_ARG(Esys_GetRandom), DLSYM_ARG(Esys_Initialize), DLSYM_ARG(Esys_Load), @@ -310,11 +312,93 @@ static int tpm2_make_primary( return 0; } +static int tpm2_get_best_pcr_bank( + ESYS_CONTEXT *c, + TPMI_ALG_HASH *ret) { + + _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; + TPMI_ALG_HASH hash = TPM2_ALG_SHA1; + bool found = false; + TPMI_YES_NO more; + TSS2_RC rc; + + rc = sym_Esys_GetCapability( + c, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + TPM2_CAP_PCRS, + 0, + 1, + &more, + &pcap); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); + + assert(pcap->capability == TPM2_CAP_PCRS); + + for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { + bool valid = true; + + /* As per + * https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a + * TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over + * it. */ + if (pcap->data.assignedPCR.pcrSelections[i].sizeofSelect < TPM2_PCRS_MAX/8) { + log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.", + strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash))); + continue; + } + + assert_cc(TPM2_PCRS_MAX % 8 == 0); + + /* It's not enought to check how many PCRs there are, we also need to check that the 24 are + * enabled for this bank. Otherwise this TPM doesn't qualify. */ + for (size_t j = 0; j < TPM2_PCRS_MAX/8; j++) + if (pcap->data.assignedPCR.pcrSelections[i].pcrSelect[j] != 0xFF) { + valid = false; + break; + } + + if (!valid) { + log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.", + strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash))); + continue; + } + + if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { + hash = TPM2_ALG_SHA256; + found = true; + break; + } + + if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1) + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate."); + + if (hash == TPM2_ALG_SHA256) + log_debug("TPM2 device supports SHA256 PCR banks, yay!"); + else { + assert(hash == TPM2_ALG_SHA1); + log_debug("TPM2 device lacks support for SHA256 PCR banks, falling back to SHA1 banks."); + } + + *ret = hash; + return 0; +} + static int tpm2_make_pcr_session( ESYS_CONTEXT *c, uint32_t pcr_mask, + uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ ESYS_TR *ret_session, - TPM2B_DIGEST **ret_policy_digest) { + TPM2B_DIGEST **ret_policy_digest, + TPMI_ALG_HASH *ret_pcr_bank) { static const TPMT_SYM_DEF symmetric = { .algorithm = TPM2_ALG_AES, @@ -327,7 +411,7 @@ static int tpm2_make_pcr_session( }; TPML_PCR_SELECTION pcr_selection = { .count = 1, - .pcrSelections[0].hash = TPM2_ALG_SHA256, + .pcrSelections[0].hash = TPM2_ALG_SHA256, /* overriden below, depending on TPM2 capabilities */ .pcrSelections[0].sizeofSelect = 3, .pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF, .pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF, @@ -342,6 +426,16 @@ static int tpm2_make_pcr_session( log_debug("Starting authentication session."); + if (pcr_bank != UINT16_MAX) + pcr_selection.pcrSelections[0].hash = pcr_bank; + else { + /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect + * that use that, but preferably use SHA256 */ + r = tpm2_get_best_pcr_bank(c, &pcr_selection.pcrSelections[0].hash); + if (r < 0) + return r; + } + rc = sym_Esys_StartAuthSession( c, ESYS_TR_NONE, @@ -412,6 +506,9 @@ static int tpm2_make_pcr_session( if (ret_policy_digest) *ret_policy_digest = TAKE_PTR(policy_digest); + if (ret_pcr_bank) + *ret_pcr_bank = pcr_selection.pcrSelections[0].hash; + r = 0; finish: @@ -427,7 +524,8 @@ int tpm2_seal( void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, - size_t *ret_pcr_hash_size) { + size_t *ret_pcr_hash_size, + uint16_t *ret_pcr_bank) { _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; @@ -439,6 +537,7 @@ int tpm2_seal( TPM2B_SENSITIVE_CREATE hmac_sensitive; ESYS_TR primary = ESYS_TR_NONE; TPM2B_PUBLIC hmac_template; + TPMI_ALG_HASH pcr_bank; size_t k, blob_size; usec_t start; TSS2_RC rc; @@ -450,6 +549,7 @@ int tpm2_seal( assert(ret_blob_size); assert(ret_pcr_hash); assert(ret_pcr_hash_size); + assert(ret_pcr_bank); assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */ @@ -478,7 +578,7 @@ int tpm2_seal( if (r < 0) return r; - r = tpm2_make_pcr_session(c.esys_context, pcr_mask, NULL, &policy_digest); + r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, NULL, &policy_digest, &pcr_bank); if (r < 0) goto finish; @@ -600,6 +700,7 @@ int tpm2_seal( *ret_blob_size = blob_size; *ret_pcr_hash = TAKE_PTR(hash); *ret_pcr_hash_size = policy_digest->size; + *ret_pcr_bank = pcr_bank; r = 0; @@ -611,6 +712,7 @@ finish: int tpm2_unseal( const char *device, uint32_t pcr_mask, + uint16_t pcr_bank, const void *blob, size_t blob_size, const void *known_policy_hash, @@ -670,7 +772,7 @@ int tpm2_unseal( if (r < 0) return r; - r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest); + r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, &session, &policy_digest, NULL); if (r < 0) goto finish; @@ -909,6 +1011,7 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) { int tpm2_make_luks2_json( int keyslot, uint32_t pcr_mask, + uint16_t pcr_bank, const void *blob, size_t blob_size, const void *policy_hash, @@ -951,6 +1054,7 @@ int tpm2_make_luks2_json( JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)), + JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))), JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)))); if (r < 0) return r; @@ -960,3 +1064,36 @@ int tpm2_make_luks2_json( return keyslot; } + +/* We want the helpers below to work also if TPM2 libs are not available, hence define these two defines if + * they are missing. */ +#ifndef TPM2_ALG_SHA256 +#define TPM2_ALG_SHA256 0xB +#endif + +#ifndef TPM2_ALG_SHA1 +#define TPM2_ALG_SHA1 0x4 +#endif + +int tpm2_pcr_bank_supported(uint16_t bank) { + /* For now, let's officially only support these two. We can extend this later on, should the need + * arise. */ + return IN_SET(bank, TPM2_ALG_SHA256, TPM2_ALG_SHA1); +} + +const char *tpm2_pcr_bank_to_string(uint16_t bank) { + /* Similar here, only support the two for now, we can always extend this later. */ + if (bank == TPM2_ALG_SHA256) + return "sha256"; + if (bank == TPM2_ALG_SHA1) + return "sha1"; + return NULL; +} + +int tpm2_pcr_bank_from_string(const char *bank) { + if (streq_ptr(bank, "sha256")) + return TPM2_ALG_SHA256; + if (streq_ptr(bank, "sha1")) + return TPM2_ALG_SHA1; + return -EINVAL; +} diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 9f60fef083d..8032afe7b00 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -15,6 +15,7 @@ extern TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR prim extern void (*sym_Esys_Finalize)(ESYS_CONTEXT **context); extern TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle); extern void (*sym_Esys_Free)(void *ptr); +extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData); extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes); extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion); extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle); @@ -33,8 +34,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz int dlopen_tpm2(void); -int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size); -int tpm2_unseal(const char *device, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size); +int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank); +int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size); #endif @@ -43,13 +44,17 @@ int tpm2_find_device_auto(int log_level, char **ret); int tpm2_parse_pcrs(const char *s, uint32_t *ret); -int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret); +int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret); #define TPM2_PCRS_MAX 24 /* Default to PCR 7 only */ #define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) +int tpm2_pcr_bank_supported(uint16_t bank); +const char *tpm2_pcr_bank_to_string(uint16_t bank); +int tpm2_pcr_bank_from_string(const char *bank); + typedef struct { uint32_t search_pcr_mask; const char *device; diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 3a53c16a724..4033947d3b8 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <malloc.h> -#include <sys/poll.h> +#include <poll.h> #include "alloc-util.h" #include "errno-util.h" diff --git a/src/test/meson.build b/src/test/meson.build index 07625ad38c1..e9204b150d0 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -250,6 +250,8 @@ tests += [ [['src/test/test-sysctl-util.c']], + [['src/test/test-import-util.c']], + [['src/test/test-user-record.c']], [['src/test/test-user-util.c']], diff --git a/src/test/test-import-util.c b/src/test/test-import-util.c new file mode 100644 index 00000000000..92ff0f56545 --- /dev/null +++ b/src/test/test-import-util.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "import-util.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +static void test_import_url_last_component_one(const char *input, const char *output, int ret) { + _cleanup_free_ char *s = NULL; + + assert_se(import_url_last_component(input, &s) == ret); + assert_se(streq_ptr(output, s)); +} + +static void test_import_url_last_component(void) { + test_import_url_last_component_one("https://foobar/waldo/quux", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/", "waldo", 0); + test_import_url_last_component_one("https://foobar/", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("https://foobar", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("https://foobar/waldo/quux?foo=bar", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar#piep", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/#piep", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux#piep", "quux", 0); + test_import_url_last_component_one("https://", NULL, -EINVAL); + test_import_url_last_component_one("", NULL, -EINVAL); + test_import_url_last_component_one(":", NULL, -EINVAL); + test_import_url_last_component_one(":/", NULL, -EINVAL); + test_import_url_last_component_one("x:/", NULL, -EINVAL); + test_import_url_last_component_one("x:y", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("x:y/z", "z", 0); +} + +static void test_import_url_change_suffix_one(const char *input, size_t n, const char *suffix, const char *output, int ret) { + _cleanup_free_ char *s = NULL; + + assert_se(import_url_change_suffix(input, n, suffix, &s) == ret); + assert_se(streq_ptr(output, s)); +} + +static void test_import_url_change_suffix(void) { + test_import_url_change_suffix_one("https://foobar/waldo/quux", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief#opopo", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 2, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff/", 2, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff?aa?bb##4", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0); + test_import_url_change_suffix_one("https://", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one(":", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one(":/", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("x:/", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("x:y", 0, "wuff", "x:y/wuff", 0); + test_import_url_change_suffix_one("x:y/z", 0, "wuff", "x:y/z/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 0, "wuff", "x:y/z/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 1, "wuff", "x:y/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 2, "wuff", "x:y/wuff", 0); +} + +int main(int argc, char *argv[]) { + + test_setup_logging(LOG_INFO); + + test_import_url_last_component(); + test_import_url_change_suffix(); + + return 0; +} diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 972423e2fa2..e22481b1b6e 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -89,7 +89,7 @@ static void test_keymaps(void) { #define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x)) static void dump_special_glyphs(void) { - assert_cc(SPECIAL_GLYPH_TOUCH + 1 == _SPECIAL_GLYPH_MAX); + assert_cc(SPECIAL_GLYPH_SPARKLES + 1 == _SPECIAL_GLYPH_MAX); log_info("/* %s */", __func__); @@ -120,6 +120,9 @@ static void dump_special_glyphs(void) { dump_glyph(SPECIAL_GLYPH_DEPRESSED_SMILEY); dump_glyph(SPECIAL_GLYPH_LOCK_AND_KEY); dump_glyph(SPECIAL_GLYPH_TOUCH); + dump_glyph(SPECIAL_GLYPH_RECYCLING); + dump_glyph(SPECIAL_GLYPH_DOWNLOAD); + dump_glyph(SPECIAL_GLYPH_SPARKLES); } int main(int argc, char *argv[]) { diff --git a/units/systemd-boot-update.service b/units/systemd-boot-update.service new file mode 100644 index 00000000000..61ff12762a5 --- /dev/null +++ b/units/systemd-boot-update.service @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Automatic Boot Loader Update +Documentation=man:bootctl(1) +DefaultDependencies=no +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target systemd-update-done.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=bootctl --no-variables --graceful update + +[Install] +WantedBy=sysinit.target |