X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fsystemd.git;a=blobdiff_plain;f=src%2Fshared%2Fdissect-image.c;h=d3f183b50c54cb3ac94034a86d761253dca1bb72;hp=fdf4d481f6545805d93560244c110c507fc69fa3;hb=c53da7ed02a5d732c9449f79c19675b90a6032e3;hpb=55aacd502b0a80c21aee50bdd0f325d378250261 diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index fdf4d481f65..d3f183b50c5 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -33,8 +33,10 @@ #include "hexdecoct.h" #include "hostname-util.h" #include "id128-util.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "namespace-util.h" #include "nulstr-util.h" #include "os-util.h" #include "path-util.h" @@ -213,22 +215,18 @@ static int wait_for_partitions_to_appear( break; r = -errno; if (r == -EINVAL) { - struct loop_info64 info; + /* If we are running on a block device that has partition scanning off, return an + * explicit recognizable error about this, so that callers can generate a proper + * message explaining the situation. */ - /* If we are running on a loop device that has partition scanning off, return - * an explicit recognizable error about this, so that callers can generate a - * proper message explaining the situation. */ - - if (ioctl(fd, LOOP_GET_STATUS64, &info) >= 0) { -#if HAVE_VALGRIND_MEMCHECK_H - /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */ - VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info)); -#endif + r = blockdev_partscan_enabled(fd); + if (r < 0) + return r; + if (r == 0) + return log_debug_errno(EPROTONOSUPPORT, + "Device is a loop device and partition scanning is off!"); - if ((info.lo_flags & LO_FLAGS_PARTSCAN) == 0) - return log_debug_errno(EPROTONOSUPPORT, - "Device is a loop device and partition scanning is off!"); - } + return -EINVAL; /* original error */ } if (r != -EBUSY) return r; @@ -308,6 +306,7 @@ int dissect_image( const void *root_hash, size_t root_hash_size, const char *verity_data, + const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret) { @@ -400,8 +399,8 @@ int dissect_image( (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { - _cleanup_free_ char *t = NULL, *n = NULL; - const char *fstype = NULL; + _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL; + const char *fstype = NULL, *options = NULL; /* OK, we have found a file system, that's our root partition then. */ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); @@ -420,6 +419,13 @@ int dissect_image( m->verity = root_hash && verity_data; m->can_verity = !!verity_data; + options = mount_options_from_part(mount_options, 0); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_ROOT] = (DissectedPartition) { .found = true, .rw = !m->verity, @@ -427,6 +433,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .fstype = TAKE_PTR(t), .node = TAKE_PTR(n), + .mount_options = TAKE_PTR(o), }; m->encrypted = streq_ptr(fstype, "crypto_LUKS"); @@ -691,7 +698,8 @@ int dissect_image( } if (designator != _PARTITION_DESIGNATOR_INVALID) { - _cleanup_free_ char *t = NULL, *n = NULL; + _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL; + const char *options = NULL; /* First one wins */ if (m->partitions[designator].found) @@ -707,6 +715,13 @@ int dissect_image( if (!n) return -ENOMEM; + options = mount_options_from_part(mount_options, nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[designator] = (DissectedPartition) { .found = true, .partno = nr, @@ -715,6 +730,7 @@ int dissect_image( .node = TAKE_PTR(n), .fstype = TAKE_PTR(t), .uuid = id, + .mount_options = TAKE_PTR(o), }; } @@ -740,9 +756,9 @@ int dissect_image( break; case 0xEA: { /* Boot Loader Spec extended $BOOT partition */ - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *n = NULL, *o = NULL; sd_id128_t id = SD_ID128_NULL; - const char *sid; + const char *sid, *options = NULL; /* First one wins */ if (m->partitions[PARTITION_XBOOTLDR].found) @@ -756,6 +772,13 @@ int dissect_image( if (!n) return -ENOMEM; + options = mount_options_from_part(mount_options, nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_XBOOTLDR] = (DissectedPartition) { .found = true, .partno = nr, @@ -763,6 +786,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .node = TAKE_PTR(n), .uuid = id, + .mount_options = TAKE_PTR(o), }; break; @@ -785,6 +809,8 @@ int dissect_image( zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); } else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) { + _cleanup_free_ char *o = NULL; + const char *options = NULL; /* If the root has was set, then we won't fallback to a generic node, because the root hash * decides */ @@ -800,6 +826,13 @@ int dissect_image( if (multiple_generic) return -ENOTUNIQ; + options = mount_options_from_part(mount_options, generic_nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_ROOT] = (DissectedPartition) { .found = true, .rw = generic_rw, @@ -807,6 +840,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .node = TAKE_PTR(generic_node), .uuid = generic_uuid, + .mount_options = TAKE_PTR(o), }; } } @@ -869,6 +903,7 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { free(m->partitions[i].node); free(m->partitions[i].decrypted_fstype); free(m->partitions[i].decrypted_node); + free(m->partitions[i].mount_options); } free(m->hostname); @@ -1008,6 +1043,10 @@ static int mount_partition( return -ENOMEM; } + if (!isempty(m->mount_options)) + if (!strextend_with_separator(&options, ",", m->mount_options, NULL)) + return -ENOMEM; + r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); if (r < 0) return r; @@ -1140,8 +1179,9 @@ static int make_dm_name_and_node(const void *original_node, const char *suffix, base = strrchr(original_node, '/'); if (!base) - return -EINVAL; - base++; + base = original_node; + else + base++; if (isempty(base)) return -EINVAL; @@ -1217,6 +1257,50 @@ static int decrypt_partition( return 0; } +static int verity_can_reuse(const void *root_hash, size_t root_hash_size, bool has_sig, const char *name, struct crypt_device **ret_cd) { + /* If the same volume was already open, check that the root hashes match, and reuse it if they do */ + _cleanup_free_ char *root_hash_existing = NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + struct crypt_params_verity crypt_params = {}; + size_t root_hash_existing_size = root_hash_size; + int r; + + assert(ret_cd); + + r = crypt_init_by_name(&cd, name); + if (r < 0) + return log_debug_errno(r, "Error opening verity device, crypt_init_by_name failed: %m"); + r = crypt_get_verity_info(cd, &crypt_params); + if (r < 0) + return log_debug_errno(r, "Error opening verity device, crypt_get_verity_info failed: %m"); + root_hash_existing = malloc0(root_hash_size); + if (!root_hash_existing) + return -ENOMEM; + r = crypt_volume_key_get(cd, CRYPT_ANY_SLOT, root_hash_existing, &root_hash_existing_size, NULL, 0); + if (r < 0) + return log_debug_errno(r, "Error opening verity device, crypt_volume_key_get failed: %m"); + if (root_hash_size != root_hash_existing_size || memcmp(root_hash_existing, root_hash, root_hash_size) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Error opening verity device, it already exists but root hashes are different."); +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + /* Ensure that, if signatures are supported, we only reuse the device if the previous mount + * used the same settings, so that a previous unsigned mount will not be reused if the user + * asks to use signing for the new one, and viceversa. */ + if (has_sig != !!(crypt_params.flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Error opening verity device, it already exists but signature settings are not the same."); +#endif + + *ret_cd = TAKE_PTR(cd); + return 0; +} + +static inline void dm_deferred_remove_clean(char *name) { + if (!name) + return; + (void) crypt_deactivate_by_name(NULL, name, CRYPT_DEACTIVATE_DEFERRED); + free(name); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean); + static int verity_partition( DissectedPartition *m, DissectedPartition *v, @@ -1229,8 +1313,9 @@ static int verity_partition( DissectImageFlags flags, DecryptedImage *d) { - _cleanup_free_ char *node = NULL, *name = NULL; + _cleanup_free_ char *node = NULL, *name = NULL, *hash_sig_from_file = NULL; _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL; int r; assert(m); @@ -1249,12 +1334,23 @@ static int verity_partition( return 0; } - r = make_dm_name_and_node(m->node, "-verity", &name, &node); + if (FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) { + /* Use the roothash, which is unique per volume, as the device node name, so that it can be reused */ + _cleanup_free_ char *root_hash_encoded = NULL; + root_hash_encoded = hexmem(root_hash, root_hash_size); + if (!root_hash_encoded) + return -ENOMEM; + r = make_dm_name_and_node(root_hash_encoded, "-verity", &name, &node); + } else + r = make_dm_name_and_node(m->node, "-verity", &name, &node); if (r < 0) return r; - if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) - return -ENOMEM; + if (!root_hash_sig && root_hash_sig_path) { + r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, &hash_sig_from_file, &root_hash_sig_size); + if (r < 0) + return r; + } r = crypt_init(&cd, verity_data ?: v->node); if (r < 0) @@ -1270,27 +1366,69 @@ static int verity_partition( if (r < 0) return r; - if (root_hash_sig || root_hash_sig_path) { + if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) + return -ENOMEM; + + /* If activating fails because the device already exists, check the metadata and reuse it if it matches. + * In case of ENODEV/ENOENT, which can happen if another process is activating at the exact same time, + * retry a few times before giving up. */ + for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) { + if (root_hash_sig || hash_sig_from_file) { #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY - if (root_hash_sig) - r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, root_hash_sig, root_hash_sig_size, CRYPT_ACTIVATE_READONLY); - else { - _cleanup_free_ char *hash_sig = NULL; - size_t hash_sig_size; + r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, root_hash_sig ?: hash_sig_from_file, root_hash_sig_size, CRYPT_ACTIVATE_READONLY); +#else + r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()"); +#endif + } else + r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY); + /* libdevmapper can return EINVAL when the device is already in the activation stage. + * There's no way to distinguish this situation from a genuine error due to invalid + * parameters, so immediately fallback to activating the device with a unique name. + * Improvements in libcrypsetup can ensure this never happens: https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */ + if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) + return verity_partition(m, v, root_hash, root_hash_size, verity_data, NULL, root_hash_sig ?: hash_sig_from_file, root_hash_sig_size, flags & ~DISSECT_IMAGE_VERITY_SHARE, d); + if (!IN_SET(r, 0, -EEXIST, -ENODEV)) + return r; + if (r == -EEXIST) { + struct crypt_device *existing_cd = NULL; - r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, &hash_sig, &hash_sig_size); - if (r < 0) - return r; + if (!restore_deferred_remove){ + /* To avoid races, disable automatic removal on umount while setting up the new device. Restore it on failure. */ + r = dm_deferred_remove_cancel(name); + if (r < 0) + return log_debug_errno(r, "Disabling automated deferred removal for verity device %s failed: %m", node); + restore_deferred_remove = strdup(name); + if (!restore_deferred_remove) + return -ENOMEM; + } - r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY); + r = verity_can_reuse(root_hash, root_hash_size, !!root_hash_sig || !!hash_sig_from_file, name, &existing_cd); + /* Same as above, -EINVAL can randomly happen when it actually means -EEXIST */ + if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) + return verity_partition(m, v, root_hash, root_hash_size, verity_data, NULL, root_hash_sig ?: hash_sig_from_file, root_hash_sig_size, flags & ~DISSECT_IMAGE_VERITY_SHARE, d); + if (!IN_SET(r, 0, -ENODEV, -ENOENT)) + return log_debug_errno(r, "Checking whether existing verity device %s can be reused failed: %m", node); + if (r == 0) { + if (cd) + crypt_free(cd); + cd = existing_cd; + } } -#else - r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()"); -#endif - } else - r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY); - if (r < 0) - return r; + if (r == 0) + break; + } + + /* Sanity check: libdevmapper is known to report that the device already exists and is active, + * but it's actually not there, so the later filesystem probe or mount would fail. */ + if (r == 0) + r = access(node, F_OK); + /* An existing verity device was reported by libcryptsetup/libdevmapper, but we can't use it at this time. + * Fall back to activating it with a unique device name. */ + if (r != 0 && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) + return verity_partition(m, v, root_hash, root_hash_size, verity_data, NULL, root_hash_sig ?: hash_sig_from_file, root_hash_sig_size, flags & ~DISSECT_IMAGE_VERITY_SHARE, d); + + /* Everything looks good and we'll be able to mount the device, so deferred remove will be re-enabled at that point. */ + restore_deferred_remove = mfree(restore_deferred_remove); d->decrypted[d->n_decrypted].name = TAKE_PTR(name); d->decrypted[d->n_decrypted].device = TAKE_PTR(cd); @@ -1357,7 +1495,7 @@ int dissected_image_decrypt( k = PARTITION_VERITY_OF(i); if (k >= 0) { - r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags, d); + r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags | DISSECT_IMAGE_VERITY_SHARE, d); if (r < 0) return r; } @@ -1437,7 +1575,7 @@ int decrypted_image_relinquish(DecryptedImage *d) { if (p->relinquished) continue; - r = dm_deferred_remove(p->name); + r = crypt_deactivate_by_name(NULL, p->name, CRYPT_DEACTIVATE_DEFERRED); if (r < 0) return log_debug_errno(r, "Failed to mark %s for auto-removal: %m", p->name); @@ -1720,6 +1858,7 @@ int dissect_image_and_warn( const void *root_hash, size_t root_hash_size, const char *verity_data, + const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret) { @@ -1734,7 +1873,7 @@ int dissect_image_and_warn( name = buffer; } - r = dissect_image(fd, root_hash, root_hash_size, verity_data, flags, ret); + r = dissect_image(fd, root_hash, root_hash_size, verity_data, mount_options, flags, ret); switch (r) { @@ -1781,6 +1920,102 @@ bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_ return k >= 0 && image->partitions[k].found; } +MountOptions* mount_options_free_all(MountOptions *options) { + MountOptions *m; + + while ((m = options)) { + LIST_REMOVE(mount_options, options, m); + free(m->options); + free(m); + } + + return NULL; +} + +const char* mount_options_from_part(const MountOptions *options, unsigned int partition_number) { + MountOptions *m; + + LIST_FOREACH(mount_options, m, (MountOptions *)options) + if (partition_number == m->partition_number && !isempty(m->options)) + return m->options; + + return NULL; +} + +int mount_image_privately_interactively( + const char *image, + DissectImageFlags flags, + char **ret_directory, + LoopDevice **ret_loop_device, + DecryptedImage **ret_decrypted_image) { + + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; + _cleanup_(rmdir_and_freep) char *created_dir = NULL; + _cleanup_free_ char *temp = NULL; + int r; + + /* Mounts an OS image at a temporary place, inside a newly created mount namespace of our own. This + * is used by tools such as systemd-tmpfiles or systemd-firstboot to operate on some disk image + * easily. */ + + assert(image); + assert(ret_directory); + assert(ret_loop_device); + assert(ret_decrypted_image); + + r = tempfn_random_child(NULL, program_invocation_short_name, &temp); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary mount directory: %m"); + + r = loop_device_make_by_path( + image, + FLAGS_SET(flags, DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, + FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, + &d); + if (r < 0) + return log_error_errno(r, "Failed to set up loopback device: %m"); + + r = dissect_image_and_warn(d->fd, image, NULL, 0, NULL, NULL, flags, &dissected_image); + if (r < 0) + return r; + + r = dissected_image_decrypt_interactively(dissected_image, NULL, NULL, 0, NULL, NULL, NULL, 0, flags, &decrypted_image); + if (r < 0) + return r; + + r = detach_mount_namespace(); + if (r < 0) + return log_error_errno(r, "Failed to detach mount namespace: %m"); + + r = mkdir_p(temp, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create mount point: %m"); + + created_dir = TAKE_PTR(temp); + + r = dissected_image_mount(dissected_image, created_dir, UID_INVALID, flags); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed: %m"); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + if (decrypted_image) { + r = decrypted_image_relinquish(decrypted_image); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM devices: %m"); + } + + loop_device_relinquish(d); + + *ret_directory = TAKE_PTR(created_dir); + *ret_loop_device = TAKE_PTR(d); + *ret_decrypted_image = TAKE_PTR(decrypted_image); + + return 0; +} + static const char *const partition_designator_table[] = { [PARTITION_ROOT] = "root", [PARTITION_ROOT_SECONDARY] = "root-secondary",