diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 29d893e03b..2989d31d3c 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -32,6 +32,7 @@ #include "def.h" #include "device-nodes.h" #include "device-util.h" +#include "devnum-util.h" #include "discover-image.h" #include "dissect-image.h" #include "dm-util.h" @@ -73,19 +74,44 @@ /* how many times to wait for the device nodes to appear */ #define N_DEVICE_NODE_LIST_ATTEMPTS 10 -int probe_filesystem(const char *node, char **ret_fstype) { +int probe_filesystem_full(int fd, const char *path, char **ret_fstype) { /* Try to find device content type and return it in *ret_fstype. If nothing is found, * 0/NULL will be returned. -EUCLEAN will be returned for ambiguous results, and an * different error otherwise. */ #if HAVE_BLKID _cleanup_(blkid_free_probep) blkid_probe b = NULL; + _cleanup_free_ char *path_by_fd = NULL; + _cleanup_close_ int fd_close = -1; const char *fstype; int r; - errno = 0; - b = blkid_new_probe_from_filename(node); + assert(fd >= 0 || path); + assert(ret_fstype); + + if (fd < 0) { + fd_close = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd_close < 0) + return -errno; + + fd = fd_close; + } + + if (!path) { + r = fd_get_path(fd, &path_by_fd); + if (r < 0) + return r; + + path = path_by_fd; + } + + b = blkid_new_probe(); if (!b) + return -ENOMEM; + + errno = 0; + r = blkid_probe_set_device(b, fd, 0, 0); + if (r != 0) return errno_or_else(ENOMEM); blkid_probe_enable_superblocks(b, 1); @@ -97,16 +123,16 @@ int probe_filesystem(const char *node, char **ret_fstype) { goto not_found; if (r == -2) return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), - "Results ambiguous for partition %s", node); + "Results ambiguous for partition %s", path); if (r != 0) - return log_debug_errno(errno_or_else(EIO), "Failed to probe partition %s: %m", node); + return log_debug_errno(errno_or_else(EIO), "Failed to probe partition %s: %m", path); (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); if (fstype) { char *t; - log_debug("Probed fstype '%s' on partition %s.", fstype, node); + log_debug("Probed fstype '%s' on partition %s.", fstype, path); t = strdup(fstype); if (!t) @@ -117,7 +143,7 @@ int probe_filesystem(const char *node, char **ret_fstype) { } not_found: - log_debug("No type detected on partition %s", node); + log_debug("No type detected on partition %s", path); *ret_fstype = NULL; return 0; #else @@ -139,8 +165,8 @@ static int dissected_image_probe_filesystem(DissectedImage *m) { if (!p->found) continue; - if (!p->fstype && p->node) { - r = probe_filesystem(p->node, &p->fstype); + if (!p->fstype && p->mount_node_fd >= 0 && !p->decrypted_node) { + r = probe_filesystem_full(p->mount_node_fd, p->node, &p->fstype); if (r < 0 && r != -EUCLEAN) return r; } @@ -236,6 +262,7 @@ static void dissected_partition_done(DissectedPartition *p) { free(p->decrypted_fstype); free(p->decrypted_node); free(p->mount_options); + safe_close(p->mount_node_fd); *p = DISSECTED_PARTITION_NULL; } @@ -264,6 +291,48 @@ static int make_partition_devname( return asprintf(ret, "%s%s%i", whole_devname, need_p ? "p" : "", nr); } +static int open_partition(const char *node, bool is_partition, const LoopDevice *loop) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + _cleanup_close_ int fd = -1; + dev_t devnum; + int r; + + assert(node); + assert(loop); + + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + /* Check if the block device is a child of (or equivalent to) the originally provided one. */ + r = block_device_new_from_fd(fd, is_partition ? BLOCK_DEVICE_LOOKUP_WHOLE_DISK : 0, &dev); + if (r < 0) + return r; + + r = sd_device_get_devnum(dev, &devnum); + if (r < 0) + return r; + + if (loop->devno != devnum) + return -ENXIO; + + /* Also check diskseq. */ + if (loop->diskseq > 0) { + uint64_t diskseq; + + r = fd_get_diskseq(fd, &diskseq); + if (r < 0) + return r; + + if (loop->diskseq != diskseq) + return -ENXIO; + } + + log_debug("Opened %s (fd=%i, whole_block_devnum=" DEVNUM_FORMAT_STR ", diskseq=%" PRIu64 ").", + node, fd, DEVNUM_FORMAT_VAL(loop->devno), loop->diskseq); + return TAKE_FD(fd); +} + static int dissect_image( DissectedImage *m, int fd, @@ -365,6 +434,13 @@ static int dissect_image( if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL; const char *fstype = NULL, *options = NULL; + _cleanup_close_ int mount_node_fd = -1; + + if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) { + mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop); + if (mount_node_fd < 0) + return mount_node_fd; + } /* OK, we have found a file system, that's our root partition then. */ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); @@ -406,6 +482,7 @@ static int dissect_image( .fstype = TAKE_PTR(t), .node = TAKE_PTR(n), .mount_options = TAKE_PTR(o), + .mount_node_fd = TAKE_FD(mount_node_fd), .offset = 0, .size = UINT64_MAX, }; @@ -778,6 +855,7 @@ static int dissect_image( if (designator != _PARTITION_DESIGNATOR_INVALID) { _cleanup_free_ char *t = NULL, *o = NULL, *l = NULL; + _cleanup_close_ int mount_node_fd = -1; const char *options = NULL; if (m->partitions[designator].found) { @@ -793,6 +871,12 @@ static int dissect_image( dissected_partition_done(m->partitions + designator); } + if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) { + mount_node_fd = open_partition(node, /* is_partition = */ true, m->loop); + if (mount_node_fd < 0) + return mount_node_fd; + } + if (fstype) { t = strdup(fstype); if (!t) @@ -823,6 +907,7 @@ static int dissect_image( .label = TAKE_PTR(l), .uuid = id, .mount_options = TAKE_PTR(o), + .mount_node_fd = TAKE_FD(mount_node_fd), .offset = (uint64_t) start * 512, .size = (uint64_t) size * 512, }; @@ -851,6 +936,7 @@ static int dissect_image( break; case 0xEA: { /* Boot Loader Spec extended $BOOT partition */ + _cleanup_close_ int mount_node_fd = -1; _cleanup_free_ char *o = NULL; sd_id128_t id = SD_ID128_NULL; const char *sid, *options = NULL; @@ -859,6 +945,12 @@ static int dissect_image( if (m->partitions[PARTITION_XBOOTLDR].found) continue; + if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) { + mount_node_fd = open_partition(node, /* is_partition = */ true, m->loop); + if (mount_node_fd < 0) + return mount_node_fd; + } + sid = blkid_partition_get_uuid(pp); if (sid) (void) sd_id128_from_string(sid, &id); @@ -879,6 +971,7 @@ static int dissect_image( .node = TAKE_PTR(node), .uuid = id, .mount_options = TAKE_PTR(o), + .mount_node_fd = TAKE_FD(mount_node_fd), .offset = (uint64_t) start * 512, .size = (uint64_t) size * 512, }; @@ -1048,9 +1141,16 @@ static int dissect_image( /* If we didn't find a generic node, then we can't fix this up either */ if (generic_node) { + _cleanup_close_ int mount_node_fd = -1; _cleanup_free_ char *o = NULL; const char *options; + if (FLAGS_SET(flags, DISSECT_IMAGE_OPEN_PARTITION_DEVICES)) { + mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop); + if (mount_node_fd < 0) + return mount_node_fd; + } + options = mount_options_from_designator(mount_options, PARTITION_ROOT); if (options) { o = strdup(options); @@ -1068,6 +1168,7 @@ static int dissect_image( .node = TAKE_PTR(generic_node), .uuid = generic_uuid, .mount_options = TAKE_PTR(o), + .mount_node_fd = TAKE_FD(mount_node_fd), .offset = UINT64_MAX, .size = UINT64_MAX, }; @@ -1319,12 +1420,13 @@ static int mount_partition( assert(m); assert(where); + if (m->mount_node_fd < 0) + return 0; + /* Use decrypted node and matching fstype if available, otherwise use the original device */ - node = m->decrypted_node ?: m->node; + node = FORMAT_PROC_FD_PATH(m->mount_node_fd); fstype = m->decrypted_node ? m->decrypted_fstype: m->fstype; - if (!m->found || !node) - return 0; if (!fstype) return -EAFNOSUPPORT; @@ -1724,6 +1826,7 @@ static int decrypt_partition( _cleanup_free_ char *node = NULL, *name = NULL; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_close_ int fd = -1; int r; assert(m); @@ -1767,12 +1870,17 @@ static int decrypt_partition( return r == -EPERM ? -EKEYREJECTED : r; } + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open %s: %m", node); + d->decrypted[d->n_decrypted++] = (DecryptedPartition) { .name = TAKE_PTR(name), .device = TAKE_PTR(cd), }; m->decrypted_node = TAKE_PTR(node); + close_and_replace(m->mount_node_fd, fd); return 0; } @@ -2008,6 +2116,7 @@ static int verity_partition( _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(dm_deferred_remove_cleanp) char *restore_deferred_remove = NULL; _cleanup_free_ char *node = NULL, *name = NULL; + _cleanup_close_ int mount_node_fd = -1; int r; assert(m); @@ -2068,8 +2177,20 @@ static int verity_partition( * 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++) { + _cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL; + _cleanup_close_ int fd = -1; + /* First, check if the device already exists. */ + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0 && !ERRNO_IS_DEVICE_ABSENT(errno)) + return log_debug_errno(errno, "Failed to open verity device %s: %m", node); + if (fd >= 0) + goto check; /* The device already exists. Let's check it. */ + + /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */ r = do_crypt_activate_verity(cd, name, verity); + if (r >= 0) + goto try_open; /* The device is activated. Let's open it. */ /* 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 fall back to activating the device with a unique name. @@ -2077,50 +2198,74 @@ static int verity_partition( * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */ if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) break; - if (r < 0 && !IN_SET(r, - -EEXIST, /* Volume is already open and ready to be used */ - -EBUSY, /* Volume is being opened but not ready, crypt_init_by_name can fetch details */ - -ENODEV /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */)) - return r; - if (IN_SET(r, -EEXIST, -EBUSY)) { - _cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL; + if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */ + goto try_again; + if (!IN_SET(r, + -EEXIST, /* Volume has already been opened and ready to be used. */ + -EBUSY /* Volume is being opened but not ready, crypt_init_by_name() can fetch details. */)) + return log_debug_errno(r, "Failed to activate verity device %s: %m", node); - 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 activation returns EBUSY there might be no deferred removal to cancel, that's fine */ - if (r < 0 && r != -ENXIO) - return log_debug_errno(r, "Disabling automated deferred removal for verity device %s failed: %m", node); - if (r >= 0) { - restore_deferred_remove = strdup(name); - if (!restore_deferred_remove) - return -ENOMEM; - } - } + check: + 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); + /* -EBUSY and -ENXIO: the device has already been removed or being removed. We cannot + * use the device, try to open again. See target_message() in drivers/md/dm-ioctl.c + * and dm_cancel_deferred_remove() in drivers/md/dm.c */ + if (IN_SET(r, -EBUSY, -ENXIO)) + goto try_again; + if (r < 0) + return log_debug_errno(r, "Failed to disable automated deferred removal for verity device %s: %m", node); - r = verity_can_reuse(verity, 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)) + restore_deferred_remove = strdup(name); + if (!restore_deferred_remove) + return log_oom_debug(); + } + + r = verity_can_reuse(verity, 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)) + break; + if (IN_SET(r, + -ENOENT, /* Removed?? */ + -EBUSY, /* Volume is being opened but not ready, crypt_init_by_name() can fetch details. */ + -ENODEV /* Volume is being opened but not ready, crypt_init_by_name() would fail, try to open again. */ )) + goto try_again; + if (r < 0) + return log_debug_errno(r, "Failed to check if existing verity device %s can be reused: %m", node); + + if (fd < 0) { + /* devmapper might say that the device exists, but the devlink might not yet have been + * created. Check and wait for the udev event in that case. */ + r = device_wait_for_devlink(node, "block", verity_timeout(), NULL); + /* Fallback to activation with a unique device if it's taking too long */ + if (r == -ETIMEDOUT && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) break; - if (r < 0 && !IN_SET(r, -ENODEV, -ENOENT, -EBUSY)) - return log_debug_errno(r, "Checking whether existing verity device %s can be reused failed: %m", node); - if (r >= 0) { - /* devmapper might say that the device exists, but the devlink might not yet have been - * created. Check and wait for the udev event in that case. */ - r = device_wait_for_devlink(node, "block", verity_timeout(), NULL); - /* Fallback to activation with a unique device if it's taking too long */ - if (r == -ETIMEDOUT && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) - break; - if (r < 0) - return r; + if (r < 0) + return log_debug_errno(r, "Failed to wait device node symlink %s: %m", node); + } - crypt_free_and_replace(cd, existing_cd); + try_open: + if (fd < 0) { + /* Now, the device is activated and devlink is created. Let's open it. */ + fd = open(node, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (!ERRNO_IS_DEVICE_ABSENT(errno)) + return log_debug_errno(errno, "Failed to open verity device %s: %m", node); + + /* The device has already been removed?? */ + goto try_again; } } - if (r >= 0) - goto success; - /* Device is being opened by another process, but it has not finished yet, yield for 2ms */ + mount_node_fd = TAKE_FD(fd); + if (existing_cd) + crypt_free_and_replace(cd, existing_cd); + + goto success; + + try_again: + /* Device is being removed by another process. Let's wait for a while. */ (void) usleep(2 * USEC_PER_MSEC); } @@ -2149,6 +2294,7 @@ success: }; m->decrypted_node = TAKE_PTR(node); + close_and_replace(m->mount_node_fd, mount_node_fd); return 0; } @@ -2205,8 +2351,8 @@ int dissected_image_decrypt( return r; } - if (!p->decrypted_fstype && p->decrypted_node) { - r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype); + if (!p->decrypted_fstype && p->mount_node_fd >= 0 && p->decrypted_node) { + r = probe_filesystem_full(p->mount_node_fd, p->decrypted_node, &p->decrypted_fstype); if (r < 0 && r != -EUCLEAN) return r; } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 581c607e51..8007b544e7 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -29,6 +29,7 @@ struct DissectedPartition { char *decrypted_node; char *decrypted_fstype; char *mount_options; + int mount_node_fd; uint64_t size; uint64_t offset; }; @@ -37,6 +38,7 @@ struct DissectedPartition { ((DissectedPartition) { \ .partno = -1, \ .architecture = _ARCHITECTURE_INVALID, \ + .mount_node_fd = -1, \ }) typedef enum PartitionDesignator { @@ -207,8 +209,10 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_GROWFS = 1 << 18, /* Grow file systems in partitions marked for that to the size of the partitions after mount */ DISSECT_IMAGE_MOUNT_IDMAPPED = 1 << 19, /* Mount mounts with kernel 5.12-style userns ID mapping, if file system type doesn't support uid=/gid= */ DISSECT_IMAGE_MANAGE_PARTITION_DEVICES = 1 << 20, /* Manage partition devices, e.g. probe each partition in more detail */ - DISSECT_IMAGE_BLOCK_DEVICE = DISSECT_IMAGE_MANAGE_PARTITION_DEVICES, - DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 21, /* Don't insist that the extension-release file name matches the image name */ + DISSECT_IMAGE_OPEN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions */ + DISSECT_IMAGE_BLOCK_DEVICE = DISSECT_IMAGE_MANAGE_PARTITION_DEVICES | + DISSECT_IMAGE_OPEN_PARTITION_DEVICES, + DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */ } DissectImageFlags; struct DissectedImage { @@ -263,7 +267,10 @@ MountOptions* mount_options_free_all(MountOptions *options); DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all); const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator); -int probe_filesystem(const char *node, char **ret_fstype); +int probe_filesystem_full(int fd, const char *path, char **ret_fstype); +static inline int probe_filesystem(const char *path, char **ret_fstype) { + return probe_filesystem_full(-1, path, ret_fstype); +} int dissect_image_file( const char *path, const VeritySettings *verity,