}
#endif
+#if HAVE_BLKID
+static int dissected_image_new(const char *path, DissectedImage **ret) {
+ _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
+ _cleanup_free_ char *name = NULL;
+ int r;
+
+ assert(ret);
+
+ if (path) {
+ _cleanup_free_ char *filename = NULL;
+
+ r = path_extract_filename(path, &filename);
+ if (r < 0)
+ return r;
+
+ r = raw_strip_suffixes(filename, &name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(name)) {
+ log_debug("Image name %s is not valid, ignoring.", strna(name));
+ name = mfree(name);
+ }
+ }
+
+ m = new(DissectedImage, 1);
+ if (!m)
+ return -ENOMEM;
+
+ *m = (DissectedImage) {
+ .has_init_system = -1,
+ .image_name = TAKE_PTR(name),
+ };
+
+ for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++)
+ m->partitions[i] = DISSECTED_PARTITION_NULL;
+
+ *ret = TAKE_PTR(m);
+ return 0;
+}
+#endif
+
static void dissected_partition_done(DissectedPartition *p) {
assert(p);
free(p->decrypted_node);
free(p->mount_options);
- *p = (DissectedPartition) {
- .partno = -1,
- .architecture = _ARCHITECTURE_INVALID,
- };
+ *p = DISSECTED_PARTITION_NULL;
}
#if HAVE_BLKID
if (r != 0)
return errno_or_else(EIO);
- m = new(DissectedImage, 1);
- if (!m)
- return -ENOMEM;
-
- *m = (DissectedImage) {
- .has_init_system = -1,
- };
-
- if (image_path) {
- _cleanup_free_ char *extracted_filename = NULL, *name_stripped = NULL;
-
- r = path_extract_filename(image_path, &extracted_filename);
- if (r < 0)
- return r;
-
- r = raw_strip_suffixes(extracted_filename, &name_stripped);
- if (r < 0)
- return r;
-
- if (!image_name_is_valid(name_stripped))
- log_debug("Image name %s is not valid, ignoring.", strna(name_stripped));
- else
- m->image_name = TAKE_PTR(name_stripped);
- }
+ r = dissected_image_new(image_path, &m);
+ if (r < 0)
+ return r;
if ((!(flags & DISSECT_IMAGE_GPT_ONLY) &&
(flags & DISSECT_IMAGE_GENERIC_ROOT)) ||
if (!m)
return NULL;
+ /* First, clear dissected partitions. */
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++)
dissected_partition_done(m->partitions + i);
+ /* Second, free decrypted images. This must be after dissected_partition_done(), as freeing
+ * DecryptedImage may try to deactivate partitions. */
+ decrypted_image_unref(m->decrypted_image);
+
+ /* Third, unref LoopDevice. This must be called after the above two, as freeing LoopDevice may try to
+ * remove existing partitions on the loopback block device. */
+ loop_device_unref(m->loop);
+
free(m->image_name);
free(m->hostname);
strv_free(m->machine_info);
}
#if HAVE_LIBCRYPTSETUP
-typedef struct DecryptedPartition {
+struct DecryptedPartition {
struct crypt_device *device;
char *name;
bool relinquished;
-} DecryptedPartition;
+};
+#endif
+
+typedef struct DecryptedPartition DecryptedPartition;
struct DecryptedImage {
+ unsigned n_ref;
DecryptedPartition *decrypted;
size_t n_decrypted;
};
-#endif
-DecryptedImage* decrypted_image_unref(DecryptedImage* d) {
+static DecryptedImage* decrypted_image_free(DecryptedImage *d) {
#if HAVE_LIBCRYPTSETUP
int r;
DecryptedPartition *p = d->decrypted + i;
if (p->device && p->name && !p->relinquished) {
- r = sym_crypt_deactivate_by_name(p->device, p->name, 0);
+ /* Let's deactivate lazily, as the dm volume may be already/still used by other processes. */
+ r = sym_crypt_deactivate_by_name(p->device, p->name, CRYPT_DEACTIVATE_DEFERRED);
if (r < 0)
log_debug_errno(r, "Failed to deactivate encrypted partition %s", p->name);
}
return NULL;
}
+DEFINE_TRIVIAL_REF_UNREF_FUNC(DecryptedImage, decrypted_image, decrypted_image_free);
+
#if HAVE_LIBCRYPTSETUP
+static int decrypted_image_new(DecryptedImage **ret) {
+ _cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL;
+
+ assert(ret);
+
+ d = new(DecryptedImage, 1);
+ if (!d)
+ return -ENOMEM;
+
+ *d = (DecryptedImage) {
+ .n_ref = 1,
+ };
+
+ *ret = TAKE_PTR(d);
+ return 0;
+}
static int make_dm_name_and_node(const void *original_node, const char *suffix, char **ret_name, char **ret_node) {
_cleanup_free_ char *name = NULL, *node = NULL;
CRYPT_ACTIVATE_READONLY);
}
+static usec_t verity_timeout(void) {
+ usec_t t = 100 * USEC_PER_MSEC;
+ const char *e;
+ int r;
+
+ /* On slower machines, like non-KVM vm, setting up device may take a long time.
+ * Let's make the timeout configurable. */
+
+ e = getenv("SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC");
+ if (!e)
+ return t;
+
+ r = parse_sec(e, &t);
+ if (r < 0)
+ log_debug_errno(r,
+ "Failed to parse timeout specified in $SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC, "
+ "using the default timeout (%s).",
+ FORMAT_TIMESPAN(t, USEC_PER_MSEC));
+
+ return t;
+}
+
static int verity_partition(
PartitionDesignator designator,
DissectedPartition *m,
* 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(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d);
- if (!IN_SET(r,
- 0, /* Success */
- -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 */))
+ 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)) {
- struct crypt_device *existing_cd = NULL;
+ _cleanup_(sym_crypt_freep) struct crypt_device *existing_cd = NULL;
if (!restore_deferred_remove){
/* To avoid races, disable automatic removal on umount while setting up the new device. Restore it on failure. */
/* 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) {
+ if (r >= 0) {
restore_deferred_remove = strdup(name);
if (!restore_deferred_remove)
return -ENOMEM;
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))
- return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d);
- if (!IN_SET(r, 0, -ENODEV, -ENOENT, -EBUSY))
+ 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) {
- usec_t timeout_usec = 100 * USEC_PER_MSEC;
- const char *e;
-
- /* On slower machines, like non-KVM vm, setting up device may take a long time.
- * Let's make the timeout configurable. */
- e = getenv("SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC");
- if (e) {
- usec_t t;
-
- r = parse_sec(e, &t);
- if (r < 0)
- log_debug_errno(r,
- "Failed to parse timeout specified in $SYSTEMD_DISSECT_VERITY_TIMEOUT_SEC, "
- "using the default timeout (%s).",
- FORMAT_TIMESPAN(timeout_usec, USEC_PER_MSEC));
- else
- timeout_usec = t;
- }
-
+ 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", timeout_usec, NULL);
+ 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)
+ if (r == -ETIMEDOUT && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
if (r < 0)
return r;
- if (cd)
- sym_crypt_free(cd);
- cd = existing_cd;
+ crypt_free_and_replace(cd, existing_cd);
}
}
- if (r == 0)
- break;
+ if (r >= 0)
+ goto success;
/* Device is being opened by another process, but it has not finished yet, yield for 2ms */
(void) usleep(2 * USEC_PER_MSEC);
}
- /* 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))
+ /* All trials failed or a conflicting verity device exists. Let's try to activate with a unique name. */
+ if (FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) {
+ /* Before trying to activate with unique name, we need to free crypt_device object.
+ * Otherwise, we get error from libcryptsetup like the following:
+ * ------
+ * systemd[1234]: Cannot use device /dev/loop5 which is in use (already mapped or mounted).
+ * ------
+ */
+ sym_crypt_free(cd);
+ cd = NULL;
return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d);
+ }
+
+ return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name);
+success:
/* 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);
DissectedImage *m,
const char *passphrase,
const VeritySettings *verity,
- DissectImageFlags flags,
- DecryptedImage **ret) {
+ DissectImageFlags flags) {
#if HAVE_LIBCRYPTSETUP
_cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL;
if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t))
return -EINVAL;
- if (!m->encrypted && !m->verity_ready) {
- *ret = NULL;
+ if (!m->encrypted && !m->verity_ready)
return 0;
- }
#if HAVE_LIBCRYPTSETUP
- d = new0(DecryptedImage, 1);
- if (!d)
- return -ENOMEM;
+ r = decrypted_image_new(&d);
+ if (r < 0)
+ return r;
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
}
}
- *ret = TAKE_PTR(d);
+ m->decrypted_image = TAKE_PTR(d);
return 1;
#else
DissectedImage *m,
const char *passphrase,
const VeritySettings *verity,
- DissectImageFlags flags,
- DecryptedImage **ret) {
+ DissectImageFlags flags) {
_cleanup_strv_free_erase_ char **z = NULL;
int n = 3, r;
n--;
for (;;) {
- r = dissected_image_decrypt(m, passphrase, verity, flags, ret);
+ r = dissected_image_decrypt(m, passphrase, verity, flags);
if (r >= 0)
return r;
if (r == -EKEYREJECTED)
}
}
-int decrypted_image_relinquish(DecryptedImage *d) {
+static int decrypted_image_relinquish(DecryptedImage *d) {
assert(d);
/* Turns on automatic removal after the last use ended for all DM devices of this image, and sets a
return 0;
}
+int dissected_image_relinquish(DissectedImage *m) {
+ int r;
+
+ assert(m);
+
+ if (m->decrypted_image) {
+ r = decrypted_image_relinquish(m->decrypted_image);
+ if (r < 0)
+ return r;
+ }
+
+ if (m->loop)
+ loop_device_relinquish(m->loop);
+
+ return 0;
+}
+
static char *build_auxiliary_path(const char *image, const char *suffix) {
const char *e;
char *n;
return r;
}
+int dissect_loop_device(
+ LoopDevice *loop,
+ const VeritySettings *verity,
+ const MountOptions *mount_options,
+ DissectImageFlags flags,
+ DissectedImage **ret) {
+
+ _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
+ int r;
+
+ assert(loop);
+ assert(ret);
+
+ r = dissect_image(loop->fd, loop->node, loop->backing_file ?: loop->node, verity, mount_options, flags, &m);
+ if (r < 0)
+ return r;
+
+ m->loop = loop_device_ref(loop);
+
+ *ret = TAKE_PTR(m);
+ return 0;
+}
+
int dissect_loop_device_and_warn(
- const LoopDevice *loop,
+ LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
DissectImageFlags flags,
const char *image,
DissectImageFlags flags,
char **ret_directory,
- LoopDevice **ret_loop_device,
- DecryptedImage **ret_decrypted_image) {
+ LoopDevice **ret_loop_device) {
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
_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;
assert(image);
assert(ret_directory);
assert(ret_loop_device);
- assert(ret_decrypted_image);
r = verity_settings_load(&verity, image, NULL, NULL);
if (r < 0)
if (r < 0)
return r;
- r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, flags, &decrypted_image);
+ r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, flags);
if (r < 0)
return r;
if (r < 0)
return r;
- 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);
+ r = dissected_image_relinquish(dissected_image);
+ if (r < 0)
+ return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
*ret_directory = TAKE_PTR(created_dir);
*ret_loop_device = TAKE_PTR(d);
- *ret_decrypted_image = TAKE_PTR(decrypted_image);
return 0;
}
const char *required_sysext_scope) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
- _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
DissectImageFlags dissect_image_flags;
dissected_image,
NULL,
&verity,
- dissect_image_flags,
- &decrypted_image);
+ dissect_image_flags);
if (r < 0)
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
return log_debug_errno(r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", dissected_image->image_name);
}
- if (decrypted_image) {
- r = decrypted_image_relinquish(decrypted_image);
- if (r < 0)
- return log_debug_errno(r, "Failed to relinquish decrypted image: %m");
- }
-
- loop_device_relinquish(loop_device);
+ r = dissected_image_relinquish(dissected_image);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to relinquish dissected image: %m");
return 0;
}